和闭包中的 [weak self] 说拜拜

2020-05-21  本文已影响0人  番茄炒西红柿啊

一点题外话

苹果官方的Cocoa API 中大量使用了委托模式.平时开发中最常用的比如tableView, collectionView的代理回调方法.
我们自己封装模块时自然而然的也会经常沿用了这套模式, 以自定的view将内部的textfield内容回调给控制器的情景为例:

// 定义一个协议
protocol TestViewDelegate: class {
    func textInputView(_ view: TestView, didConfirmInput text: String?)
}

class TestView: UIView {
    @IBOutlet weak var inputTextField: UITextField!
    weak var delegate: TestViewDelegate?
    @IBAction func confirmButtonPressed(_ sender: Any) {
        // 委托给代理处理
        self.delegate?.textInputView(self, didConfirmInput: inputTextField.text)
    }
}

class MyViewController : UIViewController {
    var text: String? = ""
    override func viewDidLoad() {
        super.viewDidLoad()
        let testView = TestView.init(frame: CGRect.zero)
        // 设置代理
        testView.delegate = self
    }
}
// 代理回调
extension MyViewController: TestViewDelegate {
    func textInputView(_ view: TestView, didConfirmInput text: String?) {
        self.text = text
    }
}

实现三部曲: 1. 创建协议模板 2.设置代理委托 3.实现回调方法

但是我个人的编程习惯是不太喜欢使用代理委托的方式.因为创建视图设置代理的代码和实现回调的代码是分开的.在维护一段老代码时,我们需要先定位到设置代理的地方,然后点进去看代理遵循的是什么协议, 最后才能定位到代理回调的地方,去查看前任的实现逻辑.

分散的代码,分散的逻辑.碰到一些代码习惯不太友好的前任.维护起来难度比较大.

所以我平常使用block会比委托要多一点

将上述的代码改成block实现:

class TestView: UIView {
    @IBOutlet weak var inputTextField: UITextField!
    var block: ((String?)->Void)?
    @IBAction func confirmButtonPressed(_ sender: Any) {
        self.block?(inputTextField.text)
    }
}
class MyViewController : UIViewController {
    var text: String? = ""
    override func viewDidLoad() {
        super.viewDidLoad()
        let testView = TestView.init(frame: CGRect.zero)
        testView.block = { self.text = $0 }
        self.view.addSubview(testView)
    }
}
// 这样创建视图以及回调的地方都集中在了一起,方便查找和维护.

回到正题

上述代码中有个致命的错误, 就是没有考虑循环引用的问题.所以设置block的代码片段需要修改成:

testView.block = { [weak self] (text) in
    guard let `self` = self else {return}
    self.text = text
 }

都说偷懒是促进人类进步的动力. 每次定义的block都需要手动写上[weak self], 这个Xcode是没有自动补全功能的,实属有些麻烦.而且人难免会犯错,很有可能会出现漏写的情况.

无意中在看喵神博客的时候,发现了前辈是这样处理的:

  1. 定义一个来处理弱引用的类:
class Delegate<Input, Output> {
    typealias blockType = ((Input) -> Output?)?

    // 创建一个私有属性持有block
    private var block: blockType = nil
    
    /// 将需要弱引用的目标对象传递进来,在方法内部处理,并将处理完成后的结果回调出去
    /// - Parameters:
    ///   - target: 需要弱引用的目标对象
    ///   - block: 弱引用处理后的结果回调
    func delegate<T: AnyObject>(on target: T, block: ((T, Input) -> Output)?) {
        self.block = { [weak target] (input) in
            guard let target = target else {return nil}
            return block?(target, input)
        }
    }
    
    /// callAsFunction是固定关键字, 为了可以隐式调用.
    func callAsFunction(_ input: Input) -> Output? {
        return self.block?(input)
    }
}
  1. 在view中写法入下:
class TestView: UIView {
    @IBOutlet weak var inputTextField: UITextField!
    let textDelegate = Delegate<String?, Void>()
    @IBAction func confirmButtonPressed(_ sender: Any) {
        // 隐式调用callAsFunction方法
        textDelegate(inputTextField.text)
    }
}

3 .控制器中的回调代码

let testView = TestView.init(frame: CGRect.zero)
self.view.addSubview(testView)
testView.textDelegate.delegate(on: self) { (self, text) in
    // 这里的已经是弱引用的self
    self.text = text
}

到这里基本就结束了, 通过这种做法就省去了手写 [weak self] 的麻烦.彻底和它说拜拜了.

但是有一点需要注意的是:

testView.textDelegate.delegate(on: self) { ( _ , text) in
    // 将block中回调的第一个参数设置成下划线后
   // 这里的其实是外部的强引用的self, 是会造成内存泄漏的.
    self.text = text
}

参考原文: 链接

上一篇 下一篇

猜你喜欢

热点阅读