防止Block 循环引用,__weak、__block、__strong的使用

循环引用的理解

首先说一下循环引用,为什么没用 __weak 修饰就直接用 self. 属性,有时候不会造成循环引用,有时候会造成循环引用呢。
循环引用是指两个或者多个对象循环持有造成的无法释放(即引用计数减不到0)。
例如:类 Person 有个属性 block, 在 block 实现后, 此时 self 持有 block,如果在 block 中,直接使用 selfblock 将持有 self,造成循环引用, 如果 block 本身不是 self 的属性,则 self 不持有 block,即使在 block 中直接使用 self 也不会造成循环引用,但是为了避免多个对象的循环引用,所以 block 中最好还是用 __weak,防止这种情况出现。代理用 weak 与此同理。

 

__weak、__block、__strong的作用

  • __weak:弱引用变量修饰词,引用计数不会 +1。本身可以避免循环引用的问题的,但是其会导致外部对象释放了之后,Block 内部也访问不到这个对象的问题,我们可以通过在 Block 内部声明一个 __strong 的变量来指向 weakObj,使外部对象既能在 Block 内部保持住,又能避免循环引用的问题。
  • __block:Block内部修改外部变量修饰词,使外部变量可以在 Block 内部进行修改。本身无法避免循环引用的问题,但是我们可以通过在 Block 内部手动把 blockObj 赋值为 nil 的方式来避免循环引用的问题。另外一点就是 __block 修饰的变量在 Block 内外都是唯一的,要注意这个特性可能带来的隐患。
    但是 __block 有一点:这只是限制在ARC环境下。在非ARC下,__block 是可以避免引用循环的。
  • __strong:强引用变量修饰词,引用计数会+1。常用于 Block 内部对 blockObj的引用修饰,如上面👆__weak的说明。

代码示例

示例1(__weak的使用)

- (void)methond_1
{
    NSString *string = @"1";
    __weak NSString *weakStr = string;
    void (^ block)() = ^ {
        // 此处 weakStr 不能被修改,会报红
        //weakStr = @"2";
    };
    
    block();
    NSLog(@"string   = %@   pointer = %p   pointer_content = %p", string, &string, string);
    NSLog(@"weakStr  = %@   pointer = %p   pointer_content = %p", weakStr, &weakStr, weakStr);
    // &string 得到的是变量 string 本身的存储地址,而 number 得到的是存储的内容 @"1" 的地址。
    // log:
    // string   = 1   pointer = 0x7fff587269f8   pointer_content = 0x1075d6ee0
    // weakStr  = 1   pointer = 0x7fff587269f0   pointer_content = 0x1075d6ee0
}

示例2(__block的使用)

- (void)methond_2
{
    // __block:使外部变量可以在 Block 内部进行修改.
    NSNumber *number = @1;
    __block NSNumber *blockNum = number;
    void (^ block)() = ^ {
        blockNum = @2;
    };
    
    block();
    NSLog(@"number   = %@   pointer = %p   pointer_content = %p", number, &number, number);
    NSLog(@"blockNum = %@   pointer = %p   pointer_content = %p", blockNum, &blockNum, blockNum);
    // log:
    // number   = 1   pointer = 0x7fff5e35dad0   pointer_content = 0xb000000000000012
    // blockNum = 2   pointer = 0x618000051008   pointer_content = 0xb000000000000022
    
    // 可见 Block 会拷贝原来对象, __block 修饰的对象可被 Block 内外同时修改.
}

示例3(在堆区的变量与在栈区的变量对比)

