[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的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
    void (^__weak block)(void) = ^{
        NSLog(@"step3 -- arrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
    };
    NSLog(@"step2 -- arrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
    block();
    NSLog(@"step4 -- arrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
    NSLog(@"step5 -- arrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
    
    return 0;
}

控制臺打印結果如下:

2019-07-09 16:28:03.143161+0800 BlockDemo[28846:845053] step1 -- arrM的引用計數(shù)為:1
2019-07-09 16:28:03.143340+0800 BlockDemo[28846:845053] step2 -- arrM的引用計數(shù)為:2
2019-07-09 16:28:03.143356+0800 BlockDemo[28846:845053] step3 -- arrM的引用計數(shù)為:2
2019-07-09 16:28:03.143389+0800 BlockDemo[28846:845053] step4 -- arrM的引用計數(shù)為:2
2019-07-09 16:28:03.143420+0800 BlockDemo[28846:845053] step5 -- arrM的引用計數(shù)為:2

我們可以看到,step1到step2過程中arrM對象引用計數(shù)+1,那么為什么會出現(xiàn)這個情況呢?我們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函數(shù)使arrM對象的引用計數(shù)+1。

那么為什么不是__main_block_func_0函數(shù)中第一句NSMutableArray *__strong arrM = __cself->arrM; // bound by copy使arrM對象的引用計數(shù)+1呢?我們稍微修改一下OC代碼,也就是把block();block的調用注釋掉,代碼如下:

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

我們再看一下控制臺的打印情況:

2019-07-20 00:50:32.337811+0800 BlockDemo[3835:360334] step1 -- arrM的引用計數(shù)為:1
2019-07-20 00:50:32.338017+0800 BlockDemo[3835:360334] step2 -- arrM的引用計數(shù)為:2
2019-07-20 00:50:32.338032+0800 BlockDemo[3835:360334] step4 -- arrM的引用計數(shù)為:2
2019-07-20 00:50:32.338042+0800 BlockDemo[3835:360334] step5 -- arrM的引用計數(shù)為:2

em...說明確實是在實例化__main_block_impl_0結構體的時候,強引用的arrM對象。但是為什么NSMutableArray *__strong arrM = __cself->arrM; // bound by copy沒有使arrM引用計數(shù)+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的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
    void (^__weak block)(void) = ^{
        NSLog(@"step3 -- weakArrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
    };
    NSLog(@"step2 -- weakArrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
    block();
    NSLog(@"step4 -- weakArrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
    NSLog(@"step5 -- weakArrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
    
    return 0;
}

控制臺打印結果如下:

2019-07-09 17:13:36.327298+0800 BlockDemo[31615:970330] step1 -- weakArrM的引用計數(shù)為:2
2019-07-09 17:13:36.327448+0800 BlockDemo[31615:970330] step2 -- weakArrM的引用計數(shù)為:2
2019-07-09 17:13:36.327459+0800 BlockDemo[31615:970330] step3 -- weakArrM的引用計數(shù)為:2
2019-07-09 17:13:36.327467+0800 BlockDemo[31615:970330] step4 -- weakArrM的引用計數(shù)為:2
2019-07-09 17:13:36.327475+0800 BlockDemo[31615:970330] step5 -- weakArrM的引用計數(shù)為:2

我們可以看到,step1到step2過程中weakArrM對象引用計數(shù)不變,我們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對象的引用計數(shù)不會+1;

二、堆block截獲對象

接下來我們看一下堆block截獲對象會是什么情況。

2.1 堆block截獲__strong對象

OC代碼如下:

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

控制臺打印如下:

2019-07-09 16:26:17.214917+0800 BlockDemo[28763:838840] step1 -- arrM的引用計數(shù)為:1
2019-07-09 16:26:17.215060+0800 BlockDemo[28763:838840] step2 -- arrM的引用計數(shù)為:3
2019-07-09 16:26:17.215071+0800 BlockDemo[28763:838840] step3 -- arrM的引用計數(shù)為:3
2019-07-09 16:26:17.215080+0800 BlockDemo[28763:838840] step4 -- arrM的引用計數(shù)為:3
2019-07-09 16:26:17.215087+0800 BlockDemo[28763:838840] step5 -- arrM的引用計數(shù)為:3

我們知道了,__strong對象被棧block捕獲時,引用計數(shù)會+1,可是被堆block捕獲時,為什么arrM的引用計數(shù)多加了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函數(shù)
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函數(shù)
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*);兩個函數(shù)指針成員,為了控制捕獲的對象的生命周期。在棧block復制到堆上的時候,對__strong對象引用計數(shù)+1,堆block銷毀的時候,__strong對象引用計數(shù)-1。為了保證捕獲的__strong對象不會在堆block銷毀之前引用計數(shù)變?yōu)?而銷毀。copy函數(shù)指針指向的__main_block_copy_0函數(shù)的內(nèi)部實際上調用了_Block_object_assign函數(shù),_Block_object_assign源碼地址。我們來看一下_Block_object_assign函數(shù)的內(nèi)部實現(xiàn):

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 引用計數(shù)+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是不會對這個對象引用計數(shù)+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內(nèi)調用了_Block_retain_object(object);函數(shù),使arrM引用計數(shù)+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的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
    void (^block)(void) = ^{
        NSLog(@"step3 -- weakArrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
    };
    NSLog(@"step2 -- weakArrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
    block();
    NSLog(@"step4 -- weakArrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
    NSLog(@"step5 -- weakArrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
    
    return 0;
}

控制臺打印如下:

2019-07-09 17:34:54.507969+0800 BlockDemo[32534:1023819] step1 -- weakArrM的引用計數(shù)為:2
2019-07-09 17:34:54.508120+0800 BlockDemo[32534:1023819] step2 -- weakArrM的引用計數(shù)為:2
2019-07-09 17:34:54.508131+0800 BlockDemo[32534:1023819] step3 -- weakArrM的引用計數(shù)為:2
2019-07-09 17:34:54.508139+0800 BlockDemo[32534:1023819] step4 -- weakArrM的引用計數(shù)為:2
2019-07-09 17:34:54.508146+0800 BlockDemo[32534:1023819] step5 -- weakArrM的引用計數(shù)為:2

我們發(fā)現(xiàn)堆block截獲__weak對象的結果和棧block截獲__weak對象的結果一致。原因有兩點:

1、 __main_block_impl_0結構體中的成員__weak typeof(NSMutableArray *) weakArrM;弱引用weakArrM對象,所以在實例化__main_block_impl_0結構體的時候,weakArrM對象的引用計數(shù)不會+1;

2、 堆block拷貝__weak對象時調用objc_copyWeak函數(shù),objc_copyWeak函數(shù)如下:

void objc_copyWeak(id *dst, id *src)
{
    // 根據(jù)scr獲取指向的對象obj,retain obj
    // 函數(shù)取出附有__weak修飾符變量所引用的對象并retain 引用計數(shù)+1
    id obj = objc_loadWeakRetained(src);
    // 調用objc_initWeak 方法
    objc_initWeak(dst, obj); 
    // release obj 引用計數(shù)-1
    objc_release(obj);       
}

該函數(shù)會先調用objc_loadWeakRetained取得對象(同時也導致導致引用計數(shù)+1), 然后調用objc_initWeak,將weakArrM這個變量指向取得的對象,最終調用一次 release,引用計數(shù)-1,一前一后相互抵消,最終引用計數(shù)沒變。


三、總結

我們總結一下,ARC環(huán)境下:

  • 棧block和堆block捕獲__strong對象時,都會先強引用__strong對象,使其引用計數(shù)+1,堆block在從棧復制到堆的過程中又拷貝了一遍__strong對象,為的是延長__strong對象的生命周期。
  • 棧block和堆block捕獲__weak對象時,只是會處理__weak對象的弱引用,并沒有使__weak對象的引用計數(shù)改變(要是能改的話,__weak和__strong豈不是笑話...)。
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容