[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岂不是笑话...)。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容