- (void)methond_3
{
    // model 变量是在堆区
    BaseModel *model = [[BaseModel alloc] init];
    __weak BaseModel *weakModel = model;
    __weak __block TestVC *blockSelf = self;
    self.blockModel = ^ {
        // 如果 blockSelf 不用 __block 修饰,则在此处不能修改 testString 值,如果不用 __weak 修饰,则会引起循环引
        blockSelf.testString = @"此时 model = nil,model 已被释放,所以 weakModel = nil";
    };
    
    model = nil;
    self.blockModel();
    NSLog(@"model       = %@   pointer = %p   pointer_content = %p", model, &model, model);
    NSLog(@"weakMoedl   = %@   pointer = %p   pointer_content = %p", weakModel, &weakModel, weakModel);
    // log:
    // model       = (null)   pointer = 0x7fff595a9ad0   pointer_content = 0x0
    // weakMoedl   = (null)   pointer = 0x7fff595a9ac8   pointer_content = 0x0
    
    // number 变量是在栈区, 值@1是在常量区
    NSNumber *number = @1;
    __weak NSNumber *blockNum = number;
    
    number = nil;
    NSLog(@"number   = %@   pointer = %p   pointer_content = %p", number, &number, number);
    NSLog(@"blockNum = %@   pointer = %p   pointer_content = %p", blockNum, &blockNum, blockNum);
    // log:
    // number   = (null)   pointer = 0x7fff5a31cad0   pointer_content = 0x0
    // blockNum = 1        pointer = 0x7fff5a31cac8   pointer_content = 0xb000000000000012
    
    // string 变量是在栈区,值@"string"是在常量区
    NSString *string = @"string";
    __weak NSString *weakString = string;

    string = nil;
    NSLog(@"string     = %@   pointer = %p   pointer_content = %p", string, &string, string);
    NSLog(@"weakString = %@   pointer = %p   pointer_content = %p", weakString, &weakString, weakString);
    // log:
    // string     = (null)   pointer = 0x7fff5f627ad0   pointer_content = 0x0
    // weakString = string   pointer = 0x7fff5f627ac8   pointer_content = 0x1006d4e00
    
    // 字符串常量是存在常量区的,栈内存并不会动态释放,而是当当前线程执行完毕后,释放当前线程的栈内存。所有的常量都存在常量区,
    // 所以上面的例子中即使使用__ weak 修饰, 但是 @1 和 @"string" 这2个常量并没有被释放, 所以 weak 的地址指向依然存在值.
}

示例4(__weak与__block作用的对比)

- (void)methond_4
{
    BaseModel *model = [[BaseModel alloc] init];
    __weak BaseModel *weakModel = model;
    void (^ block)() = ^ {
        // weakModel 弱引用, 此时 model = nil ,所以 strongModel = weakModel = nil
        __strong BaseModel *strongModel = weakModel;
        NSLog(@"strongModel  = %@   pointer = %p   pointer_content = %p", strongModel, &strongModel, strongModel);
    };
    
    model = nil;
    block();
    NSLog(@"model        = %@   pointer = %p   pointer_content = %p", model, &model, model);
    NSLog(@"weakMoedl    = %@   pointer = %p   pointer_content = %p", weakModel, &weakModel, weakModel);
    // 在 model 置为 nil 之前, block 的 __ strong 并没有执行, 所以当时 model 对象被当前的区块持有, 当 model 置为 nil 时, 该对象已经被释放, 所以 __strong 的时候, weakModel 地址的内存已经被释放, strongModel 指向 nil, 所以 model 对象引用计数并没有加 1.
    // log:
    // strongModel  = (null)   pointer = 0x7fff5e6669a8   pointer_content = 0x0
    // model        = (null)   pointer = 0x7fff5e666ad0   pointer_content = 0x0
    // weakMoedl    = (null)   pointer = 0x7fff5e666ac8   pointer_content = 0x0
    
    BaseModel *model_2 = [[BaseModel alloc] init];
    __block BaseModel *blockModel = model_2;
    void (^ blockModel_2)() = ^ {
        // weakModel 只是被 __block 修饰,并不是弱引用,所以 model = nil 并不影响 weakModel 的值,所以 strongModel = weakModel != nil
        __strong BaseModel *strongModel = blockModel;
        NSLog(@"strongModel  = %@   pointer = %p   pointer_content = %p", strongModel, &strongModel, strongModel);
    };
    
    model = nil;
    blockModel_2();
    NSLog(@"model        = %@   pointer = %p   pointer_content = %p", model_2, &model_2, model_2);
    NSLog(@"blockModel   = %@   pointer = %p   pointer_content = %p", blockModel, &blockModel, blockModel);
    // log:
    // strongModel  = <BaseModel: 0x60800001b590>   pointer = 0x7fff558af9e8   pointer_content = 0x60800001b590
    // model        = (null)                        pointer = 0x7fff558afad0   pointer_content = 0x0
    // weakMoedl    = <BaseModel: 0x60800001b590>   pointer = 0x7fff558afac8   pointer_content = 0x60800001b590
}

