使用 block 的小技巧和注意事项
2016-03-25 10:47
549 查看
个人比较喜欢使用Block,因为要写个规范的代理就要规定协议方法,觉得略麻烦,所以我很少使用代理;但是使用Block时必须要注意它带来的副作用:
循环引用;
诈尸;
下面解释下 block 引起的循环引用,这个是很好理解的,因为我们应该都是用过代理,都知道把 delegate 描述为 retain(strong)会引起循环引用,block 之所以会引起是因为 block 捕获环境变量引起的, 这一特性有的时候能给编程带来很大的便利性,当然也需要注意带来的副作用,在 block 内部使用到的对象捕获后会被 retain,基本数据类型会进行值拷贝(不是地址拷贝,因此只读哦!),因此如果被捕获的对象持有 block 的话,那么就会造成循环引用!经常发生的场景是一个控制器持有(或者间接持有)(copy)了 block ,并且 block 捕获了该控制器对象 self;
@interface A : NSObject @property (copy, nonatomic) TypeBlock succBlock; @end @interface B : NSObject @property (retain, nonatomic) A a; @end //B.m - (void)testRetainCycle { self.a.succBlock = ^(id obj){ [self handleSuccess:obj]; }; }
如果不是 ARC 编译的话,需要定义一个弱化的指针:
__block B *this = self;
因此改为:
- (void)testRetainCycle { __block B *this = self; self.a.succBlock = ^(id obj){ [this handleSuccess:obj]; }; }
我比较懒,这种写法每次都要知道类名,我感到不爽,我想要一个通用的写法,无意间找到了 typeof ,所以改写下:
__block typeof(self) this = self; [self methodThatTakesABlock:^ { [this handelSomthing]; }
PS:另外也有人使用 __typeof 的,应该和 typeof 没啥大的区别;在 JAVA 里没有 self ,而是使用 this 关键字表示当前,我是之前学的是 JAVA ,因此沿用了 JAVA 的写法,但是如果你的工程使用了 c++的话,最好换个名字,因为 this 是 c++ 的关键字!
在 ARC 编译环境下 __block 就没用了,需要改为 __weak ! 因此 ARC 下可以这样写:
__weak __typeof(self)weakSelf = self;
还有人搞了个 weak strong dance 呢:
__weak __typeof(self)weakSelf = self;
StatusBlock callback = ^(Status status) {
__strong __typeof(weakSelf)self = weakSelf;
self.status = status;
};
这里的技巧是:block内部使用了 __strong 来保证进入block内后 self 不会被释放,并且这里重写定义了 self 就会覆盖掉 block 块捕获之外的 self ,因此在 block 内可继续使用 self,并且不会循环引用!
下面再说下所谓的 诈尸 是什么意思,看下面的场景,我觉得没有场景那是在幻想,毫无意义可言,下面是我在实际编程中遇到的问题,下面简单说下:
//DownloadManger.m - (void)downTheVideo:(NSString *)vid { //鉴定会员权限 __weak __typeof(self)weakSelf = self; [VipStatus isVip:^(Bool vip){ __strong __typeof(weakSelf)self = weakSelf; [self startDownloadTheVideo:vid]; }; }
DownloadManger 类调用了 VipStatus 类的 isVip 方法,该方法使用了 block 回调结果,这里应该没什么问题的,这样写是对的,但是
DownloadManger 和 VipStatus 都是单例类,并且 isVip 方法是需要发送网络请求的,所以这个是异步回调的,DownloadManger 维持了下载的状态机,有可能用户停止了下载之后,这个 block 才被唤醒,这时问题就来了,明明用户已经停止了,但是这个 block 诈尸一样开始运行了,结果导致任务继续下载!
我的解决办法是给 DownloadManger 添加一个记录状态的 int 属性,充分利用 block 捕获的特性,当状态机发生变化就改变这个 int 属性,block 回调的时候就判断捕获的 int 值和当前状态机的是否一致,不一致就不再执行!
//DownloadManger.m - (void)downTheVideo:(NSString *)vid { //鉴定会员权限 int currentStatus = self.currentStatus; __weak __typeof(self)weakSelf = self; [VipStatus isVip:^(Bool vip){ __strong __typeof(weakSelf)self = weakSelf; //检查状态机; if(currentStatus != self.currentStatus) { return;//block 大爷快躺下休息吧! } [self startDownloadTheVideo:vid]; }; } - (void)pauseAllDownloadTask { //改变状态机,防止 block 诈尸! self.currentStatus ++; ... }
关于 block 的坑就先填到这里啦,有问题请指正…
相关文章推荐
- stl中string作为成员变量引起的core问题
- Restful架构学习
- 解析 iOS 动画原理与实现
- ADS简单程序编译分析(2)
- 策略模式(strategy)
- 外观模式
- 解决ssh访问linux虚拟机特别慢
- 匹配符
- 神经网络翻译系统dl4mt源码之Numpy库相关函数的使用(一)
- 字符串匹配的KMP算法
- iOS开发基础 - APP生命周期及启动页面
- java overriding
- CocoaPods详解之----使用篇
- Android系列--DOM、SAX、Pull解析XML
- cent os 服务器 搭建
- markdown 语法
- 微信公众平台开发之会员卡
- 4.1/4.2 多线程进阶篇<上>(Pthread & NSThread)
- 命令ord
- 360导航板式