Swift系列十 - inout的本质
inout
是可以用来在函数内部修改外部属性内存的。
一、inout回顾
示例代码:
func test(_ num: inout Int) {
num = 20
}
var a = 10
test(&a)
print(a) // 输出:20
test(&a)
通过汇编分析,全局变量a
的地址0x6c52(%rip)
传递给了寄存器rdi
,rdi
作为参数传递给了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)
首先调用了girth
的getter
方法; - 然后
getter
方法会返回一个值,这个值放在临时空间内(局部变量); - 调用
test
方法时是把getter
返回的临时变量作为参数传递的(传递的还是地址值),这时候在test
方法内部修改的是临时变量内存的值; - 当修改局部变量内存时,会调用
girth
的setter
方法,把局部变量的值作为参数传递; - 最终的结果就是值被修改了。
结论:
由于计算属性没有自己的地址值,所以会调用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
函数执行完成后,开始执行side
的setter
方法,并把之前的局部变量rdi
作为参数传递过去;willset
之前没有修改rdi
,所以rdi
保存的还是20,并且作为第一个参数传递给了willset
;- 由于
willset
之后才会真正修改属性值,并且didset
之前已经知道修改过的属性值,所以真正修改属性值是在willset
和didset
之间;
结论:
修改带有属性观察器的存储属性值时,和计算属性的过程有点类似。先拿到属性的值给局部变量,然后把局部变量的地址值传递给需要修改的函数,函数内部会修改局部变量的值。函数执行完成后把已经修改过的局部变量的值赋值给属性。赋值时,优先执行属性的willset
方法,willset
执行结束后,才会真正修改属性的值,最后调用didset
。
小技巧:需要传递
inout
参数的函数,业务逻辑是非常独立的,目的仅仅是修改传递过来的参数值,不会影响计算属性/存储属性(属性观察器)的逻辑,所以除了计算属性可以直接传地址,其他属性都需要一个局部变量做一个中转。
2.4. inout的本质总结
如果实参有物理内存地址,且没有设置属性观察器
- 直接将实参的内存地址传入函数(实参进行引用传递)
如果实参是计算属性或设置了属性观察器,采取Copy In Copy Out的做法:
- 调用该函数时,先复制实参的值,产生一个副本(局部变量-执行
get
方法) - 将副本的内存地址传入函数(副本进行引用传递),在函数内部可以修改副本的值
- 函数返回后,再将副本的值覆盖实参的值(执行
set
方法)
- 调用该函数时,先复制实参的值,产生一个副本(局部变量-执行
总结:inout
的本质就是引用传递(地址传递)。
什么是Copy In Copy Out?先Copy到函数里,修改后再Copy到外面。
Swift系列十 - inout的本质的更多相关文章
- Alamofire源码解读系列(十二)之时间轴(Timeline)
本篇带来Alamofire中关于Timeline的一些思路 前言 Timeline翻译后的意思是时间轴,可以表示一个事件从开始到结束的时间节点.时间轴的概念能够应用在很多地方,比如说微博的主页就是一个 ...
- Alamofire源码解读系列(十二)之请求(Request)
本篇是Alamofire中的请求抽象层的讲解 前言 在Alamofire中,围绕着Request,设计了很多额外的特性,这也恰恰表明,Request是所有请求的基础部分和发起点.这无疑给我们一个Req ...
- 学习ASP.NET Core Razor 编程系列十二——在页面中增加校验
学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...
- Web 前端开发精华文章推荐(jQuery、HTML5、CSS3)【系列十二】
2012年12月12日,[<Web 前端开发人员和设计师必读文章>系列十二]和大家见面了.梦想天空博客关注 前端开发 技术,分享各种增强网站用户体验的 jQuery 插件,展示前沿的 HT ...
- 窥探Swift系列博客说明及其Swift版本间更新
Swift到目前为止仍在更新,每次更新都会推陈出新,一些Swift旧版本中的东西在新Swift中并不适用,而且新版本的Swift会添加新的功能.到目前为止,Swift为2.1版本.去年翻译的Swift ...
- Web 前端开发精华文章集锦(jQuery、HTML5、CSS3)【系列十九】
<Web 前端开发精华文章推荐>2013年第七期(总第十九期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各种增强网站用户体验的 jQuery 插件,展示前沿的 HTML5 和 C ...
- Web 前端开发精华文章集锦(jQuery、HTML5、CSS3)【系列十八】
<Web 前端开发精华文章推荐>2013年第六期(总第十八期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各种增强网站用户体验的 jQuery 插件,展示前沿的 HTML5 和 C ...
- SQL Server 2008空间数据应用系列十二:Bing Maps中呈现GeoRSS订阅的空间数据
原文:SQL Server 2008空间数据应用系列十二:Bing Maps中呈现GeoRSS订阅的空间数据 友情提示,您阅读本篇博文的先决条件如下: 1.本文示例基于Microsoft SQL Se ...
- SQL Server 2008空间数据应用系列十:使用存储过程生成GeoRSS聚合空间信息
原文:SQL Server 2008空间数据应用系列十:使用存储过程生成GeoRSS聚合空间信息 友情提示,您阅读本篇博文的先决条件如下: 1.本文示例基于Microsoft SQL Server 2 ...
随机推荐
- Go语言操作数据库及其常规操作
Go操作MySQL 安装: go get -u github.com/go-sql-driver/mysql GO语言的操作数据库的驱动原生支持连接池, 并且是并发安全的 标准库没有具体的实现 只是列 ...
- 了解什么是redis的雪崩和穿透?redis崩溃之后会怎么样?系统该如何应对这种情况?如何处理redis的穿透?
缓存雪崩发生的现象 缓存雪崩的事前事中事后的解决方案 事前:redis高可用,主从+哨兵,redis cluster,避免全盘崩溃 事中:本地ehcache缓存 + hystrix限流&降级, ...
- MySQL提升笔记(4)InnoDB存储结构
这一节本来计划开始索引的学习,但是在InnoDB存储引擎的索引里,存在一些数据存储结构的概念,这一节先了解一下InnodDB的逻辑存储结构,为索引的学习打好基础. 从InnoDB存储引擎的存储结构看, ...
- 这可能是最容易理解的 Go Mutex 源码剖析
Hi,大家好,我是 haohongfan. 上一篇文章<一文完全掌握 Go math/rand>,我们知道 math/rand 的 global rand 有一个全局锁,我的文章里面有一句 ...
- 死磕Spring之AOP篇 - Spring AOP自动代理(二)筛选合适的通知器
该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...
- Java多线程详解——一篇文章搞懂Java多线程
目录 1. 基本概念 2. 线程的创建和启动 2.1. 多线程实现的原理 2.2.多线程的创建,方式一:继承于Thread类 2.3.多线程的创建,方式一:创建Thread匿名子类(也属于方法一) 2 ...
- 10- JMeter5.1.1 工具快速入门
什么是JMeter JMeter是Apache组织开发的开源软件,由Java语言实现. 主要用于软件系统性能测试,他最初被设计用于web测试,后来被扩展到其他领域. Jmeter特点 http://w ...
- composer 使用提示
1.使用国内镜像[推荐] composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/ 或者 comp ...
- 【原创】ansible-playbook 详解
YAML的语法和其他高阶语言类似并且可以简单表达清单.散列表.标量等数据结构.(列表用横杆表示,键值对用冒号分割,键值对里又可以嵌套另外的键值对) YAML文件扩展名通常为.yaml或者.yml.下面 ...
- PAT 乙级 -- 1001 -- 害死人不偿命的(3n+1)猜想
题目: 卡拉兹(Callatz)猜想: 对任何一个自然数n,如果它是偶数,那么把它砍掉一半:如果它是奇数,那么把(3n+1)砍掉一半.这样一直反复砍下去,最后一定在某一步得到n=1.卡拉兹在1950年 ...