__block的實(shí)現(xiàn)

__block的內(nèi)存管理

當(dāng)block使用外部變量時(shí)酥夭,是不能直接在block內(nèi)修改這些變量的。我們用__block修飾變量后就能夠修改了肺素。但需要說明一點(diǎn)__block只能用于auto變量無法修改,__block不能修飾全局變量、靜態(tài)變量窒典。

先看一段代碼:

- (void)blockModifyVariable {
    __block int a = 10;
    __block Person *person = [Person new];
    person.name = @"mm";
    BlockDemo block = ^{
        a = 11;
        person = [Person new];
        person.name = @"modified";
        NSLog(@"%d---%@",a,person.name);//打印結(jié)果:11---modified
    };
    block();
}

下面是block通過clang轉(zhuǎn)換成C++的代碼 :

struct __ViewController__blockModifyVariable_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__blockModifyVariable_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __Block_byref_person_1 *person; // by ref
  __ViewController__blockModifyVariable_block_impl_0(void *fp, struct __ViewController__blockModifyVariable_block_desc_0 *desc, __Block_byref_a_0 *_a, __Block_byref_person_1 *_person, int flags=0) : a(_a->__forwarding), person(_person->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

加了__block修飾后,block結(jié)構(gòu)體里面也是增加了兩個(gè)成員變量稽莉,不同的是并不是直接捕獲外部的變量瀑志,而是增加了兩個(gè)__Block_byref開頭的對(duì)象。下面看這兩個(gè)對(duì)象:

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __Block_byref_person_1 {
  void *__isa;
__Block_byref_person_1 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 Person *__strong person;
};

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

對(duì)比兩個(gè)結(jié)構(gòu)體污秆,對(duì)象類型的person內(nèi)部多了copy和dispose這兩個(gè)管理內(nèi)存的函數(shù)劈猪。對(duì)象內(nèi)部分別有和外部變量名稱一致的a和person。并且能看出__Block_byref_person_1是強(qiáng)引用著person的良拼。__Block_byref_person_1是否強(qiáng)引用person要取決于指向外部的person變量是用什么修飾符修飾战得,如果是用weak或者_(dá)_unsafe_unretained,那這里就是弱引用。

下面是方法blockModifyVariable轉(zhuǎn)換后的代碼:

static void _I_ViewController_blockModifyVariable(ViewController * self, SEL _cmd) {
    
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a ={
        (void*)0,
        (__Block_byref_a_0 *)&a,
        0,
        sizeof(__Block_byref_a_0),
        10
        };
    
    __attribute__((__blocks__(byref))) __Block_byref_person_1 person = {
        (void*)0,
        (__Block_byref_person_1 *)&person,
        33554432,
        sizeof(__Block_byref_person_1),
        __Block_byref_id_object_copy_131,
        __Block_byref_id_object_dispose_131,
        ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("new"))
    };
    
    ((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)(person.__forwarding->person), sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_zx_b2snvmns3v14tgx8jk3ysvrm0000gn_T_ViewController_5067e2_mi_0);
    
    BlockDemo block = ((void (*)())&__ViewController__blockModifyVariable_block_impl_0((void *)__ViewController__blockModifyVariable_block_func_0, &__ViewController__blockModifyVariable_block_desc_0_DATA, (__Block_byref_a_0 *)&a, (__Block_byref_person_1 *)&person, 570425344));
    
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

可以看出用__block修飾后將變量a和person包裝成__Block_byref_a_0和__Block_byref_person_1庸推。block初始化時(shí)將這兩個(gè)對(duì)象賦值給了block內(nèi)部的成員變量a和person常侦。

block的成員變量desc:

static struct __ViewController__blockModifyVariable_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __ViewController__blockModifyVariable_block_impl_0*, struct __ViewController__blockModifyVariable_block_impl_0*);
  void (*dispose)(struct __ViewController__blockModifyVariable_block_impl_0*);
} __ViewController__blockModifyVariable_block_desc_0_DATA =
{ 0,
  sizeof(struct __ViewController__blockModifyVariable_block_impl_0),
  __ViewController__blockModifyVariable_block_copy_0,
  __ViewController__blockModifyVariable_block_dispose_0
};