示例5(__weak和__strong的使用)

- (void)methond_5
{
    BaseModel *model = [[BaseModel alloc] init];
    __weak BaseModel *weakModel = model;
    void (^ block)() = ^ {
        __strong BaseModel *strongModel = weakModel;
        NSLog(@"虽然此时 model = nil, weakModel 也被 __weak 修饰,但是在下面👇线程中 weakModel 被 threadStrong 强引用,weakModel 的引用计数 +1 ,当 model = nil 时,weakModel 也不会被释放,所以此时 strongModel = weakModel != nil");
        NSLog(@"strongModel  = %@   pointer = %p   pointer_content = %p", strongModel, &strongModel, strongModel);
    };
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        __strong BaseModel *threadStrong = weakModel;
        NSLog(@"weakModel 在线程中被强引用,引用计数+1");
        sleep(5);
        NSLog(@"threadStrong = %@   pointer = %p   pointer_content = %p", threadStrong, &threadStrong, threadStrong);
    });
    
    sleep(1);
    // 此时 weakModel 在线程中已被强引用,引用计数 +1,model = nil 并不能使得 weakModel 也等于 nil
    model = nil;
    
    block();
    NSLog(@"model        = %@   pointer = %p   pointer_content = %p", model, &model, model);
    NSLog(@"weakMoedl    = %@   pointer = %p   pointer_content = %p", weakModel, &weakModel, weakModel);
    // weakModel 在线程中被强引用,引用计数+1
    // 虽然此时 model = nil, weakModel 也被 __weak 修饰,但是在下面👇线程中 weakModel 被 threadStrong 强引用,weakModel 的引用计数 +1 ,当 model = nil 时,weakModel 也不会被释放,所以此时 strongModel = weakModel != nil
    // log:
    // strongModel  = <BaseModel: 0x60000000ad90>   pointer = 0x7fff50507958   pointer_content = 0x60000000ad90
    // model        = (null)                        pointer = 0x7fff50507ad0   pointer_content = 0x0
    // weakMoedl    = <BaseModel: 0x60000000ad90>   pointer = 0x7fff50507ac8   pointer_content = 0x60000000ad90
    // threadStrong = <BaseModel: 0x60000000ad90>   pointer = 0x7000041c4d88   pointer_content = 0x60000000ad90
}

示例6(__weak和__strong的使用 -> block 内修改全局变量)

@interface TestVC ()
{
    NSString *testVar;
}
@property (nonatomic, strong) NSString *testString;
@property (nonatomic, copy) void (^ blockModel)();

@end

@implementation TestVC

- (void)methond_6
{
    // 正确使用
    __weak __block TestVC *weakSelf = self;
    self.blockModel = ^{
        __strong TestVC *strongSelf = weakSelf;
        strongSelf.testString = @"testString";
        strongSelf -> testVar = @"可以修改全局变量,并且不会导致 block 无法释放";
        //当然也可以直接把这个全局变量改为属性声明,直接用 weakSelf. 或 strongSelf. 就行
    };
    //blockModel 被 self 持有,所以在 block 内部必须使用 __weak 修饰的 weakSelf,又因为要修改全局变量 testVar 使用 "->", 所以又使用 __strong 修饰的 strongSelf
    
    // 编译不通过
    self.blockModel = ^{
        weakSelf.testString = @"testString";
        //被 __weak 修饰过的 weakSelf 不能使用 "->"
        //weakSelf -> testVar = @"这样写编译不通过,直接报红";
    };
    //报红:"dereferencing a __weak pointer is not allowed die to possible null value caused by a race condition, assign it to strong variable first"
    
    // 无法修改全局变量 testVar(原理同示例3 👆)
    __weak __block NSString *weakVar = testVar;
    self.blockModel = ^{
        weakSelf.testString = @"testString";
        weakVar = @"无法修改全局变量 testVar";
    };
    
    self.blockModel = ^{
        weakSelf.testString = @"testString";
        self -> testVar = @"可以修改全局变量 testVar,但会引起 block 无法被释放,导致内存泄漏";
    };
}

