一个更优雅的 Swift Block 写法

我们先来观察一个循环引用,如果想直接看结论的话可以点击 这里

override func viewDidLoad() {
    super.viewDidLoad()

    button?.didTouchUpInsideBlock = { button in
        let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "NxtViewController") as! NxtViewController
        self.present(vc, animated: true, completion: {
            vc.dismiss(animated: true, completion: nil)
        })
    }
}

这里 button 的点击回调里面会尝试 push 下一个控制器,这里还是很 OK 的,因为下一个控制器没有引用当前的控制器。所以我们给他添加一个引用。

override func viewDidLoad() {
    super.viewDidLoad()
    
    button?.didTouchUpInsideBlock = { button in
        let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "NxtViewController") as! NxtViewController
        vc.previousVC = self
        self.present(vc, animated: true, completion: {
            vc.dismiss(animated: true, completion: nil)
        })
    }
}
        

这里我们打印NxtViewController的 deinit 方法,会发现它已经不执行了,说明这时候已经不会销毁了。

循环引用有什么危害

有的人会说,循环引用会导致内存泄漏,是的没错,用户在不断操作过程中会不断生成NxtViewController但是不会释放,一个粒子是不断的进入下一个控制器,并返回,重复这个操作。这样会使得性能变差,导致很多问题。

当然我觉得循环引用会导致一些奇怪的问题才是最麻烦的,比如对于表格视图来说,循环引用可能会导致进入下一级的时候,下级页面还存有上次操作的数据。太可怕了!

打破循环引用

这个基本上是基础了,就是[weak self],不仅如此,凡是会导致重复引用的都要使用 weak。像是这样:
[weak self, weak someParam],一旦使用 weak 修饰 self,那么下面使用的时候都需要把 self 看成可选值。
一个处理方法是[unowned self] 这个的意思有点像是加了,强制转换都是危险不安全的,非常不建议使用。

那么另外一个解决方法是,加 guard 操作符,比如:

 button?.didTouchUpInsideBlock = { [weak self] _ in
    guard let self = self else { return }
    let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "NxtViewController") as! NxtViewController
    vc.previousVC = self
    self?.present(vc, animated: true, completion: {
        vc.dismiss(animated: true, completion: nil)
    })
}

Swift 4.2之后,官方提供了guard let self = self else { return }写法

一个更优雅的解决方法

上面的操作有两个问题:

  • 不管哪里用,我都要写这一句 [weak self]
  • 很容易忘写,其实这也是因为要写的地方太多

我们可以考虑在声明的时候就处理掉它,给 button 声明一个方法:

func setDidTouchUpInsideBlock<T: AnyObject>(to delegate: T, with block: @escaping (T, ExtendButton) -> ()) {
    self.didTouchUpInsideBlock = {[weak delegate]  button in
        if let delegate = delegate {
            block(delegate, button)
        }
    }
}

//我们就可以设置 button
button.setDidTouchUpInsideBlock(to: self) { (self, button) in
}

Done!

Comments
Write a Comment