和闭包中的 [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是没有自动补全功能的,实属有些麻烦.而且人难免会犯错,很有可能会出现漏写的情况.
无意中在看喵神博客的时候,发现了前辈是这样处理的:
- 定义一个来处理弱引用的类:
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)
}
}
- 在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
}
参考原文: 链接