《Objective-C高级编程 iOS与OS X多线程与内存管理》16

Blocks篇:7.循环引用问题

1.ARC下的Block循环引用

1.1 使用__weak或__unsafe_unretained避免循环引用

一般来说,最简单的这种情况即:block对象作为OC对象的成员,而在block函数体内部直接或间接捕获了该OC对象,造成互相强引用。举例来说:

typedef void (^MyBlock)();

@interface TestObject : NSObject {
    // block成员
    MyBlock myBlock;
}

@end

@implementation TestObject

- (instancetype)init {
    if (self = [super init]) {
        // 初始化block成员
        myBlock = ^{
            NSLog(@"%@", self);
        };
    }
    return self;
}

@end

产生循环引用的过程:

  • 由于myBlock对象捕获了OC对象(self),myBlock被自动拷贝到堆上,并自动对self产生强引用;
  • myBlock是self的成员,当myBlock分配到堆内存后,self负责对其进行内存管理,自动对myBlock对象产生强引用;
  • 互相保留,引用循环产生

不过这种情况,编译器可以直接识别并给出引用循环警告。我们可以通过所有权修饰符__weak或__unsafe_unretained,使myBlock对象通过保留弱引用或不安全的对象指针,来避免对self产生强引用:

- (instancetype)init {
    if (self = [super init]) {
        // 初始化block成员
        
        __weak TestObject *weakSelf = self;
//        __unsafe_unretained TestObject *unsafeSelf = self;
        myBlock = ^{
            NSLog(@"%@", weakSelf);
        };
    }
    return self;
}

由于在init方法中不比担心self对象释放的问题,可以安心使用__unsafe_unretained修饰符。

1.2 通过__block变量,并适时将其释放来解除循环引用
@implementation TestObject

- (instancetype)init {
    if (self = [super init]) {
        // 初始化block成员
        
        __block TestObject *blockSelf = self;
        myBlock = ^{
            NSLog(@"blockSelf - %@", blockSelf);
            
            // 释放__block变量,其释放对self的强引用
            blockSelf = nil;
        };
        // 通过执行block对象,在内部解除循环引用
        myBlock();
    }
    return self;
}

- (void)dealloc {
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

由于在ARC下,复制到堆内存中的__block对象会对内部的OC对象进行强引用,故可以让block对象捕获__block对象,并在需要时释放__block对象(一般都是执行block时),以间接解除对self的强引用。

补充:

调用block后,直接将block对象释放也可以解除循环引用。

缺点:

一般来说,必须执行block后,引用循环才可以被解除,容易出现遗漏的情况。

2.非ARC下的Block内存控制

2.1 非ARC下的Block引用循环

由于非ARC环境下,无法使用所有权修饰符,故只能通过__block变量的方式来解决引用循环问题

- (instancetype)init {
    if (self = [super init]) {
        // 初始化block成员
        
        __block TestObject *blockSelf = self;
        myBlock = ^{
            NSLog(@"blockSelf - %@", blockSelf);
        };
    }
    return self;
}

在上述代码中,直接使用__block对象即可,无需释放,即可避免生成引用循环。

原因:

非ARC环境下,__block变量被分配到堆内存时,不会对内部的OC对象进行强引用
因此,在block对象函数内部,不会对OC对象发生强引用,相当于ARC下的__weak和__unsafe_unretained的情况。

2.2 非ARC环境下的Block内存使用规则补充
  • 对不同内存区域的block执行retain和copy操作的结果:
block对象所在的内存区域 执行操作 效果
retain 无效
copy 复制到堆内存
retain 产生强引用
copy 产生强引用
  • 在C语言环境下,可以使用Block_copy()和Block_release()对堆内存的block对象进行内存管理。

猜你喜欢

转载自blog.csdn.net/weixin_33762130/article/details/87165671