[iOS]Block系列探究五 - 截获对象

上一篇文章我们探究了一下__block变量的存储域。这一篇文章我们研究一下Block是如何截获对象的。

一、栈block截获对象

首先我们看一下栈block截获对象会是什么情况。

1.1 栈block截获__strong对象

OC代码如下:

int main(int argc, const char * argv[]) {
    
    NSMutableArray *arrM = [[NSMutableArray alloc] init];
    NSLog(@"step1 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
    void (^__weak block)(void) = ^{
        NSLog(@"step3 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
    };
    NSLog(@"step2 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
    block();
    NSLog(@"step4 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
    NSLog(@"step5 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
    
    return 0;
}

控制台打印结果如下:

2019-07-09 16:28:03.143161+0800 BlockDemo[28846:845053] step1 -- arrM的引用计数为:1
2019-07-09 16:28:03.143340+0800 BlockDemo[28846:845053] step2 -- arrM的引用计数为:2
2019-07-09 16:28:03.143356+0800 BlockDemo[28846:845053] step3 -- arrM的引用计数为:2
2019-07-09 16:28:03.143389+0800 BlockDemo[28846:845053] step4 -- arrM的引用计数为:2
2019-07-09 16:28:03.143420+0800 BlockDemo[28846:845053] step5 -- arrM的引用计数为:2

我们可以看到,step1到step2过程中arrM对象引用计数+1,那么为什么会出现这个情况呢?我们clang一下:


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  // 被强引用了
  NSMutableArray *__strong arrM;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *__strong _arrM, int flags=0) : arrM(_arrM) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    NSMutableArray *__strong arrM = __cself->arrM; // bound by copy

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_qr_j931zwx56pl1pjydym04_8rr0000gn_T_main_4ce6e0_mi_1, ((NSNumber *(*)(Class, SEL, long))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithLong:"), (CFGetRetainCount((__bridgeCFTypeRef)(arrM)))));
}

__main_block_impl_0结构体中的成员NSMutableArray *__strong arrM;会强引用arrM对象,在实例化__main_block_impl_0结构体的时候,会调用objc_retain函数使arrM对象的引用计数+1。

那么为什么不是__main_block_func_0函数中第一句NSMutableArray *__strong arrM = __cself->arrM; // bound by copy使arrM对象的引用计数+1呢?我们稍微修改一下OC代码,也就是把block();block的调用注释掉,代码如下:

int main(int argc, const char * argv[]) {
    
    NSMutableArray *arrM = [[NSMutableArray alloc] init];
    NSLog(@"step1 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
    void (^__weak block)(void) = ^{
        NSLog(@"step3 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
    };
    NSLog(@"step2 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
    // block();
    NSLog(@"step4 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
    NSLog(@"step5 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
    
    return 0;
}

我们再看一下控制台的打印情况:

2019-07-20 00:50:32.337811+0800 BlockDemo[3835:360334] step1 -- arrM的引用计数为:1
2019-07-20 00:50:32.338017+0800 BlockDemo[3835:360334] step2 -- arrM的引用计数为:2
2019-07-20 00:50:32.338032+0800 BlockDemo[3835:360334] step4 -- arrM的引用计数为:2
2019-07-20 00:50:32.338042+0800 BlockDemo[3835:360334] step5 -- arrM的引用计数为:2

em...说明确实是在实例化__main_block_impl_0结构体的时候,强引用的arrM对象。但是为什么NSMutableArray *__strong arrM = __cself->arrM; // bound by copy没有使arrM引用计数+1呢?留给未来的自己去解决了。

1.2 栈block截获__weak对象

OC代码如下:

int main(int argc, const char * argv[]) {
    
    NSMutableArray *arrM = [[NSMutableArray alloc] init];
    __weak typeof(NSMutableArray *) weakArrM = arrM;
    NSLog(@"step1 -- weakArrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
    void (^__weak block)(void) = ^{
        NSLog(@"step3 -- weakArrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
    };
    NSLog(@"step2 -- weakArrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
    block();
    NSLog(@"step4 -- weakArrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
    NSLog(@"step5 -- weakArrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
    
    return 0;
}

控制台打印结果如下:

2019-07-09 17:13:36.327298+0800 BlockDemo[31615:970330] step1 -- weakArrM的引用计数为:2
2019-07-09 17:13:36.327448+0800 BlockDemo[31615:970330] step2 -- weakArrM的引用计数为:2
2019-07-09 17:13:36.327459+0800 BlockDemo[31615:970330] step3 -- weakArrM的引用计数为:2
2019-07-09 17:13:36.327467+0800 BlockDemo[31615:970330] step4 -- weakArrM的引用计数为:2
2019-07-09 17:13:36.327475+0800 BlockDemo[31615:970330] step5 -- weakArrM的引用计数为:2

我们可以看到,step1到step2过程中weakArrM对象引用计数不变,我们clang一下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  // __weak,弱引用
  __weak typeof(NSMutableArray *) weakArrM;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __weak typeof(NSMutableArray *) _weakArrM, int flags=0) : weakArrM(_weakArrM) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

__main_block_impl_0结构体中的成员__weak typeof(NSMutableArray *) weakArrM;弱引用weakArrM对象,所以在实例化__main_block_impl_0结构体的时候,weakArrM对象的引用计数不会+1;

二、堆block截获对象

接下来我们看一下堆block截获对象会是什么情况。

2.1 堆block截获__strong对象

OC代码如下:

int main(int argc, const char * argv[]) {
    
    NSMutableArray *arrM = [[NSMutableArray alloc] init];
    NSLog(@"step1 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
    void (^block)(void) = ^{
        NSLog(@"step3 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
    };
    NSLog(@"step2 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
    block();
    NSLog(@"step4 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
    NSLog(@"step5 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
    
    return 0;
}

控制台打印如下:

2019-07-09 16:26:17.214917+0800 BlockDemo[28763:838840] step1 -- arrM的引用计数为:1
2019-07-09 16:26:17.215060+0800 BlockDemo[28763:838840] step2 -- arrM的引用计数为:3
2019-07-09 16:26:17.215071+0800 BlockDemo[28763:838840] step3 -- arrM的引用计数为:3
2019-07-09 16:26:17.215080+0800 BlockDemo[28763:838840] step4 -- arrM的引用计数为:3
2019-07-09 16:26:17.215087+0800 BlockDemo[28763:838840] step5 -- arrM的引用计数为:3

我们知道了,__strong对象被栈block捕获时,引用计数会+1,可是被堆block捕获时,为什么arrM的引用计数多加了1呢?我们clang看一下:

// __main_block_desc_0结构体
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

// copy函数
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->arrM, (void*)src->arrM, 3/*BLOCK_FIELD_IS_OBJECT*/);}

// dispose函数
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->arrM, 3/*BLOCK_FIELD_IS_OBJECT*/);}

我们知道,栈block复制到堆上的时候,__main_block_desc_0结构体多了void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);void (*dispose)(struct __main_block_impl_0*);两个函数指针成员,为了控制捕获的对象的生命周期。在栈block复制到堆上的时候,对__strong对象引用计数+1,堆block销毁的时候,__strong对象引用计数-1。为了保证捕获的__strong对象不会在堆block销毁之前引用计数变为0而销毁。copy函数指针指向的__main_block_copy_0函数的内部实际上调用了_Block_object_assign函数,_Block_object_assign源码地址。我们来看一下_Block_object_assign函数的内部实现:

void _Block_object_assign(void *destArg, const void *object, const int flags) {

    const void **dest = (const void **)destArg;
    switch ((flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_OBJECT:
        /*******
        id object = ...;
        [^{ object; } copy];
        ********/
        // object 引用计数+1
        _Block_retain_object(object);  
        // 赋值
        *dest = object;  
        break;

      case BLOCK_FIELD_IS_BLOCK:
        /*******
        void (^object)(void) = ...;
        [^{ object; } copy];
        ********/

        *dest = _Block_copy(object);
        break;

      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        /*******
         // copy the onstack __block container to the heap
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __block ... x;
         __weak __block ... x;
         [^{ x; } copy];
         ********/

        *dest = _Block_byref_copy(object);
        break;

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        /*******
         // copy the actual field held in the __block container
         // Note this is MRC unretained __block only. 
         // ARC retained __block is handled by the copy helper directly.
         __block id object;
         __block void (^object)(void);
         [^{ object; } copy];
         ********/

            /// 在mrc的情况下,你对对象添加__block, block是不会对这个对象引用计数+1
        *dest = object;
        break;

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        /*******
         // copy the actual field held in the __block container
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __weak __block id object;
         __weak __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;

      default:
        break;
    }
}

我们可以看到_Block_object_assign内调用了_Block_retain_object(object);函数,使arrM引用计数+1。

2.2 堆block截获__weak对象

OC代码如下:

int main(int argc, const char * argv[]) {
    
    NSMutableArray *arrM = [[NSMutableArray alloc] init];
    __weak typeof(NSMutableArray *) weakArrM = arrM;
    NSLog(@"step1 -- weakArrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
    void (^block)(void) = ^{
        NSLog(@"step3 -- weakArrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
    };
    NSLog(@"step2 -- weakArrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
    block();
    NSLog(@"step4 -- weakArrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
    NSLog(@"step5 -- weakArrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
    
    return 0;
}

控制台打印如下:

2019-07-09 17:34:54.507969+0800 BlockDemo[32534:1023819] step1 -- weakArrM的引用计数为:2
2019-07-09 17:34:54.508120+0800 BlockDemo[32534:1023819] step2 -- weakArrM的引用计数为:2
2019-07-09 17:34:54.508131+0800 BlockDemo[32534:1023819] step3 -- weakArrM的引用计数为:2
2019-07-09 17:34:54.508139+0800 BlockDemo[32534:1023819] step4 -- weakArrM的引用计数为:2
2019-07-09 17:34:54.508146+0800 BlockDemo[32534:1023819] step5 -- weakArrM的引用计数为:2

我们发现堆block截获__weak对象的结果和栈block截获__weak对象的结果一致。原因有两点:

1、 __main_block_impl_0结构体中的成员__weak typeof(NSMutableArray *) weakArrM;弱引用weakArrM对象,所以在实例化__main_block_impl_0结构体的时候,weakArrM对象的引用计数不会+1;

2、 堆block拷贝__weak对象时调用objc_copyWeak函数,objc_copyWeak函数如下:

void objc_copyWeak(id *dst, id *src)
{
    // 根据scr获取指向的对象obj,retain obj
    // 函数取出附有__weak修饰符变量所引用的对象并retain 引用计数+1
    id obj = objc_loadWeakRetained(src);
    // 调用objc_initWeak 方法
    objc_initWeak(dst, obj); 
    // release obj 引用计数-1
    objc_release(obj);       
}

该函数会先调用objc_loadWeakRetained取得对象(同时也导致导致引用计数+1), 然后调用objc_initWeak,将weakArrM这个变量指向取得的对象,最终调用一次 release,引用计数-1,一前一后相互抵消,最终引用计数没变。


三、总结

我们总结一下,ARC环境下:

  • 栈block和堆block捕获__strong对象时,都会先强引用__strong对象,使其引用计数+1,堆block在从栈复制到堆的过程中又拷贝了一遍__strong对象,为的是延长__strong对象的生命周期。
  • 栈block和堆block捕获__weak对象时,只是会处理__weak对象的弱引用,并没有使__weak对象的引用计数改变(要是能改的话,__weak和__strong岂不是笑话...)。

你可能感兴趣的:([iOS]Block系列探究五 - 截获对象)