Swift 使用闭包(block)

最近接触到个Swift写的项目,馋了很久的Swift终于有机会练手了,同事封装的组件回调使用了delegate,尝试把其改成了block回调。

如何使用闭包

  1. 声明闭包

    // 声明闭包
    typealias SwiftBlock = (String)->()
  2. 赋值闭包

     self.buttonBlock = {(text)->() in
       print(text)
     }
  3. 调用闭包

    @objc func onButton() {
        print("onButton")
        if buttonBlock != nil {
            buttonBlock!("onButton - block")
        }
    }

Swift 闭包的 @escaping 和 @noescape

在OC里,闭包block及其容易造成循环引用,因为调用block不确定传入的block是否被持有,例如下面的代码:

- (void)func_A_WithBlock:(void(^)())block {
    _block = block;
}
- (void)func_B_WithBlock:(void(^)())block {
    block();
}
  1. 第一个方法是将block作为实例变量存入当前的对象。常见的例子是异步的网络请求回调。

  2. 第二个方法是立即调用这个传入来的block。常见的例子是数组的排序。

如果这是一个私有的类,@implementation看不到。那怎么判断这个block是拿来干什么的呢?

答案是无法判断。

第一个方法里的block是被当作实例变量接收了,例如该对象是A。对象A同时也被对象B持有,就成了这样B->A->block,这时block实现里引用了B,那么就变成了经典的B->A->block->B,引用循环。

如果我们不看内部实现,根本无法确切地判断出这个block是被对象A持有的。当然这是比较极端的例子,一般在声明方法时都会注明这个Block是作什么用的,只是在语言上无法防止这种不确定行为而已。

而Swift在闭包上加强了静态检查。它有两个修饰词@escaping和@noescape。这个看代码就能说明。

 func addBlock(_ block: @escaping SwiftBlock) {
     self.buttonBlock_2 = block
 }
    
 func doSomething(_ block: SwiftBlock) {
     block("doSomething")
 }
  1. 第一个方法加了@escaping,以为着“逃脱”,闭包的生命周期可以逃脱方法的作用域,在方法return后不会销毁,这意味着它的调用时机是不确定的,是异步的。一般用于异步网络请求。

  2. 第二个没有修饰词,所以是默认的@noescape,这意味着该闭包不能超出方法的作用域,方法return后闭包就销毁了,所以它是安全的。

下面这种做法是会报错的,因为在方法doSomething返回后,闭包还存在于异步队列里等候调用。

测试代码

import UIKit

// 声明闭包
typealias SwiftBlock = (String)->()

class ViewControllerUIViewController {
    
    public var buttonBlock : SwiftBlock? = nil
    public var buttonBlock_2 : SwiftBlock? = nil
    public var buttonBlock_3 : SwiftBlock? = nil
    
    private let button = UIButton()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        button.frame = CGRect(x: 200,y: 200,width: 100,height: 40)
        button.setTitle("button"for:  UIControl.State.normal)
        button.addTarget(self, action: #selector(onButton), for: .touchUpInside)
        button.backgroundColor = UIColor.red
        
        self.view.addSubview(button)
        
        self.buttonBlock = {(text)->() in
            print(text)
        }
    }
    
    @objc func onButton() {
        print("onButton")
        if buttonBlock != nil {
            buttonBlock!("onButton - block")
        }
    }
    
    func addBlock(_ block: @escaping SwiftBlock) {
        self.buttonBlock_2 = block
    }
    
    func doSomething(_ block: SwiftBlock) {
        block("doSomething")
    }
    
//    func doSomething_2(_ block: SwiftBlock) {
//        DispatchQueue(label: "queue").async {
//            block("doSomething")
//        }
//    }
}

最后

Swift 是真香。