前言
Swift的闭包在capture变量时,跟oc的还是有些区别的。下面来讲讲。
一览Swift的闭包
在closure中使用外部变量,swift默认会capture该变量的reference
,之后会细说。为了避免在闭包执行时,变量释放,所以会retain该变量。来看个例子。
Animal类。
class Animal:CustomDebugStringConvertible {
let name: String
init(name: String) {
self.name = name
}
var debugDescription: String {
return "<Animal \(name)>"
}
deinit {
print("\(self) dealloc")
}
}
delay函数
func delay(seconds: NSTimeInterval, closure: ()->()) {
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC)))
dispatch_after(time, dispatch_get_main_queue()) {
closure()
}
}
func demo1() {
let animal = Animal(name: "cat")
print("before closure:\(animal)")
delay(1) {
// retain animal
print("inside closure:\(animal)")
}
print("bye")
}
output:
before closure:<Animal cat>
bye
inside closure:<Animal cat>
<Animal cat> dealloc
这里,我们用dispatch_after延迟执行一个闭包。当demo1执行完后,animal是还没有dealloc的,因为被闭包retain了。只有当闭包执行完后,才会释放掉。
好了。知道了上述理论后,来看看刚刚提到的reference。
reference
闭包默认持有变量的reference,而不是变量本身。什么意思呢?看个例子。请注意inside closure打印的那行
。
// caputure value evaluted when closure is executed
func demo2() {
var animal = Animal(name: "cat")
print("before closure:\(animal)")
delay(1) {
print("inside closure:\(animal)")
}
animal = Animal(name: "dog")
print("after closure:\(animal)")
}
ouput:
before closure:<Animal cat>
<Animal cat> dealloc
after closure:<Animal dog>
inside closure:<Animal dog>
<Animal dog> dealloc
是不是觉得有点奇怪?这就是持有reference的作用。
可以看成其持有的是指向animal的指针p,即使animal所指向的obj在变,但其实p的指针是始终不变的。看2张图应该就明白了。
p就是图中的reference。
如果熟悉c的指针的话,跟下面的代码意思类似。
char a = 'a';
char *p1 = &a;
char **p2 = &p1;
printf("%c", **p2);
char b = 'b';
p1 = &b;
// 此时**p2的值会为'b',虽然p1的指向在变,但p2-->p1的指向关系始终不变
printf("%c", **p2);
除了reference之外,还有提到的是。
1)capture的变量在闭包执行的时候才计算
。
在这个例子中,我们修改了animal为dog,闭包中打印的也是成了dog。
如果改成这样,猜猜会打印出什么?
delay(1) {
animal = Animal(name:"duck")
print("inside closure:\(animal)")
}
- animal的dealloc。在demo2执行完后,cat就dealloc了。而dog还被持有。看上图,animal--->cat之前的关联已经断了,所以会dealloc,而此时animal指向了dog,所以dog仍会等到闭包执行完后销毁。
value type
那么对于值类型的捕获呢,是否不一样?很遗憾,仍然是reference。意思是:不是简单的捕获当前的值,存起来,亘古不变了。跟上面一样,value变了之后,闭包中的值跟着变。
func demo3() {
var a = 40
print("before closure:\(a)")
delay(1) {
print("inside closure:\(a)")
}
a = 50
print("after closure:\(a)")
}
output:
before closure:40
after closure:50
inside closure:50
在闭包中修改值
可以在直接闭包中修改捕获的值,并且后续获取到的就是新值。仍然是reference的功劳。而在oc中,需要声明为__block。
func demo4() {
var a = 40
print("before closure:\(a)")
delay(1) {
print("inside closure1, before change:\(a)")
a = 60
print("inside closure1, after change:\(a)")
}
delay(2) {
print("inside closure2:\(a)")
}
}
如何做到和oc的捕获一样
如果只想capture变量在创建闭包时的值,使用capture list。它会copy一份变量,而非采用reference的方式。或者先声明个变量 let copyValue = a,跟capture list效果一样。
func demo5() {
var a = 40
print("before closure:\(a)")
let copyValue = a
delay(1) { [constantValue = a] in
print("inside closure:\(constantValue)")
print("inside closure:\(copyValue)")
}
a = 50
print("after closure:\(a)")
}
同样对于class类型的,也一样
func demo6() {
var animal = Animal(name: "cat")
print("before closure:\(animal)")
let animalCopy = animal
delay(1) { [constantAnimal = animal] in
print("inside closure constantAnimal:\(constantAnimal)")
print("inside closure constantAnimal:\(animalCopy)")
}
animal = Animal(name: "dog")
print("after closure:\(animal)")
}
终极混合
来个各种混合的。
func demo7() {
var animal = Animal(name: "cat")
print("before closure:\(animal)")
delay(1) { [constantAnimal = animal] in
print("inside closure1 constantAnimal:\(constantAnimal)")
print("inside closure1:\(animal)")
animal = Animal(name: "snake")
print("inside closure1 animal changed:\(animal)")
}
animal = Animal(name: "dog")
print("animal changed:\(animal)")
delay(2) { [constantAnimal = animal] in
print("inside closure2 constantAnimal:\(constantAnimal)")
print("inside closure2:\(animal)")
animal = Animal(name: "bear")
print("inside closure2 animal changed:\(animal)")
}
}
看看会是神马结果??
后语
- swift闭包默认持有变量的reference
- swift闭包默认在执行时才计算捕获变量的值
- 可在swift闭包中修改捕获变量的值
- 使用capture list,做变量的constant copy捕获。