美文网首页
14 协议、闭包和懒加载

14 协议、闭包和懒加载

作者: LeeLeCoder | 来源:发表于2018-05-29 00:01 被阅读0次

    1. 协议

    1.1 协议的格式

    • 协议的定义方式与类,结构体,枚举的定义都非常相似
    protocol SomeProtocol {
        // 协议方法
    }
    
    • 遵守协议的格式
    class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
        // 类的内容
        // 实现协议中的方法
    }
    

    1.2 协议的基本使用

    • 定义协议和遵守协议
    // 1.定义协议
    protocol SportProtocol {
        func playBasketball()
        func playFootball()
    }
    
    // 2.遵守协议
    // 注意:默认情况下在swift中所有的协议方法都是必须实现的,如果不实现,则编译器会报错
    class Person : SportProtocol {
        var name : String?
        var age : Int = 0
    
        // 实现协议中的方法
        func playBasketball() {
            print("人在打篮球")
        }
    
        func playFootball() {
            print("人在踢足球")
        }
    }
    
    • 协议之间的继承
    protocol CrazySportProtocol {
        func jumping()
    }
    
    protocol SportProtocol : CrazySportProtocol {
        func playBasketball()
        func playFootball()
    }
    
    

    1.3 代理设计模式

    • 协议继承用于代理设计模式
    protocol BuyTicketProtocol {
        func buyTicket()
    }
    
    class Person {
        // 1.定义协议属性
        var delegate : BuyTicketProtocol
    
        // 2.自定义构造函数
        init (delegate : BuyTicketProtocol) {
            self.delegate = delegate
        }
    
        // 3.行为
        func goToBeijing() {
            delegate.buyTicket()
        }
    }
    
    
    class HuangNiu: BuyTicketProtocol {
        func buyTicket() {
            print("买了一张火车票")
        }
    }
    
    let p = Person(delegate: HuangNiu())
    p.goToBeijing()
    

    1.4 协议中方法的可选

    // 1.定义协议
    @objc
    protocol SportProtocol {
        func playBasketball()
    
        optional func playFootball()
    }
    
    // 2.遵守协议
    class Person : SportProtocol {
        var name : String?
        var age : Int = 0
    
        // 实现协议中的方法
        @objc func playBasketball() {
            print("人在打篮球")
        }
    }
    

    2. 闭包

    2.1 闭包的介绍

    • 闭包和OC中的block非常相似
      • OC中的block是匿名的函数
      • Swift中的闭包是一个特殊的函数
      • block和闭包都经常用于回调
    • 注意:闭包和block一样,第一次使用时可能不习惯它的语法,可以先使用简单的闭包,随着学习的深入,慢慢掌握其灵活的运用方法.

    2.2 闭包的使用

    block的用法回顾

    • 定义网络请求的类
    @interface HttpTool : NSObject
    - (void)loadRequest:(void (^)())callBackBlock;
    @end
    
    @implementation HttpTool
    - (void)loadRequest:(void (^)())callBackBlock
    {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"加载网络数据:%@", [NSThread currentThread]);
    
            dispatch_async(dispatch_get_main_queue(), ^{
                callBackBlock();
            });
        });
    }
    @end
    
    • 进行网络请求,请求到数据后利用block进行回调
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        [self.httpTool loadRequest:^{
            NSLog(@"主线程中,将数据回调.%@", [NSThread currentThread]);
        }];
    }
    
    • block写法总结:
      • 类型:
    返回值(^block的名称)(block的参数)
    
    ^(参数列表) {
       // 执行的代码
    };
    

    使用闭包代替block

    • 定义网络请求的类
    class HttpTool: NSObject {
    
        func loadRequest(callBack : ()->()){
            dispatch_async(dispatch_get_global_queue(0, 0)) { () -> Void in
                print("加载数据", [NSThread.currentThread()])
    
                 dispatch_async(dispatch_get_main_queue(), { () -> Void in
                    callBack()
                 })
            }
        }
    }
    
    • 进行网络请求,请求到数据后利用闭包进行回调
    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
            // 网络请求
            httpTool.loadRequest ({ () -> () in
                print("回到主线程", NSThread.currentThread());
            })
        }
    
    • 闭包写法总结:
      • 技巧:初学者定义闭包类型,直接写()->().再填充参数和返回值
    类型:(形参列表)->(返回值)
        
    {
        (形参) -> 返回值类型 in
        // 执行代码
    }
    

    闭包的简写

    • 如果闭包没有参数,没有返回值.in和in之前的内容可以省略
     httpTool.loadRequest({
         print("回到主线程", NSThread.currentThread());
     })
    
    • 尾随闭包写法:
      • 如果闭包是函数的最后一个参数,则可以将闭包写在()后面
      • 如果函数只有一个参数,并且这个参数是闭包,那么()可以不写
    httpTool.loadRequest() {
        print("回到主线程", NSThread.currentThread());
    }
    
    // 开发中建议该写法
    httpTool.loadRequest {
        print("回到主线程", NSThread.currentThread());
    }
    

    2.3 闭包的循环引用

    • 如果在HttpTool中有对闭包进行强引用,则会形成循环引用
    • 补充:在Swift中检测一个对象是否销毁,可以实现对象的deinit函数
     // 析构函数(相当于OC中dealloc方法)
     deinit {
         print("ViewController----deinit")
     }
    
    • 循环引用的(实现)
      • 该实现是为了产生循环引用
    class HttpTool: NSObject {
    
        // 定义属性,来强引用传入的闭包
        var callBack : (()->())?
    
        func loadRequest(callBack : ()->()){
            dispatch_async(dispatch_get_global_queue(0, 0)) { () -> Void in
                print("加载数据", [NSThread.currentThread()])
    
                 dispatch_async(dispatch_get_main_queue(), { () -> Void in
                    callBack()
                 })
            }
    
            self.callBack = callBack
        }
    }
    
    • swift中解决循环引用的方式
    • 方案一:
      • 使用weak,对当前控制器使用弱引用
      • 但是因为self可能有值也可能没有值,因此weakSelf是一个可选类型,在真正使用时可以对其强制解包(该处强制解包没有问题,因为控制器一定存在,否则无法调用所在函数)
    // 解决方案一:
    weak var weakSelf = self
    httpTool.loadData {
        print("加载数据完成,更新界面:", NSThread.currentThread())
        weakSelf!.view.backgroundColor = UIColor.redColor()
    }
    
    • 方案二:
      • 和方案一类型,只是书写方式更加简单
      • 可以写在闭包中,并且在闭包中用到的self都是弱引用
    httpTool.loadData {[weak self] () -> () in
         print("加载数据完成,更新界面:", NSThread.currentThread())
         self!.view.backgroundColor = UIColor.redColor()
    }
    
    • 方案三:(常用)
      • 使用关键字unowned
      • 从行为上来说 unowned 更像OC中的 unsafe_unretained
      • unowned 表示:即使它原来引用的对象被释放了,仍然会保持对被已经释放了的对象的一个 "无效的" 引用,它不能是 Optional 值,也不会被指向 nil
    httpTool.loadData {[unowned self] () -> () in
        print("加载数据完成,更新界面:", NSThread.currentThread())
        self.view.backgroundColor = UIColor.redColor()
    }
    

    3. 懒加载

    3.1 懒加载的介绍

    • swift中也有懒加载的方式
      • (苹果的设计思想:希望所有的对象在使用时才真正加载到内存中)
    • 和OC不同的是swift有专门的关键字来实现懒加载
    • lazy关键字可以用于定义某一个属性懒加载

    3.2 懒加载的使用

    • 格式
    lazy var 变量: 类型 = { 创建变量代码 }()
    
    • 懒加载的使用
    // 懒加载的本质是,在第一次使用的时候执行闭包,将闭包的返回值赋值给属性
    // lazy的作用是只会赋值一次
    lazy var array : [String] = {
        () -> [String] in
        return ["why", "lmj", "lnj"]
    }()
    

    4. 非逃逸闭包与逃逸闭包理解(难点)

    从Swift3开始,传递闭包到函数中的时候,系统默认为非逃逸闭包(Nonescaping Closures),有非逃逸闭包必然就有逃逸闭包(Escaping Closures),下面分析一下两者的区别。

    4.1 非逃逸闭包

    非逃逸闭包的生命周期比较简单:

    1、把闭包作为参数传递给函数。
    2、函数中运行该闭包。
    3、退出函数。

    非逃逸闭包

    非逃逸闭包被限制在函数内,当函数退出的时候,该闭包引用计数不会增加,也就是说其引用计数在进入函数和退出函数时保持不变。

    而对于非逃逸型闭包,由于其生命周期确定短于相关函数,编译器可以据此做性能优化。

    class ClassA {
      // 接受非逃逸闭包作为参数
      func someMethod(closure: () -> Void) {
        // 想干什么?
      }
    }
    
    class ClassB {
      let classA = ClassA()
      var someProperty = "Hello"
    
      func testClosure() {
        classA.someMethod {
          // self 被捕获
          someProperty = "闭包内..."
        }
      }
    }
    

    当传递闭包参数给函数someMethod时,要注意ClassB中的属性someProperty,虽然闭包会捕获self,但是由于默认闭包参数是非逃逸型,这里可以省略 self, 反正编译器已经知道这里不会有循环引用的潜在风险。

    4.2 逃逸闭包

    逃逸闭包恰恰与非逃逸闭包相反,其生命周期长于相关函数,当函数退出的时候,逃逸闭包的引用仍然被其他对象持有,不会在相关函数结束后释放。

    逃逸闭包
    • 逃逸闭包的定义:一个传入函数的闭包如果在函数执行结束之后才会被调用,那么这个闭包就叫做逃逸闭包。

    下面通过一个例子来理解逃逸闭包:

    // 定义一个存放闭包的全局数组
    var completionHandlers: [() -> Void] = []
    
    // 定义一个接收闭包的函数
    func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) 
    {
        completionHandlers.append(completionHandler)
    }
    
    // 定义另一个接收闭包的函数
    func someFunctionWithNonescapingClosure(closure: () -> Void) {
        closure()
    }
    
    /*
    定义一个类:
    初始化x值为10
    通过调用上面定义的两个函数,使用尾随闭包的方式将实现"对x赋值"这么一个功能的闭包传入
    */
    class SomeClass {
        var x = 10
        func doSomething() {
            someFunctionWithEscapingClosure { self.x = 100 }
            someFunctionWithNonescapingClosure { x = 200 }
        }
    }
    
    // 创建类的对象
    let instance = SomeClass()
    
    /*
    执行doSomething函数
    PS:内部执行someFunctionWithEscapingClosure,someFunctionWithNonescapingClosure,即期望内部会利用两个尾随闭包对x进行赋值
    */
    instance.doSomething()
    print(instance.x)
    // 打印出 "200"
    
    completionHandlers.first?()
    print(instance.x)
    // 打印出 "100"
    

    因为逃逸闭包是在函数执行之后才会执行,所以可以这么理解:

    (1)我创建了一个类的对象instance
    (2)对象中初始化了一个x = 10
    (3)利用对象执行了函数doSomething
    (4)函数内部先调用了全局函数someFunctionWithEscapingClosure该函数传入了尾随闭包{ self.x = 100 },期望修改instance对象中的x值为100(但是此时并没有执行这个包含了赋值语句的闭包)
    (5)函数内部进一步调用全局函数someFunctionWisthNonescapingClosure该函数传入了尾随闭包{ x = 200 },期望修改instance对象中的x值为200(因为此时全局函数someFunctionWithNonescapingClosure内部执行这个包含了赋值语句的闭包,所以x的值由10变为了200)
    (6)输出(显然结果为200)
    (7)查找全局数组completionHandlers,找到里面第一个元素,显然找到的是在someFunctionWithEscapingClosure函数中添加的闭包{ self.x = 100 },此时才通过全局数组的查询找出闭包并执行,于是x此时才被赋值为100(典型的someFunctionWithEscapingClosure函数执行完毕后才执行闭包{ self.x = 100 },于是这个在函数之后最后才执行到的闭包,就是符合定义的逃逸闭包了。

    • 使用逃逸闭包的2个场景:
      (1)异步调用:如果需要调度队列中异步调用闭包, 这个队列会持有闭包的引用,至于什么时候调用闭包,或闭包什么时候运行结束都是不可预知的。
      (2)存储:需要存储闭包作为属性,全局变量或其他类型做稍后使用。

    相关文章

      网友评论

          本文标题:14 协议、闭包和懒加载

          本文链接:https://www.haomeiwen.com/subject/dpwcjxtx.html