这里每天分享一个 iOS 的新知识,快来关注我吧
前言
上次介绍了 mutating 关键字的用法和实现原理,其中就提到他的实现原理是在方法中传入了一个被标记了 inout 的 self。
今天就来讲讲 inout 这个关键字的实现原理,以及在实际应用中遇到的坑。
inout 的使用
默认情况下,传递到方法中的所有参数都是常量 (let),因此无法更改它们。如果需要在函数内部更改参数,可以传入一个或多个参数标记为 inout,这意味着它们可以在函数内部更改,并且这些更改会反映在函数外部的原始值中。
举个例子,如果你想将一个数字乘以 2,写一个函数,传入这个数字,在函数内部直接修改这个数字,而不是返回新值,可以编写如下函数:
func doubleInPlace(number: inout Int) {
number *= 2
}
要使用这个函数,你首先需要创建一个可变整数,不能将常量整数与 inout 参数一起使用,传入的时候还需要在其名称之前使用 & 符号:
var myNum = 10
doubleInPlace(number: &myNum)
print(myNum) // 打印:20
注意:inout 参数不能有默认值,而且可变参数不能标记为 inout
inout 的实现原理
首先,当函数被调用时,inout 参数的值会被复制一份(调用 get 方法),在函数体内修改这个参数,其实是副本被修改了。在函数最后结束的时候,副本的值将赋值给原始参数(调用 set 方法)。
这是苹果文档中的描述,这种行为被称为 copy-in,copy-out,当计算属性或具有观察者的属性作为输入输出参数传递时,其 getter 和 setter 方法都会被调用。
苹果针对这种行为还做了一些优化,当参数是存储在内存中物理地址的值时,函数体内部和外部都使用相同的内存地址,这种优化称为引用调用,这么优化的好处是避免了一次复制的开销。
可能存在的坑
上边介绍原理的时候说过,copy-in,copy-out 的过程会调用属性的 get 和 set 方法,这里可能存在一个坑,因为即使你在函数中没有改动这个属性,get 和 set 依然会执行一次,我们来验证一下:
struct MyStruct {
var name: String = "iOS 新知" {
willSet {
print("willSet \(newValue)")
}
didSet {
print("didSet \(name)")
}
}
}
func changeName(_ name: inout String) {
// 什么都不做
}
var s = MyStruct()
changeName(&s.name)
首先定义了一个结构体 MyStruct,其中有一个名为 name 的存储属性,对该属性添加了 willSet 和 didSet 观察器。
然后定义了一个 changeName 函数,参数是 inout 类型的 String,后面创建 MyStruct 的实例 s,并调用 changeName 函数,传入 s.name的引用。
在 changeName 中对 name 并没有进行修改,但是输出结果是
willSet iOS 新知
didSet iOS 新知
证明 willSet 和 didSet 被调用了。
这里每天分享一个 iOS 的新知识,快来关注我吧
本文同步自微信公众号 “iOS新知”,每天准时分享一个新知识,这里只是同步,想要及时学到就来关注我吧!