作者:黄兢成
链接:https://www.zhihu.com/question/29023547/answer/109570584
来源:知乎
著作权归作者所有,转载请联系作者获得授权。
block 和 delegate 都可以通知外面。block 更轻型,使用更简单,能够直接访问上下文,这样类中不需要存储临时数据,使用 block 的代码通常会在同一个地方,这样读代码也连贯。delegate 更重一些,需要实现接口,它的方法分离开来,很多时候需要存储一些临时数据,另外相关的代码会被分离到各处,没有 block 好读。
应该优先使用 block。而有两个情况可以考虑 delegate。
- 有多个相关方法。假如每个方法都设置一个 block, 这样会更麻烦。而 delegate 让多个方法分成一组,只需要设置一次,就可以多次回调。当多于 3 个方法时就应该优先采用 delegate。
比如一个网络类,假如只有成功和失败两种情况,每个方法可以设计成单独 block。但假如存在多个方法,比如有成功、失败、缓存、https 验证,网络进度等等,这种情况下,delegate 就要比 block 要好。
在 swift 中,利用 enum, 多个方法也可以合并成一个 block 接口。swift 中的枚举根据情况不同,可以关联不同数据类型。而在 objc 就不建议这样做,objc 这种情况下,额外数据需要使用 NSObject 或者 字典进行强转,接口就不够安全。
- 为了避免循环引用,也可以使用 delegate。使用 block 时稍微不注意就形成循环引用,导致对象释放不了。这种循环引用,一旦出现就比较难检查出来。而 delegate 的方法是分离开的,并不会引用上下文,因此会更安全些。
假如写一个库供他人使用,不清楚使用者的水平如何。这时为防止误用,宁愿麻烦一些,笨一些,使用 delegate 来替代 block。
将 block 简单分类,有三种情形。
临时性的,只用在栈当中,不会存储起来。
比如数组的 foreach 遍历,这个遍历用到的 block 是临时的,不会存储起来。需要存储起来,但只会调用一次,或者有一个完成时期。
比如一个 UIView 的动画,动画完成之后,需要使用 block 通知外面,一旦调用 block 之后,这个 block 就可以删掉。需要存储起来,可能会调用多次。
比如按钮的点击事件,假如采用 block 实现,这种 block 就需要长期存储,并且会调用多次。调用之后,block 也不可以删除,可能还有下一次按钮的点击。
对于临时性的,只在栈中使用的 block, 没有循环引用问题,block 会自动释放。而只调用一次的 block,需要看内部的实现,正确的实现应该是 block 调用之后,马上赋值为空,这样 block 也会释放,同样不会循环引用。
而多次调用时,block 需要长期存储,就很容易出现循环引用问题。
Cocoa 中的 API 设计也是这样的,临时性的,只会调用一次的,采用 block。而多次调用的,并不会使用 block。比如按钮事件,就使用 target-action。有些库将按钮事件从 target-action 封装成 block 接口, 反而容易出问题。