static void __ViewController__blockModifyVariable_block_copy_0(struct __ViewController__blockModifyVariable_block_impl_0*dst, struct __ViewController__blockModifyVariable_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->person, (void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __ViewController__blockModifyVariable_block_dispose_0(struct __ViewController__blockModifyVariable_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);}

desc中用copy函數(shù)和dispose函數(shù)來管理包裝后的對(duì)象a和person的內(nèi)存浇冰;

下面看看block執(zhí)行代碼塊時(shí)是如何修改和取值的:

static void __ViewController__blockModifyVariable_block_func_0(struct __ViewController__blockModifyVariable_block_impl_0 *__cself) {
    __Block_byref_a_0 *a = __cself->a; // bound by ref  取出block內(nèi)部的成員變量a
    __Block_byref_person_1 *person = __cself->person; // bound by ref 取出block內(nèi)部的成員變量person

    (a->__forwarding->a) = 11; // __forwarding指針:當(dāng)block在棧上,__forwarding指向棧上block的成員變量a聋亡,當(dāng)block被拷貝到堆上__forwarding指向拷貝到堆上的block的成員變量a肘习,保證block內(nèi)部一定是訪問到堆上的變量。這一步就是通過__forwarding指針找到變量a對(duì)其修改坡倔。
    
    // 通過__forwarding指針找到變量person對(duì)其修改漂佩。
    (person->__forwarding->person) = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("new"));
    
    ((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)(person->__forwarding->person), sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_zx_b2snvmns3v14tgx8jk3ysvrm0000gn_T_ViewController_5067e2_mi_1);
    
    // 取值時(shí)也是通過__forwarding指針找到變量a、person罪塔。
     NSLog((NSString *)&__NSConstantStringImpl__var_folders_zx_b2snvmns3v14tgx8jk3ysvrm0000gn_T_ViewController_5067e2_mi_2,(a->__forwarding->a),((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)(person->__forwarding->person), sel_registerName("name")));
}

代碼中關(guān)于__forwarding指針指向的說明可參看下圖:

forwarding.png

對(duì)以上做個(gè)總結(jié):用__block修飾auto變量時(shí)投蝉,編譯器會(huì)將__block變量包裝成對(duì)象,對(duì)象包含該auto變量且有個(gè)__forwarding指針指向包裝后的對(duì)象征堪,而block內(nèi)部會(huì)持有這個(gè)對(duì)象瘩缆;在block內(nèi)部訪問auto變量實(shí)際上是通過取得block內(nèi)部持有的包裝后的對(duì)象,然后通過這個(gè)對(duì)象中的__forwarding指針找到包裝后的對(duì)象或包裝后被復(fù)制到堆上的對(duì)象请契,最后取出對(duì)象中包含的變量進(jìn)行取值或修改咳榜;這就是為什么用__block修飾之后可以修改變量的原因。

__block的內(nèi)存管理

當(dāng)block在棧上時(shí)爽锥,不會(huì)對(duì)__block變量產(chǎn)生強(qiáng)引用涌韩。當(dāng)block被拷貝到堆上時(shí)會(huì)調(diào)用block內(nèi)部的copy函數(shù),copy函數(shù)調(diào)用內(nèi)部的_Block_object_assign函數(shù)根據(jù)所指向?qū)ο蟮男揎椃麑?duì)__block變量形成強(qiáng)引用(retain)或弱引用(注意:ARC會(huì)retain氯夷,MRC時(shí)不會(huì)retain)臣樱;block被拷貝到堆上后,其內(nèi)部用到的成員也都會(huì)被拷貝到堆上腮考。當(dāng)兩個(gè)棧上的block內(nèi)部訪問同一個(gè)block變量雇毫,兩個(gè)block被拷貝到堆上后,堆上只有會(huì)有一份__block變量的拷貝踩蔚,這兩個(gè)block仍同時(shí)持有這個(gè)變量棚放;參考下圖:

__block_copy.png

通過代碼也驗(yàn)證下:

- (void)modifyVariable {
    __block int a = 10;
    
    __block Person *person = [Person new];
    person.name = @"mm";
    
    BlockDemo block = ^{
        a = 11;
        NSLog(@"%d---%@",a,person.name);
    };
    BlockDemo block1 = ^{
        a = 12;
        NSLog(@"%d",a);
    };
    struct __ViewController__modifyVariable_block_impl_0* blockImpl = (__bridge struct __ViewController__modifyVariable_block_impl_0*)block;
    struct __ViewController__modifyVariable_block_impl_0* blockImpl1 = (__bridge struct __ViewController__modifyVariable_block_impl_0*)block1;
    NSLog(@"%p -- %p",blockImpl->a,blockImpl1->a);// 打印結(jié)果:0x600000438600 -- 0x600000438600 
    block();
    block1();
}

// 以下是block轉(zhuǎn)換成c++后的代碼,我們通過
struct __ViewController__modifyVariable_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(void);
    void (*dispose)(void);
};

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

struct __Block_byref_a_0 {
    void *__isa;
    struct __Block_byref_a_0 *__forwarding;
    int __flags;
    int __size;
    int a;
};

struct __ViewController__modifyVariable_block_impl_0 {
    struct __block_impl impl;
    struct __ViewController__modifyVariable_block_desc_0* Desc;
    struct __Block_byref_a_0 *a; // by ref
    
};

__ViewController__modifyVariable_block_impl_0是block的底層實(shí)現(xiàn),我們通過__ViewController__modifyVariable_block_impl_0來訪問block內(nèi)部持有的__block變量a馅闽,代碼中可以看到這兩個(gè)block內(nèi)部持有的a的內(nèi)存地址相同的飘蚯。

當(dāng)block從堆中移除時(shí),會(huì)調(diào)用block內(nèi)部的dispose函數(shù)福也,dispose函數(shù)內(nèi)部調(diào)用_Block_object_dispose函數(shù)局骤,_Block_object_dispose函數(shù)自動(dòng)釋放_(tái)_block變量。過程如下圖:

dispose.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末暴凑,一起剝皮案震驚了整個(gè)濱河市峦甩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌现喳,老刑警劉巖凯傲,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件犬辰,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡泣洞,警方通過查閱死者的電腦和手機(jī)忧风,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來球凰,“玉大人,你說我怎么就攤上這事腿宰∨凰撸” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵吃度,是天一觀的道長(zhǎng)甩挫。 經(jīng)常有香客問我,道長(zhǎng)椿每,這世上最難降的妖魔是什么伊者? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮间护,結(jié)果婚禮上亦渗,老公的妹妹穿的比我還像新娘。我一直安慰自己汁尺,他們只是感情好法精,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著痴突,像睡著了一般搂蜓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上辽装,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天帮碰,我揣著相機(jī)與錄音,去河邊找鬼拾积。 笑死殉挽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的殷勘。 我是一名探鬼主播此再,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼玲销!你這毒婦竟也來了输拇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤贤斜,失蹤者是張志新(化名)和其女友劉穎策吠,沒想到半個(gè)月后逛裤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡猴抹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年带族,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蟀给。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蝙砌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出跋理,到底是詐尸還是另有隱情择克,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布前普,位于F島的核電站肚邢,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏拭卿。R本人自食惡果不足惜骡湖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望峻厚。 院中可真熱鬧响蕴,春花似錦、人聲如沸目木。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽刽射。三九已至军拟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間誓禁,已是汗流浹背懈息。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留摹恰,地道東北人辫继。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像俗慈,于是被迫代替她去往敵國(guó)和親姑宽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

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