iOS底層原理 - 探尋block本質(zhì) 之 copy

面試題引發(fā)的思考:

Q: block的屬性修飾詞為什么是copy?

  • 如果block是在棧上,將不會(huì)對(duì)auto變量產(chǎn)生強(qiáng)引用智嚷;
  • 如果block被copy到堆上:
    1> 會(huì)調(diào)用block內(nèi)部的copy函數(shù);
    2> copy函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù)纺且;
    3> _Block_object_assign函數(shù)會(huì)根據(jù)auto變量的修飾符(__strong盏道,_weak)做出相應(yīng)的操作,形成強(qiáng)引用隆檀、弱引用(僅限于ARC時(shí)會(huì)retain摇天,MRC時(shí)不會(huì))
  • 如果block從堆上移除:
    1> 會(huì)調(diào)用block內(nèi)部的dispose函數(shù)恐仑;
    2> dispose函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose函數(shù)泉坐;
    3> _Block_object_dispose函數(shù)會(huì)自動(dòng)釋放引用的auto變量,類(lèi)似于release裳仆。

對(duì)象類(lèi)型的auto變量

iOS底層原理 - 探尋block本質(zhì)(一)中介紹到block底層原理以及block的變量捕獲腕让,那么block對(duì)對(duì)象類(lèi)型變量的捕獲同對(duì)基本數(shù)據(jù)類(lèi)型變量的捕獲是否相同?

(1) 椘缯澹空間纯丸、堆空間的強(qiáng)引用、弱引用

Q: 首先查看一下代碼静袖,block訪(fǎng)問(wèn)對(duì)象類(lèi)型變量時(shí)觉鼻,對(duì)象何時(shí)銷(xiāo)毀?

// TODO: -----------------  Person類(lèi)  -----------------
@interface Person : NSObject
@property (nonatomic, assign) int age;
@end

@implementation Person
- (void)dealloc {
    NSLog(@"------------ Person - dealloc");
}
@end

// TODO: -----------------  main  -----------------
typedef void (^Block)(void);

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

// 打印結(jié)果
Demo[1234:567890] ------------ 外部 
Demo[1234:567890] ------------ Person - dealloc

由打印結(jié)果可知:
大括號(hào)執(zhí)行完畢坠陈,person沒(méi)有被釋放。
person對(duì)象是在大括號(hào)內(nèi)聲明的局部變量捐康,它的生命周期僅限于這個(gè)大括號(hào)內(nèi)仇矾,打印結(jié)果是什么原因造成的?

上篇文章介紹到:personauto變量解总,person會(huì)被捕獲到block內(nèi)部贮匕,即block對(duì)person進(jìn)行強(qiáng)引用; 那么block銷(xiāo)毀之前花枫,person是不會(huì)被銷(xiāo)毀的刻盐。

查看源碼掏膏,符合我們的結(jié)論。

強(qiáng)指針引用

下面我們進(jìn)入MRC環(huán)境:

// MRC環(huán)境
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            block = ^{
                NSLog(@"------------ 內(nèi)部 %d", person.age);
            };
            [person release];
        }  // 執(zhí)行完畢隙疚,person被釋放
        NSLog(@"------------ 外部");
    }
    return 0;
}

// 打印結(jié)果
Demo[1234:567890] ------------ Person - dealloc
Demo[1234:567890] ------------ 外部

由打印結(jié)果可知:
大括號(hào)執(zhí)行完畢壤追,person被釋放。
原因是:在MRC環(huán)境下供屉,block訪(fǎng)問(wèn)auto變量,會(huì)在椖缃叮空間伶丐,不會(huì)對(duì)person進(jìn)行強(qiáng)引用。

對(duì)block進(jìn)行copy操作疯特,person沒(méi)有被釋放