@end

总结

  1. 当在 block 内部修改外部局部变量时,需要用 __block 修饰;
    e.g.:
    NSNumber *number = @1;
    __block NSNumber *blockNum = number;
    void (^ block)() = ^ {
        blockNum = @2;
    };
  1. 当 block 被 self 持有,并且不对 self 做修改,如 self = nil;(对 self 的属性修改不算是对 self 的修改),或者是不对 self 的全局变量做修改(因为会用到 "->"),只需要用 __weak 修饰即可;
    e.g.:
    __weak TestVC *weakSelf = self;
    self.blockModel = ^{
        weakSelf.testString = @"testString";
        [weakSelf testMethod];
    };
  1. 当 block 被 self 持有,并且对 self 做修改,如 self = nil;,则需要用 __weak__block 修饰;
    e.g.:
    __weak __block TestVC *weakSelf = self;
    self.blockModel = ^{
        weakSelf.testString = @"testString";
        [weakSelf testMethod];
        weakSelf = nil;
    };
  1. 当 block 和 self 相互持有时,或者 block 内需要修改 self 的全局变量时,则 block 外部需要用 __weak 修饰,block 内部需要使用 __strong 修饰的变量(为了安全起见,block 内部最好还是使用 __strong 修饰的变量吧,不明白的请看上面👆示例5(__weak和__strong的使用));
    e.g.:
    __weak TestVC *weakSelf = self;
    self.blockModel = ^{
        __strong TestVC *strongSelf = weakSelf;
        strongSelf.testString = @"testString";
        strongSelf -> testVar = @"可以修改全局变量,并且不会导致 block 无法释放";
    };
  1. 当 block 和 self 相互持有时,并且有修改 self ,则外部需要用 __weak__block 修饰,block 内部需要使用 __strong 修饰的变量;
    e.g.:
    __weak __block  TestVC *weakSelf = self;
    self.blockModel = ^{
        __strong TestVC *strongSelf = weakSelf;
        strongSelf.testString = @"testString";
        strongSelf -> testVar = @"可以修改全局变量,并且不会导致 block 无法释放";
        strongSelf = nil;
    };

以上5种情况基本说明了各个修饰词的使用场景,如果把握不来的,或者不理解的,为了安全起见直接按地种情况去写,老铁,没毛病。反正记住 以下几点:

  • __weak 是防止循环引用的;
  • __block 是在 block 内部可以修改外部变量的 (在非ARC环境下也可以防止循环引用);
  • __strong 是在内部防止外部的 weak 变量被提前释放,在内部无法获取 weak 变量;

以上示例代码基本可以说明__weak__block__strong的使用规则了,如果还有哪些不清楚的,没有在示例代码中展现出来,建议自己动手写写看。如有对内存分配不太理解的小伙伴可以看看这篇文章《iOS程序中的内存分配(栈区和堆区的对比)》

补充(weak的实现原理)

weak 变量在引用计数为0时,会被自动设置成 nil,这个特性是如何实现的?

很少有人知道weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是 weak 指针的地址数组。更多人的人只是知道 weak 是弱引用,所引用对象的计数器不会加一,并在引用对象被释放的时候自动被设置为nil。通常用于解决循环引用问题。但现在单知道这些已经不足以应对面试了,好多公司会问 weak 的原理。weak 的原理是什么呢?具体细节分析请看《iOS 底层解析weak的实现原理(包含weak对象的初始化,引用,释放的分析)》

 

weak 实现原理的概括
Runtime 维护了一个 weak 表,用于存储指向某个对象的所有 weak 指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak 指针的地址(这个地址的值是所指对象的地址)数组。

 

weak 的实现原理可以概括一下三步:
1、初始化时:runtime 会调用 objc_initWeak 函数,初始化一个新的 weak 指针指向对象的地址。
2、添加引用时:objc_initWeak 函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
3、释放时,调用 clearDeallocating 函数。clearDeallocating 函数首先根据对象地址获取所有 weak 指针地址的数组,然后遍历这个数组把其中的数据设为 nil,最后把这个 entryweak 表中删除,最后清理对象的记录。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值