闭包捕获语义第一弹:一网打尽!
尽管现在已经是 ARC 的天下了,但对于程序员来说理解内存管理和对象的生命周期依然是一门必修课。对于在 Swift 当中广泛应用的闭包就是其中一个特殊的例子,与 Objc 的闭包相比,Swift 的闭包也有着不同的捕获语义。下面让我们看看闭包是如何工作的。 介绍在 Swift 中,闭包捕获他们所引用的变量:虽然这些变量在闭包之外声明,但只要在闭包内使用都会默认被闭包保留引用(retain),这是为了确保闭包执行时,这些变量还活着(译者注:没有被提前释放)。 在文章接下来的部分,我们来定义一个简单的 class Pokemon: CustomDebugStringConvertible { let name: String init(name: String) { self.name = name } var debugDescription: String { return "<Pokemon (name)>" } deinit { print("(self) escaped!") } } 接下来声明一个简单的函数,他接受一个闭包作为参数,然后在一段时间后执行这个闭包(使用 GCD)。下面的例子展示了闭包是如何捕获外部变量的。 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()) { print("?") closure() } }
func delay(seconds: Int,closure: ()->()) { let time = DispatchTime.now() + .seconds(seconds) DispatchQueue.main.after(when: time) { print("?") closure() } } 默认的捕获语义现在,先从一个简单的例子开始: func demo1() { let pokemon = Pokemon(name: "Mewtwo") print("before closure: (pokemon)") delay(1) { print("inside closure: (pokemon)") } print("bye") } 这个例子看上去很简单,但它有趣的地方在于闭包的运行被推迟了 1 秒钟,所以当 demo1() 函数执行完毕后,闭包才开始执行;并且 1 秒后当闭包被执行的时候 before closure: <Pokemon Mewtwo> bye ? inside closure: <Pokemon Mewtwo> <Pokemon Mewtwo> escaped! 这是因为闭包捕获(强引用)了 因此,闭包有点像精灵球 ?,只要你持有着 在这个例子中,一旦 GCD 执行完毕,闭包就会被释放,所以
被捕获的变量在执行时才取值有一点值得注意的是 Swift 在闭包执行时才会取出捕获变量的值1。我们可以认为它之前捕获的是变量的引用(或指针)。 这里有一个有趣的例子: func demo2() { var pokemon = Pokemon(name: "Pikachu") print("before closure: (pokemon)") delay(1) { print("inside closure: (pokemon)") } pokemon = Pokemon(name: "Mewtwo") print("after closure: (pokemon)") } 你能猜猜打印的结果吗?答案如下: before closure: <Pokemon Pikachu> <Pokemon Pikachu> escaped! after closure: <Pokemon Mewtwo> ? inside closure: <Pokemon Mewtwo> <Pokemon Mewtwo> escaped! 请注意我们在创建完闭包之后修改了 具体的细节为:首先初始化一个值为 Pikachu 的 这个特性对于值类型也是一样的,关于这一点或许会有些奇怪,比如下面例子中的 func demo3() { var value = 42 print("before closure: (value)") delay(1) { print("inside closure: (value)") } value = 1337 print("after closure: (value)") } 打印结果: before closure: 42 after closure: 1337 ? inside closure: 1337 你没看错,闭包打印了新的整型变量值---尽管整型变量是值类型!---因为它捕获了变量的引用,而不是变量自身的内容! 你可以修改闭包中捕获的变量如果捕获的是变量 func demo4() { var value = 42 print("before closure: (value)") delay(1) { print("inside closure 1,before change: (value)") value = 1337 print("inside closure 1,after change: (value)") } delay(2) { print("inside closure 2: (value)") } } 代码的打印结果如下: before closure: 42 ? inside closure 1,before change: 42 inside closure 1,after change: 1337 ? inside closure 2: 1337 变量 捕获一个变量作为一个常量拷贝如果想要在闭包创建时捕获变量的值,而不是在闭包执行时才去获取变量的值,你可以使用 捕获列表 捕获列表写在闭包的方括号之间,紧跟闭包的左括号(并且在闭包的参数或返回类型之前)3 在创建闭包时捕获变量的值(而不是变量的引用),你可以使用 func demo5() { var value = 42 print("before closure: (value)") delay(1) { [constValue = value] in print("inside closure: (constValue)") } value = 1337 print("after closure: (value)") } 打印结果: before closure: 42 after closure: 1337 ? inside closure: 42 与上面的 这就是 回到 Pokemon 上正如我们上面所看到的:如果一个变量是引用类型---就像我们的 Pokemon 类,闭包并没有真正(强引用)捕获变量的引用,而是捕获了一个针对原始实例 pokemon 的拷贝: func demo6() { var pokemon = Pokemon(name: "Pikachu") print("before closure: (pokemon)") delay(1) { [pokemonCopy = pokemon] in print("inside closure: (pokemonCopy)") } pokemon = Pokemon(name: "Mewtwo") print("after closure: (pokemon)") } 这类似创建了一个中间变量指向同一个 pokemon,然后捕获了这个中间变量: func demo6_equivalent() { var pokemon = Pokemon(name: "Pikachu") print("before closure: (pokemon)") // here we create an intermediate variable to hold the instance // pointed by the variable at that point in the code: let pokemonCopy = pokemon delay(1) { print("inside closure: (pokemonCopy)") } pokemon = Pokemon(name: "Mewtwo") print("after closure: (pokemon)") } 事实上,使用捕获列表完全等同于上述代码的行为...除了中间变量 相比 before closure: <Pokemon Pikachu> after closure: <Pokemon Mewtwo> <Pokemon Mewtwo> escaped! ? inside closure: <Pokemon Pikachu> <Pokemon Pikachu> escaped! 以下是详细过程:
与此刚好相反,我们来分析下
知识点整合上面的知识点都掌握了吗?我承认,确实有点多... 下面是一个人为设计的例子,它包含了闭包创建时就对变量取值---归功于捕获列表,以及先捕获变量的引用,而真正的取值放到闭包执行时这两种情形: func demo7() { var pokemon = Pokemon(name: "Mew") print("?? Initial pokemon is (pokemon)") delay(1) { [capturedPokemon = pokemon] in print("closure 1 — pokemon captured at creation time: (capturedPokemon)") print("closure 1 — variable evaluated at execution time: (pokemon)") pokemon = Pokemon(name: "Pikachu") print("closure 1 - pokemon has been now set to (pokemon)") } pokemon = Pokemon(name: "Mewtwo") print("? pokemon changed to (pokemon)") delay(2) { [capturedPokemon = pokemon] in print("closure 2 — pokemon captured at creation time: (capturedPokemon)") print("closure 2 — variable evaluated at execution time: (pokemon)") pokemon = Pokemon(name: "Charizard") print("closure 2 - value has been now set to (pokemon)") } } 能猜猜打印结果是什么吗?可能有点难猜,不过这是一个很好的练习,通过自己判断打印结果来测试你是否掌握了今天的课程...
下面是打印结果,你猜对了吗? ?? Initial pokemon is <Pokemon Mew> ? pokemon changed to <Pokemon Mewtwo> ? closure 1 — pokemon captured at creation time: <Pokemon Mew> closure 1 — variable evaluated at execution time: <Pokemon Mewtwo> closure 1 - pokemon has been now set to <Pokemon Pikachu> <Pokemon Mew> escaped! ? closure 2 — pokemon captured at creation time: <Pokemon Mewtwo> closure 2 — variable evaluated at execution time: <Pokemon Pikachu> <Pokemon Pikachu> escaped! closure 2 - value has been now set to <Pokemon Charizard> <Pokemon Mewtwo> escaped! <Pokemon Charizard> escaped! 所以,到底发生了什么?稍微有点复杂,让我给大家来逐步解释一下:
总结是不是感觉有点烧脑?这很正常,闭包捕获语义有时候会比较复杂,尤其类似最后那个例子。我们要记住下面几个关键点:
今天的课程就先学到这里,或许有些难以理解。不要犹豫,打开你的 Playground 尝试测试、修改、运行这些代码,直到你彻底理解了其中的原理。 一旦你理解了以上内容,就可以期待我的下一篇文章了,接下来我会讨论捕获弱变量(weakly)来避免循环引用,以及闭包中的
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |