SwiftTips程序员iOS Developer

iOS开发之带你畅游闭包Closure --Swift

2017-03-17  本文已影响1305人  Qinz
Qinz
在Swift中引进了闭包Closure的概念,使用起来更加的方便和简洁了,下面让我们揭开它的神秘面纱,带你畅行它执行的详细过程,熟悉理解本文中的每个实例,你就对闭包有了深刻认识了,废话不多说,开始畅游~
一:首先我们创建一个Cat的类,声明一个name的常量和description的变量
class Cat{
    let name:String
    init(name:String) {
        self.name = name
    }
    var description:String{return "<名字 \(name)>"}
    
    deinit {
        print("🐱对象被销毁")
    }
}

1.写一个延迟执行的闭包函数,这里延迟主要是方便我们接下来的观察

 
    func delay(seconds:Int,closure:@escaping ()->()){
        
        print("当前时间为\(Date())")
        
        DispatchQueue(label: "com.ys").asyncAfter(deadline: .now() + .seconds(seconds), execute: {
            
        print("延迟执行所在的线程\(Thread.current)")
            
            print("当前时间为\(Date())")
            
            print("🐱")
            
            closure()
        })
    }

2.我们来调用闭包函数

   func demo1(){
        let pokemon1 = Cat(name: "ys")
        print("-----开始执行demo1的函数-----\(pokemon1)")
        
        delay(seconds: 2, closure: {

            print("-----执行闭包中-------\(pokemon1)")
        })
        print("当前所在的线程\(Thread.current)")

        print("--------demo1的函数执行结束---------------------")
    }

3.我们来看看控制台输出了什么

-----开始执行demo1的函数-----ClosureSamples.Cat
当前时间为2017-03-17 02:10:33 +0000
--------demo1的函数执行结束---------------------
当前时间为2017-03-17 02:10:35 +0000
🐱
-----执行闭包中-------ClosureSamples.Cat

🐱对象被销毁

4.分析如下

5.我们来打印出当前的线程


    func demo1(){
        let pokemon1 = Cat(name: "ys")
        print("-----开始执行demo1的函数-----\(pokemon1)")
        
        delay(seconds: 0, closure: {

            print("-----执行闭包中-------\(pokemon1)")
        })
        print("当前所在的线程\(Thread.current)")

        print("--------demo1的函数执行结束---------------------")
    }
    
    
    func delay(seconds:Int,closure:@escaping ()->()){
        
        print("当前时间为\(Date())")
        
        DispatchQueue(label: "com.ys").asyncAfter(deadline: .now() + .seconds(seconds), execute: {
            
        print("延迟执行所在的线程\(Thread.current)")
            
            print("当前时间为\(Date())")
            
            print("🐱")
            
            closure()
        })
    }

6.控制台输出为

-----开始执行demo1的函数-----ClosureSamples.Cat
当前时间为2017-03-17 02:16:25 +0000
--------demo1的函数执行结束---------------------
延迟执行所在的线程<NSThread: 0x610000066bc0>{number = 3, name = (null)}
当前时间为2017-03-17 02:16:25 +0000
🐱
-----执行闭包中-------ClosureSamples.Cat
🐱对象被销毁

对于上面闭包执行过程的总结:demo1() 函数执行完毕后,闭包才开始执行;并且2秒后当闭包被执行的时候 实例依然存活着。这是因为闭包捕获(强引用)了 变量:编译器发现在闭包内部引用了 变量,它会自动捕获该变量(默认是强引用),所以 的生命周期与闭包自身是一致的。因此,闭包有点像精灵球 ,只要你持有着精灵球闭包, 变量也就会在那里,不过一旦精灵球闭包被释放,引用的 也会被释放。例子中:一旦 GCD 执行完毕,闭包就会被释放,所以 的 deinit 方法也会被调用。值得注意的是 Swift 在闭包执行时才会取出捕获变量的值[^1],所以这里的性能消耗是很小的,我们可以认为它之前捕获的是变量的引用(或指针)。

二:接下来我们在demo1的基础上加上对变量进行赋值的代码,看第二个例子
   func demo2(){
    
        var pokemon2 = Cat(name: "王子🍡")
        print("-----开始执行demo2的函数-----\(pokemon2.name)")
        delay(seconds: 2, closure: {
            
            print("-----执行闭包中-------\(pokemon2.name)")
        
        })
        pokemon2 = Cat(name: "公主👸")
        
        print("--------demo2的函数执行结束---------------------")
   
    }

1.我们看看控制台输出的内容

-----开始执行demo2的函数-----王子🍡
当前时间为2017-03-17 02:22:18 +0000
🐱对象被销毁
--------demo2的函数执行结束---------------------
延迟执行所在的线程<NSThread: 0x618000264ac0>{number = 3, name = (null)}
当前时间为2017-03-17 02:22:20 +0000
🐱
-----执行闭包中-------公主👸
🐱对象被销毁

2.分析

对于二的总结:在创建完闭包之后修改了 pokemon 对象,闭包延迟2秒后执行(虽然此时已经脱离了 demo2() 函数的作用域),我们打印的结果是新的 pokemon 对象,而不是旧的!这是因为 Swift 默认捕获的是变量的引用:首先初始化一个值为 "王子" 的 pokemon 对象,接着修改该对象的值为 "公主",之前值为 "王子" 的对象由于没有其他变量强引用,所以会被释放。接着闭包等待2秒钟执行,打印捕获 "公主" 变量(引用)的内容,待闭包执行完毕“公主”也就被释放了。

三:我们来看一个值类型的闭包捕获过程
  func demo3() {
        var value = 200
        print("-----开始执行demo3的函数-----\(value)")
        
        delay(seconds: 1, closure: {
            
            value = 10000
            print("-----执行闭包中-------\(value)")
        })
        
        value = 230
        print("--------demo3的函数执行结束---------------------")
    }

1.我们来看看控制台输出了什么

-----开始执行demo3的函数-----200
当前时间为2017-03-17 02:24:55 +0000
--------demo3的函数执行结束---------------------
延迟执行所在的线程<NSThread: 0x6100000701c0>{number = 3, name = (null)}
当前时间为2017-03-17 02:24:56 +0000
🐱
-----执行闭包中-------10000

四:我们在demo3的基础上做一点点小小的改动
    func demo4(){
    
        var value = 100
        
        print("-----开始执行demo4的函数-----\(value)")

        delay(seconds: 1, closure: { [oldValue = value] in
          
            
            print("-----执行闭包中-------\(oldValue)")

            })
        value = 800
        
        print("--------demo4的函数执行结束---------------------")
    }
  1. 我们来看看控制台输出了什么:很有趣是不是吗?你会发现最后输出的是100,这就是闭包一个很好的特征,可以拿到之前的值,我们在这里写了[oldValue = value]就可以拿到oldValue之前的值,也就是被赋值为800之前的值,此时的100是旧值,800是新的值。

-----开始执行demo4的函数-----100
当前时间为2017-03-17 02:29:26 +0000
--------demo4的函数执行结束---------------------
延迟执行所在的线程<NSThread: 0x618000074a40>{number = 3, name = (null)}
当前时间为2017-03-17 02:29:27 +0000
🐱
-----执行闭包中-------100

2.如果你想,800到哪里去了呢?没错,它就是此时的value,看打印输出

  
    func demo4(){
    
        var value = 100
        
        print("-----开始执行demo4的函数-----\(value)")

        delay(seconds: 1, closure: { [oldValue = value] in
          
            
            print("-----执行闭包中-------\(oldValue)")
            print("-----执行闭包中-------\(value)")
            
        })
        value = 800
        
        print("--------demo4的函数执行结束---------------------")
    }

-----开始执行demo4的函数-----100
当前时间为2017-03-17 02:30:51 +0000
--------demo4的函数执行结束---------------------
延迟执行所在的线程<NSThread: 0x60800006f0c0>{number = 3, name = (null)}
当前时间为2017-03-17 02:30:52 +0000
🐱
-----执行闭包中-------100
-----执行闭包中-------800

3.有人会想我能不能对oldValue再在闭包里面赋值呢?孩子你想多了---显然它是一个let类型啊!

五:根据以上,我们来看个稍微复杂的示例
    func demo5() {
        var pokemon = Cat(name: "王子 🍡")
        print("-----开始执行demo4的函数-----\(pokemon.name)")
        delay(seconds: 1, closure: { [pokemonCopy = pokemon] in
            
            print("-----执行闭包中-------\(pokemonCopy.name)")
            print("******执行闭包中*******\(pokemon.name)")
            
        })
        pokemon = Cat(name: "公主 👸")
        print("--------demo5的函数执行结束---------------------")
    }

1.我们来看看控制台的输出:经过上面熟悉了四个例子的前提,是不是很容易明白下面的输出结果了呢?如果还不明白,看我下面的分析。有点懵逼的就是为什么”王子 “被赋值为"公主 "之后"王子 "没有被销毁,原因就在于在闭包的参数中我们写了[pokemonCopy = pokemon],这样我们又对旧值有了引用,不会消失了吧?我们看看有这句和没有这句的区别 ,下面是对比:

