inout是可以用来在函数内部修改外部属性内存的。

一、inout回顾

示例代码:

func test(_ num: inout Int) {
num = 20
}
var a = 10
test(&a)
print(a) // 输出:20
test(&a)

通过汇编分析,全局变量a的地址0x6c52(%rip)传递给了寄存器rdirdi作为参数传递给了test函数,所以inout的本质就是引用传递(地址传递)。

二、inout本质

示例代码:

struct Shape {
var width: Int
var side: Int {
willSet {
print("willSet", newValue)
}
didSet {
print("didSet", oldValue, side)
}
}
var girth: Int {
set {
print("setGirth")
width = newValue / side
}
get {
print("getGirth")
return width * side
}
}
func show() {
print("width=\(width), side=\(side), girth=\(girth)")
}
} func test(_ num: inout Int) {
print("test")
num = 20
}
var s = Shape(width: 10, side: 4)

2.1. 存储属性

test(&s.width)
s.show()
/*
输出:
test
getGirth
width=20, side=4, girth=80
*/

分析:

  • 0x6c9d(%rip)是全局变量s的地址值;
  • s的内存地址和结构体Shape中第一个存储属性的地址是相同的(值类型);
  • 相当于把实例s中存储属性width的内存地址传给了test函数;
  • 所以结构体的存储属性使用inout的本质和全局/局部变量都一样。

结论:

由于存储属性有自己的内存地址,所以直接把存储属性的地址传递给需要修改的函数,在函数内部修改存储属性的值。

2.2. 计算属性

test(&s.girth)
s.show()
/*
输出:
getGirth
test
setGirth
getGirth
width=5, side=4, girth=20
*/

> 思考:上面的代码中s.girth也是地址传递么?答案:不是,因为girth不是存储属性,所以不占用结构体的内存,但是使用&s.girth不会报错,并且正常读写值,所以编译器是允许我们这么做的。那它是如何传递修改值的呢?

分析:

  • 执行代码test(&s.girth)首先调用了girthgetter方法;
  • 然后getter方法会返回一个值,这个值放在临时空间内(局部变量);
  • 调用test方法时是把getter返回的临时变量作为参数传递的(传递的还是地址值),这时候在test方法内部修改的是临时变量内存的值;
  • 当修改局部变量内存时,会调用girthsetter方法,把局部变量的值作为参数传递;
  • 最终的结果就是值被修改了。

结论:

由于计算属性没有自己的地址值,所以会调用getter方法获取一个局部变量,把局部变量的值传递给需要修改的函数,在函数内部修改局部变量的值,最后把局部变量的值传递给setter方法。

2.3. 属性观察器

test(&s.side)
s.show()
/*
输出:
test
willSet 20
didSet 4 20
getGirth
width=10, side=20, girth=200
*/







分析:

  • 取出0x6cc3(%rip)的前8个字节给rax,而0x6cc3(%rip)的本质就是存储属性side(通过汇编注释可以看出s偏移8个字节,而width占用8个字节,跳过width就是side);
  • rax的值又给了局部变量-0x28(%rbp)
  • 然后把局部变量rdi的值传递给了test函数,通过打印发现rdi保存的值就是20;
  • test函数执行完成后,开始执行sidesetter方法,并把之前的局部变量rdi作为参数传递过去;
  • willset之前没有修改rdi,所以rdi保存的还是20,并且作为第一个参数传递给了willset
  • 由于willset之后才会真正修改属性值,并且didset之前已经知道修改过的属性值,所以真正修改属性值是在willsetdidset之间;

结论:

修改带有属性观察器的存储属性值时,和计算属性的过程有点类似。先拿到属性的值给局部变量,然后把局部变量的地址值传递给需要修改的函数,函数内部会修改局部变量的值。函数执行完成后把已经修改过的局部变量的值赋值给属性。赋值时,优先执行属性的willset方法,willset执行结束后,才会真正修改属性的值,最后调用didset

小技巧:需要传递inout参数的函数,业务逻辑是非常独立的,目的仅仅是修改传递过来的参数值,不会影响计算属性/存储属性(属性观察器)的逻辑,所以除了计算属性可以直接传地址,其他属性都需要一个局部变量做一个中转。

2.4. inout的本质总结

  1. 如果实参有物理内存地址,且没有设置属性观察器

    • 直接将实参的内存地址传入函数(实参进行引用传递)
  2. 如果实参是计算属性或设置了属性观察器,采取Copy In Copy Out的做法:

    • 调用该函数时,先复制实参的值,产生一个副本(局部变量-执行get方法)
    • 将副本的内存地址传入函数(副本进行引用传递),在函数内部可以修改副本的值
    • 函数返回后,再将副本的值覆盖实参的值(执行set方法)

总结:inout的本质就是引用传递(地址传递)。

什么是Copy In Copy Out?先Copy到函数里,修改后再Copy到外面。

