oc中block底層原理分析(二)

一句狼、block對對象變量的捕獲

block一般使用過程中都是對對象變量的捕獲,那么對象變量的捕獲同基本數(shù)據(jù)類型變量相同嗎热某?
查看一下代碼思考:當(dāng)在block中訪問的為對象類型時腻菇,對象什么時候會銷毀?

typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            
            block = ^{
                NSLog(@"------block內(nèi)部%d",person.age);
            };
        } // 執(zhí)行完畢昔馋,person沒有被釋放
        NSLog(@"--------");
    } // person 釋放
    return 0; 
}

大括號執(zhí)行完畢之后筹吐,person依然不會被釋放。上一篇文章提到過秘遏,person為aotu變量丘薛,傳入的block的變量同樣為person,即block有一個強(qiáng)引用引用person邦危,所以block不被銷毀的話洋侨,peroson也不會銷毀。

__weak

__weak添加之后倦蚪,person在作用域執(zhí)行完畢之后就被銷毀了希坚。

typedef void (^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            
            __weak Person *waekPerson = person;
            block = ^{
                NSLog(@"------block內(nèi)部%d",waekPerson.age);
            };
        }
        NSLog(@"--------");
    }
    return 0;
}

__weak修飾的變量,在生成的__main_block_impl_0中也是使用__weak修飾审丘。

__main_block_copy_0 和 __main_block_dispose_0

當(dāng)block中捕獲對象類型的變量時吏够,我們發(fā)現(xiàn)block結(jié)構(gòu)體__main_block_impl_0的描述結(jié)構(gòu)體__main_block_desc_0中多了兩個參數(shù)copy和dispose函數(shù)。
copy和dispose函數(shù)中傳入的都是__main_block_impl_0結(jié)構(gòu)體本身。

copy本質(zhì)就是__main_block_copy_0函數(shù)锅知,__main_block_copy_0函數(shù)內(nèi)部調(diào)用_Block_object_assign函數(shù)播急,_Block_object_assign中傳入的是person對象的地址,person對象售睹,以及8桩警。
dispose本質(zhì)就是__main_block_dispose_0函數(shù),__main_block_dispose_0函數(shù)內(nèi)部調(diào)用_Block_object_dispose函數(shù)昌妹,_Block_object_dispose函數(shù)傳入的參數(shù)是person對象捶枢,以及8。
總結(jié)

1.一旦block中捕獲的變量為對象類型飞崖,block結(jié)構(gòu)體中的__main_block_desc_0會出兩個參數(shù)copy和dispose烂叔。因?yàn)樵L問的是個對象,block希望擁有這個對象固歪,就需要對對象進(jìn)行引用蒜鸡,也就是進(jìn)行內(nèi)存管理的操作。比如說對對象進(jìn)行retarn操作牢裳,因此一旦block捕獲的變量是對象類型就會會自動生成copy和dispose來對內(nèi)部引用的對象進(jìn)行內(nèi)存管理逢防。

2.當(dāng)block內(nèi)部訪問了對象類型的auto變量時,如果block是在棧上蒲讯,block內(nèi)部不會對person產(chǎn)生強(qiáng)引用忘朝。不論block結(jié)構(gòu)體內(nèi)部的變量是__strong修飾還是__weak修飾,都不會對變量產(chǎn)生強(qiáng)引用判帮。

3.如果block被拷貝到堆上局嘁。copy函數(shù)會調(diào)用_Block_object_assign函數(shù),根據(jù)auto變量的修飾符(__strong脊另,__weak导狡,unsafe_unretained)做出相應(yīng)的操作,形成強(qiáng)引用或者弱引用偎痛。

4.如果block從堆中移除旱捧,dispose函數(shù)會調(diào)用_Block_object_dispose函數(shù),自動釋放引用的auto變量踩麦。

二枚赡、block內(nèi)修改變量的值

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        Block block = ^ {
            // age = 20; // 無法修改
            NSLog(@"%d",age);
        };
        block();
    }
    return 0;
}

默認(rèn)情況下block不能修改外部的局部變量。通過之前對源碼的分析可以知道谓谦。

age是在main函數(shù)內(nèi)部聲明的贫橙,說明age的內(nèi)存存在于main函數(shù)的棧空間內(nèi)部反粥,但是block內(nèi)部的代碼在__main_block_func_0函數(shù)內(nèi)部卢肃。__main_block_func_0函數(shù)內(nèi)部無法訪問age變量的內(nèi)存空間疲迂,兩個函數(shù)的棧空間不一樣莫湘,__main_block_func_0內(nèi)部拿到的age是block結(jié)構(gòu)體內(nèi)部的age尤蒿,因此無法在__main_block_func_0函數(shù)內(nèi)部去修改main函數(shù)內(nèi)部的變量。