// MRC環(huán)境
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            block = [^{
                NSLog(@"------------ 內(nèi)部 %d", person.age);
            } copy];
            [person release];
        }
        // 執(zhí)行完畢哗魂,person沒(méi)有被釋放
        NSLog(@"------------ 外部");
        // 需要block銷(xiāo)毀掉,person才會(huì)被釋放
        [block release];
    }
    return 0;
}

// 打印結(jié)果
Demo[1234:567890] ------------ 外部 
Demo[1234:567890] ------------ Person - dealloc

由打印結(jié)果可知:
大括號(hào)執(zhí)行完畢漓雅,person沒(méi)有被釋放录别。
原因是:對(duì)棧空間的block進(jìn)行copy操作邻吞,將椬樘猓空間的block拷貝到堆中,會(huì)對(duì)person進(jìn)行強(qiáng)引用抱冷;
堆空間的block可能會(huì)對(duì)person進(jìn)行一次retain操作崔列,保證person不被銷(xiāo)毀,堆空間的block銷(xiāo)毀之后會(huì)對(duì)person進(jìn)行release操作旺遮。

總結(jié)可知:堆區(qū)的block對(duì)person對(duì)象有強(qiáng)引用作用赵讯,棧空間的block對(duì)person對(duì)象沒(méi)有強(qiáng)引用作用耿眉。


(2) 關(guān)鍵字__weak

切回ARC環(huán)境边翼,執(zhí)行下列代碼:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;

            __weak Person *weakPerson = person;
            block = ^{
                NSLog(@"------------ 內(nèi)部 %d", weakPerson.age);
            };
        }
        // 執(zhí)行完畢,person沒(méi)有被釋放
        NSLog(@"------------ 外部");
    }
    return 0;
}

// 打印結(jié)果
Demo[1234:567890] ------------ Person - dealloc
Demo[1234:567890] ------------ 外部

將代碼轉(zhuǎn)化成C++鸣剪,_weak修飾變量摸柄,需要告知編譯器使用ARC環(huán)境及版本號(hào):
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

__weak修飾變量

由源碼可知:_weak修飾的變量洛搀,在生成的__main_block_impl_0中也是使用_weak修飾。
所以對(duì)person對(duì)象是弱引用,不能改變person對(duì)象的生命周期鞍盗。


(3) _main_block_copy_0函數(shù)和 __main_block_dispose_0函數(shù)

block捕獲對(duì)象類(lèi)型的變量時(shí)__main_block_impl_0內(nèi)部結(jié)構(gòu)體__main_block_desc_0中多了copy函數(shù)和dispose函數(shù)兩個(gè)參數(shù):

__main_block_copy_0、__main_block_dispose_0函數(shù)

copy函數(shù)和dispose函數(shù)中傳入的都是__main_block_impl_0結(jié)構(gòu)體本身赁咙。

a> copy函數(shù)本質(zhì)是__main_block_copy_0函數(shù)今野,其內(nèi)部調(diào)用_Block_object_assign函數(shù);
_Block_object_assign中傳入的是person對(duì)象的地址饺鹃,person對(duì)象莫秆,以及8间雀。

b> dispose函數(shù)本質(zhì)是__main_block_dispose_0函數(shù),其內(nèi)部調(diào)用_Block_object_dispose函數(shù)镊屎;
_Block_object_dispose函數(shù)傳入的參數(shù)是person對(duì)象惹挟,以及8

1> _Block_object_assign函數(shù)

block進(jìn)行copy操作的時(shí)候就會(huì)自動(dòng)調(diào)用__main_block_desc_0內(nèi)部的__main_block_copy_0函數(shù)缝驳;
__main_block_copy_0函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù)连锯;
_Block_object_assign函數(shù)會(huì)自動(dòng)根據(jù)__main_block_impl_0結(jié)構(gòu)體內(nèi)部的person的指針類(lèi)型,對(duì)person對(duì)象產(chǎn)生強(qiáng)引用或者弱引用用狱。

2> _Block_object_dispose函數(shù)

