Block 循环引用

文章主要内容

  • block的声明
  • block中的循环引用问题

block的声明和赋值

我们使用 ^运算符来声明一个block变量,而且在声明完变量后,要像声明普通变量一样,最后要加 ;

  • 声明 block 语法
    数据返回值类型 (^变量名) (参数列表) = NULL;
int ^(block)(int) = NULL;
block = ^(int m) {
   return m * m;
};

循环引用问题

由于block会持有block中的对象,如果该对象又持有了block,就会造成循环引用。如下

typedef void(^block)();

@property (copy, nonatomic) block myBlock;
@property (copy, nonatomic) NSString *blockString;

- (void)testBlock {
    self.myBlock = ^() {
        //其实注释中的代码,同样会造成循环引用
        NSString *localString = self.blockString;
        //NSString *localString = _blockString;
        //[self doSomething];
    };
}

以上循环引用很容易发现,而发生在多个对象间的时候,往往容易被忽略。如下

//ClassB
@interface ClassB : NSObject
@property (strong, nonatomic) ClassA *objA;
- (void)doSomething;
@end
  
//ClassA
@property (strong, nonatomic) ClassB *objB;
@property (copy, nonatomic) block myBlock;

- (void)testBlockRetainCycle {
    ClassB* objB = [[ClassB alloc] init];
    self.myBlock = ^() {
        [objB doSomething];
    };
    objB.objA = self;
}

一些不会造成循环引用的block

开发过程中,发现一些同学并没有完全理解循环引用,以为只要有block的地方就会要用 __weak 来修饰对象,这样完全没有必要,一下几种block是不会造成循环引用的。

  • 大部分GCD方法

dispatch_async(dispatch_get_main_queue(), ^{
    [self doSomething];
});

因为self 并没有对GCD的block进行持有,所以没有造成循环引用。

  • block不是属性,而是临时变量

- (void)doSomething {
    [self testWithBlock:^{
        [self test];
    }];
}

- (void)testWithBlock:(void(^)())block {
    block();
}

- (void)test {
    NSLog(@"test");
}

block使用对象被提前释放

看下面例子,有这种情况,如果不只是ClassA持有了myBlock,ClassB也持有了myBlock。



当ClassA被someObj对象释放后


此时,ClassA对象已经被释放,而myBlock还是被ClassB持有,没有释放;如果myBlock这个时被调度,而此时ClassA已经被释放,此时访问的ClassA将是一个nil对象(使用__weak修饰,对象释放时会置为nil),而引发错误。

另一个常见错误使用是,开发者担心循环引用错误(如上所述不会出现循环引用的情况),使用__weak。比如

__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
    [weakSelf doSomething];
});

因为将block作为参数传给dispatch_async时,系统会将block拷贝到堆上,而且block会持有block中用到的对象,因为dispatch_async并不知道block中对象会在什么时候被释放,为了确保系统调度执行block中的任务时其对象没有被意外释放掉,dispatch_async必须自己retain一次对象(即self),任务完成后再release对象(即self)。但这里使用__weak,使dispatch_async没有增加self的引用计数,这使得在系统在调度执行block之前,self可能已被销毁,但系统并不知道这个情况,导致block执行时访问已经被释放的self,而达不到预期的结果。

block执行过程中对象被释放

还有一种场景,在block执行开始时self对象还未被释放,而执行过程中,self被释放了,此时访问self时,就会发生错误。

对于这种场景,应该在block中对 对象使用__strong修饰,使得在block期间对 对象持有,block执行结束后,解除其持有。

- (void)testBlockRetainCycle {
    ClassA* objA = [[ClassA alloc] init];
    __weak typeof(objA) weakObjA = objA;
    self.myBlock = ^() {
        __strong typeof(weakObjA) strongWeakObjA = weakObjA;
        [strongWeakObjA doSomething];
    };
    objA.objA = self;
}

注:此方法只能保证在block执行期间对象不被释放,如果对象在block执行执行之前已经被释放了,该方法也无效。

参考链接
https://www.jianshu.com/p/1515fb0c3568
https://blog.devzeng.com/blog/ios-arc-block-retain-cycle.htmlhttps://www.jianshu.com/p/492be28d63c4

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容