使用 block 的小技巧和注意事项

Posted 芒果味ly

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用 block 的小技巧和注意事项相关的知识,希望对你有一定的参考价值。

个人比较喜欢使用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 = selfself.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 的坑就先填到这里啦,有问题请指正…

以上是关于使用 block 的小技巧和注意事项的主要内容,如果未能解决你的问题,请参考以下文章

两种好用的清除浮动的小技巧(clearfix hack)

整理一些css在使用中的小技巧(进行中)

满满干货!20个Python使用的小技巧

su鉴定故障

3.1 GWAS:表型鉴定与记录的基本原则和原始数据处理

关于多线程编写的小技巧--观传智播客张孝祥老师视频有感