[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;
}

控制臺打印結(jié)果如下:

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;
  // 被強(qiáng)引用了
  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結(jié)構(gòu)體中的成員NSMutableArray *__strong arrM;會強(qiáng)引用arrM對象诅岩,在實例化__main_block_impl_0結(jié)構(gòu)體的時候讳苦,會調(diào)用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的調(diào)用注釋掉膝藕,代碼如下:

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結(jié)構(gòu)體的時候,強(qiáng)引用的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;
}

控制臺打印結(jié)果如下:

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結(jié)構(gòu)體中的成員__weak typeof(NSMutableArray *) weakArrM;弱引用weakArrM對象穗慕,所以在實例化__main_block_impl_0結(jié)構(gòu)體的時候饿敲,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結(jié)構(gòu)體
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復(fù)制到堆上的時候,__main_block_desc_0結(jié)構(gòu)體多了void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);void (*dispose)(struct __main_block_impl_0*);兩個函數(shù)指針成員硕并,為了控制捕獲的對象的生命周期法焰。在棧block復(fù)制到堆上的時候,對__strong對象引用計數(shù)+1倔毙,堆block銷毀的時候埃仪,__strong對象引用計數(shù)-1。為了保證捕獲的__strong對象不會在堆block銷毀之前引用計數(shù)變?yōu)?而銷毀陕赃。copy函數(shù)指針指向的__main_block_copy_0函數(shù)的內(nèi)部實際上調(diào)用了_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)調(diào)用了_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對象的結(jié)果和棧block截獲__weak對象的結(jié)果一致桑滩。原因有兩點:

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

2、 堆block拷貝__weak對象時調(diào)用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);
    // 調(diào)用objc_initWeak 方法
    objc_initWeak(dst, obj); 
    // release obj 引用計數(shù)-1
    objc_release(obj);       
}

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


三胰丁、總結(jié)

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

  • 棧block和堆block捕獲__strong對象時,都會先強(qiáng)引用__strong對象锦庸,使其引用計數(shù)+1机蔗,堆block在從棧復(fù)制到堆的過程中又拷貝了一遍__strong對象,為的是延長__strong對象的生命周期甘萧。
  • 棧block和堆block捕獲__weak對象時萝嘁,只是會處理__weak對象的弱引用,并沒有使__weak對象的引用計數(shù)改變(要是能改的話扬卷,__weak和__strong豈不是笑話...)牙言。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市怪得,隨后出現(xiàn)的幾起案子咱枉,更是在濱河造成了極大的恐慌,老刑警劉巖徒恋,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蚕断,死亡現(xiàn)場離奇詭異,居然都是意外死亡因谎,警方通過查閱死者的電腦和手機(jī)基括,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門颜懊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來财岔,“玉大人,你說我怎么就攤上這事河爹〗宠担” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵咸这,是天一觀的道長夷恍。 經(jīng)常有香客問我,道長媳维,這世上最難降的妖魔是什么锰瘸? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任催训,我火速辦了婚禮枫绅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘朋凉。我一直安慰自己,他們只是感情好醋安,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布杂彭。 她就那樣靜靜地躺著,像睡著了一般吓揪。 火紅的嫁衣襯著肌膚如雪亲怠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天柠辞,我揣著相機(jī)與錄音团秽,去河邊找鬼。 笑死钾腺,一個胖子當(dāng)著我的面吹牛徙垫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播放棒,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼姻报,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了间螟?” 一聲冷哼從身側(cè)響起吴旋,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎厢破,沒想到半個月后荣瑟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡摩泪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年笆焰,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片见坑。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡嚷掠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出荞驴,到底是詐尸還是另有隱情不皆,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布熊楼,位于F島的核電站霹娄,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜犬耻,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一踩晶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧枕磁,春花似錦合瓢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至峭咒,卻和暖如春税弃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背凑队。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工则果, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人漩氨。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓西壮,卻偏偏與公主長得像,于是被迫代替她去往敵國和親叫惊。 傳聞我的和親對象是個殘疾皇子款青,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容