當(dāng)block從堆中移除時(shí)就會(huì)自動(dòng)調(diào)用__main_block_desc_0中的__main_block_dispose_0函數(shù)运怖;
__main_block_dispose_0函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose函數(shù);
_Block_object_dispose會(huì)對(duì)person對(duì)象做釋放操作夏伊,類(lèi)似于release摇展,即斷開(kāi)對(duì)person對(duì)象的引用,而person究竟是否被釋放還是取決于person對(duì)象自己的引用計(jì)數(shù)溺忧。

總結(jié)可知:

當(dāng)block內(nèi)部訪(fǎng)問(wèn)了對(duì)象類(lèi)型的auto變量時(shí):

  • 如果block是在棧上咏连,將不會(huì)對(duì)auto變量產(chǎn)生強(qiáng)引用;
  • 如果block被拷貝到堆上:
    1> 會(huì)調(diào)用block內(nèi)部的copy函數(shù)鲁森;
    2> copy函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù)祟滴;
    3> _Block_object_assign函數(shù)會(huì)根據(jù)auto變量的修飾符(__strong_weak)做出相應(yīng)的操作刀森,形成強(qiáng)引用踱启,弱引用。
  • 如果block從堆上移除:
    1> 會(huì)調(diào)用block內(nèi)部的dispose函數(shù)研底;
    2> dispose函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose函數(shù)埠偿;
    3> _Block_object_dispose函數(shù)會(huì)自動(dòng)釋放引用的auto變量,類(lèi)似于release榜晦。

(4) 以下幾個(gè)示例中person都在何時(shí)銷(xiāo)毀冠蒋?

1> 示例一:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    Person *person = [[Person alloc] init];

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"------------ %@", person);
    });
    NSLog(@"------------ touchesBegan");
}
打印結(jié)果

打印結(jié)果顯示:block執(zhí)行完畢后person對(duì)象銷(xiāo)毀乾胶。

iOS底層原理 - 探尋block本質(zhì)(一)可知:
ARC環(huán)境中抖剿,block作為GCD API的方法參數(shù)時(shí)會(huì)自動(dòng)進(jìn)行copy操作斩郎,將block復(fù)制到堆上,所以block內(nèi)部copy函數(shù)會(huì)對(duì)person進(jìn)行強(qiáng)引用喻频。

當(dāng)block執(zhí)行完畢需要銷(xiāo)毀時(shí)缩宜,調(diào)用dispose函數(shù)釋放對(duì)person對(duì)象的引用,person沒(méi)有強(qiáng)指針引用時(shí)會(huì)被銷(xiāo)毀锻煌。

2> 示例二:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    Person *person = [[Person alloc] init];

    __weak Person *weakPerson = person;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"------------ %@", weakPerson);
    });
    NSLog(@"------------ touchesBegan");
}
打印結(jié)果

打印結(jié)果顯示:person對(duì)象先銷(xiāo)毀,然后執(zhí)行block宋梧,打印為null匣沼。

block對(duì)weakPerson__weak弱引用,所以block內(nèi)部copy函數(shù)會(huì)對(duì)person進(jìn)行弱引用释涛。
當(dāng)touchesBegan: withEvent:執(zhí)行完畢時(shí)倦沧,person沒(méi)有強(qiáng)指針引用時(shí)會(huì)被銷(xiāo)毀,所以block執(zhí)行時(shí)打印null

3> 示例三:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    Person *person = [[Person alloc] init];

    __weak Person *weakPerson = person;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"------------1 %@", person);
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"------------2 %@", weakPerson);
        });
    });
    NSLog(@"------------ touchesBegan");
}
打印結(jié)果

打印結(jié)果顯示:外層block執(zhí)行完畢后愈污,即1秒后person對(duì)象銷(xiāo)毀轮傍,然后執(zhí)行內(nèi)層block,即3秒后打印為null创夜。

外層block對(duì)person進(jìn)行強(qiáng)引用,內(nèi)層block對(duì)person進(jìn)行弱引用涧尿。
當(dāng)外層block執(zhí)行完畢時(shí)檬贰,person有強(qiáng)指針引用;然后內(nèi)層block執(zhí)行時(shí)person沒(méi)有強(qiáng)指針會(huì)被銷(xiāo)毀桥言,所以?xún)?nèi)層block執(zhí)行時(shí)打印null葵礼。

4> 示例四:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    Person *person = [[Person alloc] init];

    __weak Person *weakPerson = person;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"------------1 %@", weakPerson);

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"------------2 %@", person);
        });
    });
    NSLog(@"------------ touchesBegan");
}
打印結(jié)果

打印結(jié)果顯示:外層block執(zhí)行完畢后鸳粉,然后執(zhí)行內(nèi)層block扔涧,3秒后person對(duì)象銷(xiāo)毀赁严。

外層block對(duì)person進(jìn)行弱引用粉铐,內(nèi)層block對(duì)person進(jìn)行強(qiáng)引用卤档。
person的強(qiáng)引用何時(shí)結(jié)束劝枣,person何時(shí)dealloc,所以3秒后person對(duì)象才銷(xiāo)毀舔腾。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末稳诚,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子才避,更是在濱河造成了極大的恐慌氨距,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件楞遏,死亡現(xiàn)場(chǎng)離奇詭異首昔,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)拘荡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)撬陵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蟋定,你說(shuō)我怎么就攤上這事草添。” “怎么了抄淑?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)矗愧。 經(jīng)常有香客問(wèn)我郑原,道長(zhǎng),這世上最難降的妖魔是什么犯犁? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任酸役,我火速辦了婚禮,結(jié)果婚禮上只壳,老公的妹妹穿的比我還像新娘暑塑。我一直安慰自己锅必,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布驹愚。 她就那樣靜靜地躺著劣纲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪劫瞳。 梳的紋絲不亂的頭發(fā)上绷柒,一...
    開(kāi)封第一講書(shū)人閱讀 49,741評(píng)論 1 289
  • 那天废睦,我揣著相機(jī)與錄音,去河邊找鬼。 笑死澜掩,一個(gè)胖子當(dāng)著我的面吹牛杖挣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播程梦,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼屿附,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了褒翰?” 一聲冷哼從身側(cè)響起匀泊,我...
    開(kāi)封第一講書(shū)人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤各聘,失蹤者是張志新(化名)和其女友劉穎揣非,沒(méi)想到半個(gè)月后早敬,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體大脉,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年琐驴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了秤标。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片苍姜。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖茁帽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情潘拨,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布季蚂,位于F島的核電站琅束,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏料滥。R本人自食惡果不足惜艾船,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望践宴。 院中可真熱鬧爷怀,春花似錦、人聲如沸磺浙。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至锦溪,卻和暖如春府怯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背牺丙。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留粟判,地道東北人档礁。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像递礼,于是被迫代替她去往敵國(guó)和親羹幸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

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

  • 快過(guò)年了蔚出,又大了一歲。那些被逼婚的其實(shí)還是讓人妒忌的骄酗,證明還算年輕,重點(diǎn)是無(wú)拘無(wú)束睛琳。 有一群人踏烙,他們已婚有小孩,快...
    鞥咕閱讀 427評(píng)論 0 0
  • 靜: 快過(guò)年了 去年的現(xiàn)在辟癌,你在廣西游山玩水呢 時(shí)間真快荐捻,我們還一起去看了廟會(huì) 我不知道我還能在這個(gè)世界上活多久寡夹,...
    二毛砸閱讀 186評(píng)論 0 0
  • 我欲從此過(guò) 諸位列隊(duì)侯 燈光照我 星光佑我 一路向著前
    圈兒里人閱讀 217評(píng)論 0 1