-----开始执行demo4的函数-----王子 🍡
当前时间为2017-03-17 02:34:41 +0000
--------demo5的函数执行结束---------------------
延迟执行所在的线程<NSThread: 0x60000007a480>{number = 3, name = (null)}
当前时间为2017-03-17 02:34:42 +0000
🐱
-----执行闭包中-------王子 🍡
******执行闭包中*******公主 👸
🐱对象被销毁
🐱对象被销毁

不写[pokemonCopy = pokemon]

    func demo5() {
        var pokemon = Cat(name: "王子 🍡")
        print("-----开始执行demo4的函数-----\(pokemon.name)")
        delay(seconds: 1, closure: {
            
//            print("-----执行闭包中-------\(pokemonCopy.name)")
            print("******执行闭包中*******\(pokemon.name)")
            
        })
        pokemon = Cat(name: "公主 👸")
        print("--------demo5的函数执行结束---------------------")
    }

控制台输出

-----开始执行demo4的函数-----王子 🍡
当前时间为2017-03-17 02:36:07 +0000
🐱对象被销毁
--------demo5的函数执行结束---------------------
延迟执行所在的线程<NSThread: 0x610000260a80>{number = 3, name = (null)}
当前时间为2017-03-17 02:36:08 +0000
🐱
******执行闭包中*******公主 👸
🐱对象被销毁

分析

六:好了,最后来看一个综合例子

1.先想想下面这段代码会怎么输出?

func demo6() {
    var pokemon = Cat(name: "王子 🍡")
    print("----- 初始化 pokemon 为 \(pokemon.name)")
    
    delay(seconds: 2, closure:  { [capturedPokemon = pokemon] in
        print("closure 1 — 旧值: \(capturedPokemon.name)")
        print("closure 1 — 新值: \(pokemon.name)")
        pokemon = Cat(name: "公主 👸")
        print("closure 1 - pokemon的值为 \(pokemon.name)")
    })
    
    pokemon = Cat(name: "爱心 ❤️")
    print("****** pokemon 发生改变为 \(pokemon.name)")
    
    delay(seconds: 2, closure:  { [capturedPokemon = pokemon] in
        print("closure 2 — 旧值: \(capturedPokemon.name)")
        print("closure 2 — 新值: \(pokemon.name)")
        pokemon = Cat(name: "青蛙 🐸")
        print("closure 2 - pokemon的值为 \(pokemon.name)")
    })
}

2.来~我们看看控制台输出结果:估计你看到又懵逼了,不急,我们来慢慢分析为什么是这样输出的,如果你理解了这个,说明闭包就真的已经深入理解了。

----- 初始化 pokemon 为 王子 🍡
当前时间为2017-03-17 02:41:00 +0000
****** pokemon 发生改变为 爱心 ❤️
当前时间为2017-03-17 02:41:00 +0000
延迟执行所在的线程<NSThread: 0x6000002624c0>{number = 4, name = (null)}
延迟执行所在的线程<NSThread: 0x61800007ca00>{number = 3, name = (null)}
当前时间为2017-03-17 02:41:02 +0000
当前时间为2017-03-17 02:41:02 +0000
🐱
closure 2 — 旧值: 爱心 ❤️
🐱
closure 1 — 新值: 爱心 ❤️
closure 1 — 旧值: 王子 🍡
closure 2 - pokemon的值为 青蛙 🐸
closure 1 - 新值: 青蛙 🐸
🐱对象被销毁
🐱对象被销毁
closure 1 - pokemon的值为 公主 👸

🐱对象被销毁
🐱对象被销毁

分析

----- 初始化 pokemon 为 王子 🍡
当前时间为2017-03-17 02:43:39 +0000
****** pokemon 发生改变为 爱心 ❤️
当前时间为2017-03-17 02:43:39 +0000
延迟执行所在的线程<NSThread: 0x608000071a80>{number = 3, name = (null)}
延迟执行所在的线程<NSThread: 0x610000073b80>{number = 4, name = (null)}
当前时间为2017-03-17 02:43:41 +0000
当前时间为2017-03-17 02:43:41 +0000
🐱
🐱
closure 1 — 旧值: 王子 🍡
closure 2 — 旧值: 爱心 ❤️
closure 1 — 新值: 爱心 ❤️
closure 2 — 新值: 爱心 ❤️
closure 1 - pokemon的值为 公主 👸
closure 2 - pokemon的值为 青蛙 🐸
🐱对象被销毁
🐱对象被销毁
🐱对象被销毁

🐱对象被销毁

总结:Swift中的Closure还是很方便和有趣的,了解和熟悉了它执行的过程对于以后的开发非常有利,因为你会发现在OC的工程中我们使用了大量的Block,而Swift把它变得更加优雅了。

我是Qinz,希望我的文章对你有帮助。

上一篇 下一篇

猜你喜欢

热点阅读