方式一:age使用static修飾幅垮。
前文提到過static修飾的age變量傳遞到block內(nèi)部的是指針腰池,在__main_block_func_0函數(shù)內(nèi)部就可以拿到age變量的內(nèi)存地址,因此就可以在block內(nèi)部修改age的值忙芒。
方式二:__block
__block用于解決block內(nèi)部不能修改auto變量值的問題示弓,__block不能修飾靜態(tài)變量(static) 和全局變量。

首先被__block修飾的age變量聲明變?yōu)槊麨閍ge的__Block_byref_age_0結(jié)構(gòu)體呵萨,也就是說加上__block修飾的話捕獲到的block內(nèi)的變量為__Block_byref_age_0類型的結(jié)構(gòu)體奏属。
image.png
接著將__Block_byref_age_0結(jié)構(gòu)體age存入__main_block_impl_0結(jié)構(gòu)體中,并賦值給__Block_byref_age_0 *age;
之后調(diào)用block甘桑,首先取出__main_block_impl_0中的age拍皮,通過age結(jié)構(gòu)體拿到__forwarding指針,上面提到過__forwarding中保存的就是__Block_byref_age_0結(jié)構(gòu)體本身跑杭,這里也就是age(__Block_byref_age_0),在通過__forwarding拿到結(jié)構(gòu)體中的age(10)變量并修改其值咆耿。
為什么要通過__forwarding獲取age變量的值德谅?

__forwarding是指向自己的指針。這樣的做法是為了方便內(nèi)存管理萨螺,之后內(nèi)存管理章節(jié)會詳細(xì)解釋窄做。
到此為止,__block為什么能修改變量的值已經(jīng)很清晰了慰技。__block將變量包裝成對象椭盏,然后在把a(bǔ)ge封裝在結(jié)構(gòu)體里面,block內(nèi)部存儲的變量為結(jié)構(gòu)體指針吻商,也就可以通過指針找到內(nèi)存地址進(jìn)而修改變量的值掏颊。

__block修飾對象類型
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block Person *person = [[Person alloc] init];
        NSLog(@"%@",person);
        Block block = ^{
            person = [[Person alloc] init];
            NSLog(@"%@",person);
        };
        block();
    }
    return 0;
}

通過源碼查看,將對象包裝在一個新的結(jié)構(gòu)體中艾帐。結(jié)構(gòu)體內(nèi)部會有一個person對象乌叶,不一樣的地方是結(jié)構(gòu)體內(nèi)部添加了內(nèi)存管理的兩個函數(shù)__Block_byref_id_object_copy和__Block_byref_id_object_dispose


image.png
__block內(nèi)存管理
上文提到當(dāng)block中捕獲對象類型的變量時,block中的__main_block_desc_0結(jié)構(gòu)體內(nèi)部會自動添加copy和dispose函數(shù)對捕獲的變量進(jìn)行內(nèi)存管理柒爸。
那么同樣的當(dāng)block內(nèi)部捕獲__block修飾的對象類型的變量時准浴,__Block_byref_person_0結(jié)構(gòu)體內(nèi)部也會自動添加__Block_byref_id_object_copy和__Block_byref_id_object_dispose對被__block包裝成結(jié)構(gòu)體的對象進(jìn)行內(nèi)存管理。
當(dāng)block內(nèi)存在棧上時捎稚,并不會對__block變量產(chǎn)生內(nèi)存管理乐横。當(dāng)blcok被copy到堆上時
會調(diào)用block內(nèi)部的copy函數(shù)求橄,copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù),_Block_object_assign函數(shù)會對__block變量形成強(qiáng)引用(相當(dāng)于retain)葡公。
__block修飾的變量在block結(jié)構(gòu)體中一直都是強(qiáng)引用谈撒,而其他類型的是由傳入的對象指針類型決定。

一段代碼更深入的觀察一下匾南。

typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int number = 20;
        __block int age = 10;
        
        NSObject *object = [[NSObject alloc] init];
        __weak NSObject *weakObj = object;
        
        Person *p = [[Person alloc] init];
        __block Person *person = p;
        __block __weak Person *weakPerson = p;
        
        Block block = ^ {
            NSLog(@"%d",number); // 局部變量
            NSLog(@"%d",age); // __block修飾的局部變量
            NSLog(@"%p",object); // 對象類型的局部變量
            NSLog(@"%p",weakObj); // __weak修飾的對象類型的局部變量
            NSLog(@"%p",person); // __block修飾的對象類型的局部變量
            NSLog(@"%p",weakPerson); // __block啃匿,__weak修飾的對象類型的局部變量
        };
        block();
    }
    return 0;
}

將上述代碼轉(zhuǎn)化為c++代碼查看不同變量之間的區(qū)別

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
  int number;
  NSObject *__strong object;
  NSObject *__weak weakObj;
  __Block_byref_age_0 *age; // by ref
  __Block_byref_person_1 *person; // by ref
  __Block_byref_weakPerson_2 *weakPerson; // by ref
  
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _number, NSObject *__strong _object, NSObject *__weak _weakObj, __Block_byref_age_0 *_age, __Block_byref_person_1 *_person, __Block_byref_weakPerson_2 *_weakPerson, int flags=0) : number(_number), object(_object), weakObj(_weakObj), age(_age->__forwarding), person(_person->__forwarding), weakPerson(_weakPerson->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
上述__main_block_impl_0結(jié)構(gòu)體中看出,沒有使用__block修飾的變量(object 和 weadObj)則根據(jù)他們本身被block捕獲的指針類型對他們進(jìn)行強(qiáng)引用或弱引用蛆楞,而一旦使用__block修飾的變量溯乒,__main_block_impl_0結(jié)構(gòu)體內(nèi)一律使用強(qiáng)指針引用生成的結(jié)構(gòu)體。
如上面分析的那樣豹爹,__block修飾對象類型的變量生成的結(jié)構(gòu)體內(nèi)部多了__Block_byref_id_object_copy和__Block_byref_id_object_dispose兩個函數(shù)裆悄,用來對對象類型的變量進(jìn)行內(nèi)存管理的操作。而結(jié)構(gòu)體對對象的引用類型臂聋,則取決于block捕獲的對象類型的變量光稼。weakPerson是弱指針,所以__Block_byref_weakPerson_2對weakPerson就是弱引用孩等,person是強(qiáng)指針艾君,所以__Block_byref_person_1對person就是強(qiáng)引用。
__main_block_copy_0函數(shù)中會根據(jù)變量是強(qiáng)弱指針及有沒有被__block修飾做出不同的處理肄方,強(qiáng)指針在block內(nèi)部產(chǎn)生強(qiáng)引用冰垄,弱指針在block內(nèi)部產(chǎn)生弱引用。被__block修飾的變量最后的參數(shù)傳入的是8权她,沒有被__block修飾的變量最后的參數(shù)傳入的是3虹茶。
當(dāng)block從堆中移除時通過dispose函數(shù)來釋放他們。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末隅要,一起剝皮案震驚了整個濱河市蝴罪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌步清,老刑警劉巖要门,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異尼啡,居然都是意外死亡暂衡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進(jìn)店門崖瞭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來狂巢,“玉大人,你說我怎么就攤上這事书聚∵罅欤” “怎么了藻雌?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長斩个。 經(jīng)常有香客問我胯杭,道長,這世上最難降的妖魔是什么受啥? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任做个,我火速辦了婚禮,結(jié)果婚禮上滚局,老公的妹妹穿的比我還像新娘居暖。我一直安慰自己,他們只是感情好藤肢,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布太闺。 她就那樣靜靜地躺著,像睡著了一般嘁圈。 火紅的嫁衣襯著肌膚如雪省骂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天最住,我揣著相機(jī)與錄音钞澳,去河邊找鬼。 笑死温学,一個胖子當(dāng)著我的面吹牛略贮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播仗岖,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼览妖!你這毒婦竟也來了轧拄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤讽膏,失蹤者是張志新(化名)和其女友劉穎檩电,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體府树,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡俐末,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了奄侠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卓箫。...
    茶點(diǎn)故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖垄潮,靈堂內(nèi)的尸體忽然破棺而出烹卒,到底是詐尸還是另有隱情闷盔,我是刑警寧澤,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布旅急,位于F島的核電站逢勾,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏藐吮。R本人自食惡果不足惜溺拱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望谣辞。 院中可真熱鬧迫摔,春花似錦、人聲如沸潦闲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽歉闰。三九已至辖众,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間和敬,已是汗流浹背凹炸。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留昼弟,地道東北人啤它。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像舱痘,于是被迫代替她去往敵國和親变骡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評論 2 355

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