Swift系列十 - inout的本质的更多相关文章

  1. Alamofire源码解读系列(十二)之时间轴(Timeline)

    本篇带来Alamofire中关于Timeline的一些思路 前言 Timeline翻译后的意思是时间轴,可以表示一个事件从开始到结束的时间节点.时间轴的概念能够应用在很多地方,比如说微博的主页就是一个 ...

  2. Alamofire源码解读系列(十二)之请求(Request)

    本篇是Alamofire中的请求抽象层的讲解 前言 在Alamofire中,围绕着Request,设计了很多额外的特性,这也恰恰表明,Request是所有请求的基础部分和发起点.这无疑给我们一个Req ...

  3. 学习ASP.NET Core Razor 编程系列十二——在页面中增加校验

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  4. Web 前端开发精华文章推荐(jQuery、HTML5、CSS3)【系列十二】

    2012年12月12日,[<Web 前端开发人员和设计师必读文章>系列十二]和大家见面了.梦想天空博客关注 前端开发 技术,分享各种增强网站用户体验的 jQuery 插件,展示前沿的 HT ...

  5. 窥探Swift系列博客说明及其Swift版本间更新

    Swift到目前为止仍在更新,每次更新都会推陈出新,一些Swift旧版本中的东西在新Swift中并不适用,而且新版本的Swift会添加新的功能.到目前为止,Swift为2.1版本.去年翻译的Swift ...

  6. Web 前端开发精华文章集锦(jQuery、HTML5、CSS3)【系列十九】

    <Web 前端开发精华文章推荐>2013年第七期(总第十九期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各种增强网站用户体验的 jQuery 插件,展示前沿的 HTML5 和 C ...

  7. Web 前端开发精华文章集锦(jQuery、HTML5、CSS3)【系列十八】

    <Web 前端开发精华文章推荐>2013年第六期(总第十八期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各种增强网站用户体验的 jQuery 插件,展示前沿的 HTML5 和 C ...

  8. SQL Server 2008空间数据应用系列十二:Bing Maps中呈现GeoRSS订阅的空间数据

    原文:SQL Server 2008空间数据应用系列十二:Bing Maps中呈现GeoRSS订阅的空间数据 友情提示,您阅读本篇博文的先决条件如下: 1.本文示例基于Microsoft SQL Se ...

  9. SQL Server 2008空间数据应用系列十:使用存储过程生成GeoRSS聚合空间信息

    原文:SQL Server 2008空间数据应用系列十:使用存储过程生成GeoRSS聚合空间信息 友情提示,您阅读本篇博文的先决条件如下: 1.本文示例基于Microsoft SQL Server 2 ...

随机推荐

  1. java面试一日一题:mysql中常用的存储引擎有哪些?

    问题:请讲下mysql中常用的引擎有哪些? 分析:该问题主要考察对mysql存储引擎的理解,及区别是什么? 回答要点: 主要从以下几点去考虑, 1.mysql的存储引擎的基本概念? 2.mysql中常 ...

  2. 自动化kolla-ansible部署ubuntu20.04+openstack-victoria之镜像制作centos7.8-15

    自动化kolla-ansible部署ubuntu20.04+openstack-victoria之镜像制作centos7.8-15 欢迎加QQ群:1026880196 进行交流学习   制作OpenS ...

  3. python set 一些用法

    add(增加元素) name = set(['Tom','Lucy','Ben']) name.add('Juny') print(name)#输出:{'Lucy', 'Juny', 'Ben', ' ...

  4. 【Scrapy(四)】scrapy 分页爬取以及xapth使用小技巧

    scrapy 分页爬取以及xapth使用小技巧 这里以爬取www.javaquan.com为例: 1.构建出下一页的url: 很显然通过dom树,可以发现下一页所在的a标签   2.使用scrapy的 ...

  5. OAuth2(未完待续)

    一.OAuth2是什么?OAuth2解决了什么问题 1.OAuth2是第三方授权协议,用于支撑认证和授权 2.OAuth2中的角色划分: 资源拥有者 客户端 资源服务器 授权服务器 二.OAuth2怎 ...

  6. 【原创】ansible-playbook 详解

    YAML的语法和其他高阶语言类似并且可以简单表达清单.散列表.标量等数据结构.(列表用横杆表示,键值对用冒号分割,键值对里又可以嵌套另外的键值对) YAML文件扩展名通常为.yaml或者.yml.下面 ...

  7. VPS、云主机 and 服务器集群、云计算 的区别

    VPS:(virtual private server)虚拟专用服务器,将一台服务器分割成多个虚拟专享服务器的优质服务.实现VPS的技术分为容器技术和虚拟化技术.在容器或虚拟机中,每个VPS都可分配独 ...

  8. Python中的可迭代Iterable和迭代器Iterator

    目录 Iterable可迭代对象 如何判断对象是否是可迭代对象Iterable Iterator迭代器 如何判断对象是否迭代器Iterator 将Iterable转换成Iterator Iterabl ...

  9. Jenkins反序列化漏洞复现

    Jenkins Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能. Jenkins功能包括: 持 ...

  10. CCNA 第二章 以太网回顾

    1:半双工和全双工 (1):半双工:类似于单车道: (2):全双工:类似是双向多车道: 2:思科三层模型 (1): (2):核心层.集散层(汇聚层).接入层各功能: 1:核心层:大量数据快速交换:不要 ...