美文网首页A原理/底层iOS杂谈iOS面试
Block 循环引用,__weak、__block、__stro

Block 循环引用,__weak、__block、__stro

作者: iTruda | 来源:发表于2017-07-19 18:10 被阅读121次

    循环引用的理解

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

    coverImage.jpg

    __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 表中删除,最后清理对象的记录。

    相关文章

      网友评论

      • 砖家就是我:@"字符串"是常量字符串,它的存储区,是内存四个分区中的常量区,而不是栈区,常量区的内存等系统结束时才会释放。而NSString *string 这个string 指针变量是栈区的内容。
        iTruda:@砖家就是我 对对,我写错了,谢谢指正:smile:

      本文标题:Block 循环引用,__weak、__block、__stro

      本文链接:https://www.haomeiwen.com/subject/phlzcxtx.html