Swift闭包
1.闭包
闭包(Closures)是自包含的功能代码块,可以在代码中使用或者用来作为参数传值。
1.1.语法
1 |
|
示例:
1 |
|
1.2.优化
Swift对闭包做了很多优化:
- 根据上下文推断参数和返回值类型;
- 从单行表达式闭包中隐式返回(闭包体只有一行代码,可以省略return);
- 可以使用简化参数名,如$0, $1(从0开始,表示第i个参数);
- 提供了尾随闭包语法(Trailing closure syntax);
示例:
版本1:对数组内元素做排序
1 |
|
版本2:使用闭包
1 |
|
版本3:写在一行
1 |
|
版本4:根据上下文推断参数和返回值类型
1 |
|
版本5:闭包体只有一行代码,可以省略return
关键字
1 |
|
版本6:使用简化参数名
1 |
|
甚至还可以继续简化:
1 |
|
版本7:使用尾随闭包语法
1 |
|
2.@escaping
A closure is said to escape a function when the closure is passed as an argument to the function, but is called after the function returns. When you declare a function that takes a closure as one of its parameters, you can write @escaping before the parameter’s type to indicate that the closure is allowed to escape.
当闭包作为函数的参数,在函数return
之后被调用时,我们就说这个闭包从函数中逃离,即逃逸闭包
,使用@escaping
来标示。
这种情况常见于函数中发起了一个异步请求并把闭包作为异步操作的回调。
1 |
|
3.autoclosure
An autoclosure is a closure that’s automatically created to wrap an expression that’s being passed as an argument to a function. It doesn’t take any arguments, and when it’s called, it returns the value of the expression that’s wrapped inside of it. This syntactic convenience lets you omit braces around a function’s parameter by writing a normal expression instead of an explicit closure.
自动闭包,一种自动创建的闭包,用来把作为函数参数的表达式
自动封装成闭包
。
示例1:
1 |
|
示例分析:
- 函数接受一个闭包作为参数;
- 闭包不接受任何参数;
- 闭包被调用时,返回一个值;
- 值为true时,执行打印;
示例调用:
1 |
|
优化闭包的调用:
1 |
|
继续优化,使用尾随闭包写法:
1 |
|
但调用时不管怎么优化,要么书写起来十分麻烦,要么表达上不太清晰,于是自动闭包登场了。
1 |
|
这里改换了原方法的参数,在闭包的类型前加上@autoclosure
关键字,再次调用:
1 |
|
Swift 会把2 > 1
这个表达式自动转换为()->Bool
类型的闭包。
自动闭包的好处是:
- 调用时写法简单,表意清楚;
- 允许延迟处理,对于闭包内有副作用或占用资源的代码,直到你调用闭包时才会运行。
示例2:用 Swift 实现或(||)操作
最常见的做法是:
1 |
|
这种做法也没错,但并不高效。||
的本质是短路操作,即当左边为真时,无需再计算右边。而上面这种是将右边默认值预先准备好,再传入进行操作。当右边值的计算十分复杂时会造成性能上的浪费。所以,上面这种做法违反了||
操作的本质。正确的实现方法如下:
1 |
|
输出日志
1 |
|
可以看出,autoclosure 可以将右边值的计算推迟到判定left为false的时候,这样就可以避免第一种方法带来的不必要开销了。
注意:自动闭包不接受任何参数,只有形如()->T
的参数才能使用这个特性进行简化。
4.捕获变量
4.1.捕获引用
A closure can capture constants and variables from the surrounding context in which it is defined. The closure can then refer to and modify the values of those constants and variables from within its body, even if the original scope that defined the constants and variables no longer exists.
As an optimization, Swift may instead capture and store a copy of a value if that value is not mutated by a closure, and if the value is not mutated after the closure is created.
Swift also handles all memory management involved in disposing of variables when they are no longer needed.
OC 中 block 会捕获变量,且捕获的是变量的值。
Swift 的闭包也会自动捕获其上下文中定义的变量,但默认捕获的是变量的引用
,这样就可以在闭包内修改它们的值。换句话说,Swift 闭包中变量的默认行为与 OC 中__block
变量一致。
#示例:
1 |
|
num
是局部变量,它在block1
中和之后都被修改了,而这两处改变也都影响了最终打印的信息。这说明block1
中是对num
变量进行了引用,而非值的复制,这与OC中 block 对变量的捕获有很大的不同。
4.2.强制捕获值
如果不想被引用而是被复制,则可以使用捕获列表
:
#示例:
1 |
|
定义捕获列表之后,num
在闭包中被捕获,但这次是被复制且成为了一个常量,不能在闭包内被修改。闭包之后的修改也并未影响到闭包内的打印结果,这才有点像OC中的 block。
Swift 出于性能考虑会对闭包做一些优化,比如它会自动判断你是否在闭包内或闭包外修改了变量,如果没有则会直接持有一份该变量的拷贝。
5.循环引用
5.1.原因
A strong reference cycle can also occur if you assign a closure to a property of a class instance, and the body of that closure captures the instance. This capture might occur because the closure’s body accesses a property of the instance, such as self.someProperty, or because the closure calls a method on the instance, such as self.someMethod(). In either case, these accesses cause the closure to “capture” self, creating a strong reference cycle.
与 OC 中的 block 类似,Swift 闭包也会强引用被它捕获的对象,从而引发可能的循环引用问题。
比如对象持有一个闭包属性,而闭包体中通过self.
调用了对象的属性或方法,从而捕获了self 本身,造成循环引用。
#示例1:
1 |
|
5.2.解决方案1:捕获列表
You resolve a strong reference cycle between a closure and a class instance by defining a capture list as part of the closure’s definition.Each item in a capture list is a pairing of the weak or unowned keyword with a reference to a class instance (such as self) or a variable initialized with some value (such as delegate = self.delegate!).
捕获列表
也可以解决闭包的循环引用问题,把被捕获的变量标记为weak
或 unowned
即可。
- 给带参数的闭包定义捕获列表:
1 |
|
- 给不带参数的闭包定义捕获列表:
1 |
|
所以,上面#示例1中的问题可以这样解决:
1 |
|
VC虽然强引用了闭包,但是闭包对VC的引用变成了弱引用,不增加VC的引用计数,当指向VC的其他强引用都被移除后,其引用计数为0,即可正常销毁。
区分weak
与unowned
:
- weak
A weak reference is a reference that does not keep a strong hold on the instance it refers to, and so does not stop ARC from disposing of the referenced instance.
Use a weak reference when the other instance has a shorter lifetime—that is, when the other instance can be deallocated first.
- unowned
Like a weak reference, an unowned reference does not keep a strong hold on the instance it refers to. Unlike a weak reference, however, an unowned reference is used when the other instance has the same lifetime or a longer lifetime.
Use an unowned reference only when you are sure that the reference always refers to an instance that has not been deallocated.
If you try to access the value of an unowned reference after that instance has been deallocated, you’ll get a runtime error.
weak
与unowned
的作用类似,都是用来解决循环引用问题。区别在于:
1.生命周期:
weak 对象的生命周期一般 < weak 对象所在的类的实例的生命周期,当访问该 weak 对象时它可能已经被释放了,比如 delegate、房子中的租客。因此,weak 修饰的属性一定是optional
值。
1 |
|
日志:
1 |
|
说明两个对象都已经顺利释放了~
unowned 对象的生命周期一般 >= unowned 对象所在的类的实例的生命周期。比如 Customer 与 CreditCard,人可能没有信用卡,但信用卡一定得有个主人,Customer的生命周期比 CreditCard 长。因此,unowned 修饰的属性不能是optional
值,也不能指向 nil。
1 |
|
日志:
1 |
|
两个实例都顺利析构并释放内存~
2.野指针问题
weak 修饰可选对象,当引用的对象被释放时,可选对象自动变成nil,继续访问该对象时不会闪退。
unowned 相当于OC中的unsafe_unretained
,也不会增加引用计数,其引用的对象被释放后,它依然会保持对已被释放对象的一个无效引用,继续访问该对象会闪退。
5.3.解决方案2:用结构体
循环引用,从其命名来看实际上是两个问题:
- 循环
- 引用
即对象之间出现了相互引用的怪圈。在解决此类问题时,我们的第一反应往往是使用weak
或unowned
来弱引用对象,从而打破这个环,这解决了第一个问题;
其实我们也可以从第二个问题来入手:仔细回想一下,我们所见到的循环引用一般都是出现在两个或多个引用
类型之间,比如闭包
和类
之间。所以换个角度来想,如果将引用类型改成值类型,那么也就不存在相互引用
的情况了,比如可能的话,将某些类
改成值类型的结构体
来实现:
1 |
|
调用:
1 |
|
日志:
1 |
|
分析:
- 客户对象是结构体,作为信用卡的参数时是值的拷贝而非引用,因此不存在相互引用一说;
- 作为值类型的客户对象,在出了方法体之后被自动释放。
综上,解决循环引用问题时,可以从弱化引用和替换成值类型两处入手~
相关参考: