oc中block底層原理分析

探尋block的本質(zhì)

一.首先對(duì)block有一個(gè)基本的認(rèn)識(shí)

block本質(zhì)上也是一個(gè)oc對(duì)象舰褪,他內(nèi)部也有一個(gè)isa指針。block是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對(duì)象源譬。
block的底層結(jié)構(gòu):


:

__main_block_imp_0結(jié)構(gòu)體內(nèi)有一個(gè)同名構(gòu)造函數(shù)__main_block_imp_0千贯,構(gòu)造函數(shù)中對(duì)一些變量進(jìn)行了賦值最終會(huì)返回一個(gè)結(jié)構(gòu)體。
那么也就是說(shuō)最終將一個(gè)__main_block_imp_0結(jié)構(gòu)體的地址賦值給了block變量
__main_block_impl_0結(jié)構(gòu)體內(nèi)可以發(fā)現(xiàn)__main_block_impl_0構(gòu)造函數(shù)中傳入了四個(gè)參數(shù)晌坤。(void *)__main_block_func_0逢艘、&__main_block_desc_0_DATA、age骤菠、flags它改。其中flage有默認(rèn)值,也就說(shuō)flage參數(shù)在調(diào)用的時(shí)候可以省略不傳商乎。而最后的 age(_age)則表示傳入的_age參數(shù)會(huì)自動(dòng)賦值給age成員央拖,相當(dāng)于age = _age。


image.png
1. __main_block_func_0

__main_block_func_0函數(shù)中其實(shí)存儲(chǔ)著我們block中寫(xiě)下的代碼鹉戚。而__main_block_impl_0函數(shù)中傳入的是(void *)__main_block_func_0鲜戒,也就說(shuō)將我們寫(xiě)在block塊中的代碼封裝成__main_block_func_0函數(shù),并將__main_block_func_0函數(shù)的地址傳入了__main_block_impl_0的構(gòu)造函數(shù)中保存在結(jié)構(gòu)體內(nèi)抹凳。

2. __main_block_desc_0_DATA

_main_block_desc_0中存儲(chǔ)著兩個(gè)參數(shù)遏餐,reserved和Block_size,并且reserved賦值為0而B(niǎo)lock_size則存儲(chǔ)著__main_block_impl_0的占用空間大小赢底。最終將__main_block_desc_0結(jié)構(gòu)體的地址傳入__main_block_func_0中賦值給Desc失都。

3. age

age也就是我們定義的局部變量。因?yàn)樵赽lock塊中使用到age局部變量幸冻,所以在block聲明的時(shí)候這里才會(huì)將age作為參數(shù)傳入粹庞,也就說(shuō)block會(huì)捕獲age,如果沒(méi)有在block中使用age洽损,這里將只會(huì)傳入(void *)__main_block_func_0庞溜,&__main_block_desc_0_DATA兩個(gè)參數(shù)。

__main_block_impl_0結(jié)構(gòu)體
image.png

我們可以發(fā)現(xiàn)__block_impl結(jié)構(gòu)體內(nèi)部就有一個(gè)isa指針碑定。因此可以證明block本質(zhì)上就是一個(gè)oc對(duì)象流码。而在構(gòu)造函數(shù)中將函數(shù)中傳入的值分別存儲(chǔ)在__main_block_impl_0結(jié)構(gòu)體實(shí)例中,最終將結(jié)構(gòu)體的地址賦值給block延刘。

接著通過(guò)上面對(duì)__main_block_impl_0結(jié)構(gòu)體構(gòu)造函數(shù)三個(gè)參數(shù)的分析我們可以得出結(jié)論:
1. __block_impl結(jié)構(gòu)體中isa指針存儲(chǔ)著&_NSConcreteStackBlock地址旅掂,可以暫時(shí)理解為其類(lèi)對(duì)象地址,block就是_NSConcreteStackBlock類(lèi)型的访娶。
2. block代碼塊中的代碼被封裝成__main_block_func_0函數(shù)商虐,F(xiàn)uncPtr則存儲(chǔ)著__main_block_func_0函數(shù)的地址。
3. Desc指向__main_block_desc_0結(jié)構(gòu)體對(duì)象崖疤,其中存儲(chǔ)__main_block_impl_0結(jié)構(gòu)體所占用的內(nèi)存秘车。

二.block的變量捕獲

1.局部變量
auto變量

上述代碼中我們已經(jīng)了解過(guò)block對(duì)age變量的捕獲。
auto自動(dòng)變量劫哼,離開(kāi)作用域就銷(xiāo)毀叮趴,局部變量前面自動(dòng)添加auto關(guān)鍵字。自動(dòng)變量會(huì)捕獲到block內(nèi)部权烧,也就是說(shuō)block內(nèi)部會(huì)專(zhuān)門(mén)新增加一個(gè)參數(shù)來(lái)存儲(chǔ)變量的值眯亦。
auto只存在于局部變量中伤溉,訪問(wèn)方式為值傳遞,通過(guò)上述對(duì)age參數(shù)的解釋我們也可以確定確實(shí)是值傳遞妻率。

static變量

static 修飾的變量為指針傳遞乱顾,同樣會(huì)被block捕獲。
接下來(lái)分別添加aotu修飾的局部變量和static修飾的局部變量宫静,重看源碼來(lái)看一下他們之間的差別走净。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        auto int a = 10;
        static int b = 11;
        void(^block)(void) = ^{
            NSLog(@"hello, a = %d, b = %d", a,b);
        };
        a = 1;
        b = 2;
        block();
    }
    return 0;
}
// log : block本質(zhì)[57465:18555229] hello, a = 10, b = 2
// block中a的值沒(méi)有被改變而b的值隨外部變化而變化。

為什么兩種變量會(huì)有這種差異呢孤里,因?yàn)樽詣?dòng)變量可能會(huì)銷(xiāo)毀伏伯,block在執(zhí)行的時(shí)候有可能自動(dòng)變量已經(jīng)被銷(xiāo)毀了,那么此時(shí)如果再去訪問(wèn)被銷(xiāo)毀的地址肯定會(huì)發(fā)生壞內(nèi)存訪問(wèn)捌袜,因此對(duì)于自動(dòng)變量一定是值傳遞而不可能是指針傳遞了说搅。而靜態(tài)變量不會(huì)被銷(xiāo)毀,所以完全可以傳遞地址虏等。而因?yàn)閭鬟f的是值得地址蜓堕,所以在block調(diào)用之前修改地址中保存的值,block中的地址是不會(huì)變得博其。所以值會(huì)隨之改變套才。

2.全局變量

我們同樣以代碼的方式看一下block是否捕獲全局變量

int a = 10;
static int b = 11;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^block)(void) = ^{
            NSLog(@"hello, a = %d, b = %d", a,b);
        };
        a = 1;
        b = 2;
        block();
    }
    return 0;
}
// log hello, a = 1, b = 2

通過(guò)上述代碼可以發(fā)現(xiàn),__main_block_imp_0并沒(méi)有添加任何變量慕淡,因此block不需要捕獲全局變量背伴,因?yàn)槿肿兞繜o(wú)論在哪里都可以訪問(wèn)。
最后以一張圖做一個(gè)總結(jié):


image.png
總結(jié):局部變量都會(huì)被block捕獲峰髓,自動(dòng)變量是值捕獲傻寂,靜態(tài)變量為地址捕獲富稻。全局變量則不會(huì)被block捕獲

三.block的類(lèi)型

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 1. 內(nèi)部沒(méi)有調(diào)用外部變量的block
        void (^block1)(void) = ^{
            NSLog(@"Hello");
        };
        // 2. 內(nèi)部調(diào)用外部變量的block
        int a = 10;
        void (^block2)(void) = ^{
            NSLog(@"Hello - %d",a);
        };
       // 3. 直接調(diào)用的block的class
        NSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{
            NSLog(@"%d",a);
        } class]);
    }
    return 0;
}

通過(guò)打印內(nèi)容確實(shí)可以發(fā)現(xiàn)block的三種類(lèi)型


image.png

但是我們上面提到過(guò)偿乖,上述代碼轉(zhuǎn)化為c++代碼查看源碼時(shí)卻發(fā)現(xiàn)block的類(lèi)型與打印出來(lái)的類(lèi)型不一樣汇在,c++源碼中三個(gè)block的isa指針全部都指向_NSConcreteStackBlock類(lèi)型地址癣丧。
我們可以猜測(cè)runtime運(yùn)行時(shí)過(guò)程中也許對(duì)類(lèi)型進(jìn)行了轉(zhuǎn)變。最終類(lèi)型當(dāng)然以runtime運(yùn)行時(shí)類(lèi)型也就是我們打印出的類(lèi)型為準(zhǔn)士飒。

block在內(nèi)存中的存儲(chǔ)

通過(guò)下面一張圖看一下不同block的存放區(qū)域

image.png

上圖中可以發(fā)現(xiàn)帘营,根據(jù)block的類(lèi)型不同尉姨,block存放在不同的區(qū)域中并级。
1.數(shù)據(jù)段中的NSGlobalBlock直到程序結(jié)束才會(huì)被回收拂檩,不過(guò)我們很少使用到NSGlobalBlock類(lèi)型的block,因?yàn)檫@樣使用block并沒(méi)有什么意義嘲碧。
2.NSStackBlock類(lèi)型的block存放在棧中稻励,我們知道棧中的內(nèi)存由系統(tǒng)自動(dòng)分配和釋放,作用域執(zhí)行完畢之后就會(huì)被立即釋放愈涩,而在相同的作用域中定義block并且調(diào)用block似乎也多此一舉望抽。
3.NSMallocBlock是在平時(shí)編碼過(guò)程中最常使用到的加矛。存放在堆中需要我們自己進(jìn)行內(nèi)存管理。

block是如何定義其類(lèi)型

MRC下
block是如何定義其類(lèi)型煤篙,依據(jù)什么來(lái)為block定義不同的類(lèi)型并分配在不同的空間呢斟览?首先看下面一張圖


image.png

接著我們使用代碼驗(yàn)證上述問(wèn)題,首先關(guān)閉ARC回到MRC環(huán)境下舰蟆,因?yàn)锳RC會(huì)幫助我們做很多事情,可能會(huì)影響我們的觀察狸棍。

// MRC環(huán)境I砗Α!草戈!
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // Global:沒(méi)有訪問(wèn)auto變量:__NSGlobalBlock__
        void (^block1)(void) = ^{
            NSLog(@"block1---------");
        };   
        // Stack:訪問(wèn)了auto變量: __NSStackBlock__
        int a = 10;
        void (^block2)(void) = ^{
            NSLog(@"block2---------%d", a);
        };
        NSLog(@"%@ %@", [block1 class], [block2 class]);
        // __NSStackBlock__調(diào)用copy : __NSMallocBlock__
        NSLog(@"%@", [[block2 copy] class]);
    }
    return 0;
}

查看打印內(nèi)容

image.png

通過(guò)打印的內(nèi)容可以發(fā)現(xiàn)正如上圖中所示塌鸯。
沒(méi)有訪問(wèn)auto變量的block是NSGlobalBlock類(lèi)型的,存放在數(shù)據(jù)段中唐片。
訪問(wèn)了auto變量的block是NSStackBlock類(lèi)型的丙猬,存放在棧中。
NSStackBlock類(lèi)型的block調(diào)用copy成為NSMallocBlock類(lèi)型并被復(fù)制存放在堆中费韭。
那么其他類(lèi)型的block調(diào)用copy會(huì)改變block類(lèi)型嗎茧球?
image.png

所以在平時(shí)開(kāi)發(fā)過(guò)程中MRC環(huán)境下經(jīng)常需要使用copy來(lái)保存block,將棧上的block拷貝到堆中星持,即使棧上的block被銷(xiāo)毀抢埋,堆上的block也不會(huì)被銷(xiāo)毀,需要我們自己調(diào)用release操作來(lái)銷(xiāo)毀督暂。而在ARC環(huán)境下回系統(tǒng)會(huì)自動(dòng)copy揪垄,是block不會(huì)被銷(xiāo)毀。

ARC幫我們做了什么

在ARC環(huán)境下逻翁,編譯器會(huì)根據(jù)情況自動(dòng)將棧上的block進(jìn)行一次copy操作饥努,將block復(fù)制到堆上。

什么情況下ARC會(huì)自動(dòng)將block進(jìn)行一次copy操作八回?
以下代碼都在RAC環(huán)境下執(zhí)行酷愧。

1. block作為函數(shù)返回值時(shí)
typedef void (^Block)(void);
Block myblock()
{
    int a = 10;
    // 上文提到過(guò),block中訪問(wèn)了auto變量缠诅,此時(shí)block類(lèi)型應(yīng)為_(kāi)_NSStackBlock__
    Block block = ^{
        NSLog(@"---------%d", a);
    };
    return block;
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block = myblock();
        block();
       // 打印block類(lèi)型為 __NSMallocBlock__
        NSLog(@"%@",[block class]);
    }
    return 0;
}

看一下打印的內(nèi)容

image.png

上文提到過(guò)伟墙,如果在block中訪問(wèn)了auto變量時(shí),block的類(lèi)型為NSStackBlock滴铅,上面打印內(nèi)容發(fā)現(xiàn)blcok為NSMallocBlock類(lèi)型的戳葵,并且可以正常打印出a的值,說(shuō)明block內(nèi)存并沒(méi)有被銷(xiāo)毀汉匙。
上面提到過(guò)拱烁,block進(jìn)行copy操作會(huì)轉(zhuǎn)化為NSMallocBlock類(lèi)型生蚁,來(lái)講block復(fù)制到堆中,那么說(shuō)明RAC在 block作為函數(shù)返回值時(shí)會(huì)自動(dòng)幫助我們對(duì)block進(jìn)行copy操作戏自,以保存block邦投,并在適當(dāng)?shù)牡胤竭M(jìn)行release操作。

2. 將block賦值給__strong指針時(shí)

block被強(qiáng)指針引用時(shí)擅笔,RAC也會(huì)自動(dòng)對(duì)block進(jìn)行一次copy操作志衣。

nt main(int argc, const char * argv[]) {
    @autoreleasepool {
        // block內(nèi)沒(méi)有訪問(wèn)auto變量
        Block block = ^{
            NSLog(@"block---------");
        };
        NSLog(@"%@",[block class]);
        int a = 10;
        // block內(nèi)訪問(wèn)了auto變量,但沒(méi)有賦值給__strong指針
        NSLog(@"%@",[^{
            NSLog(@"block1---------%d", a);
        } class]);
        // block賦值給__strong指針
        Block block2 = ^{
          NSLog(@"block2---------%d", a);
        };
        NSLog(@"%@",[block1 class]);
    }
    return 0;
}

查看打印內(nèi)容可以看出猛们,當(dāng)block被賦值給__strong指針時(shí)念脯,RAC會(huì)自動(dòng)進(jìn)行一次copy操作。


image.png
3. block作為Cocoa API中方法名含有usingBlock的方法參數(shù)時(shí)

例如:遍歷數(shù)組的block方法弯淘,將block作為參數(shù)的時(shí)候绿店。

NSArray *array = @[];
[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            
}];
4. block作為GCD API的方法參數(shù)時(shí)

例如:GDC的一次性函數(shù)或延遲執(zhí)行的函數(shù),執(zhí)行完block操作之后系統(tǒng)才會(huì)對(duì)block進(jìn)行release操作庐橙。

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
            
});        
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
});

block聲明寫(xiě)法

通過(guò)上面對(duì)MRC及ARC環(huán)境下block的不同類(lèi)型的分析假勿,總結(jié)出不同環(huán)境下block屬性建議寫(xiě)法。

MRC下block屬性的建議寫(xiě)法

@property (copy, nonatomic) void (^block)(void);

ARC下block屬性的建議寫(xiě)法

@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);

文章來(lái)源鏈接:http://www.reibang.com/p/c99f4974ddb5

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末态鳖,一起剝皮案震驚了整個(gè)濱河市转培,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌浆竭,老刑警劉巖堡距,帶你破解...
    沈念sama閱讀 218,640評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異兆蕉,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)虎韵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)驶社,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事或辖∷滔荆” “怎么了湿蛔?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,011評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)碉钠。 經(jīng)常有香客問(wèn)我祝高,道長(zhǎng)工闺,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,755評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮林束,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘胖腾。我一直安慰自己,他們只是感情好群井,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著酵使,像睡著了一般荐吉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上口渔,一...
    開(kāi)封第一講書(shū)人閱讀 51,610評(píng)論 1 305
  • 那天样屠,我揣著相機(jī)與錄音,去河邊找鬼缺脉。 笑死痪欲,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的攻礼。 我是一名探鬼主播业踢,決...
    沈念sama閱讀 40,352評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼礁扮!你這毒婦竟也來(lái)了知举?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,257評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤太伊,失蹤者是張志新(化名)和其女友劉穎雇锡,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體僚焦,經(jīng)...
    沈念sama閱讀 45,717評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锰提,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了叠赐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片欲账。...
    茶點(diǎn)故事閱讀 40,021評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖芭概,靈堂內(nèi)的尸體忽然破棺而出赛不,到底是詐尸還是另有隱情,我是刑警寧澤罢洲,帶...
    沈念sama閱讀 35,735評(píng)論 5 346
  • 正文 年R本政府宣布踢故,位于F島的核電站文黎,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏殿较。R本人自食惡果不足惜耸峭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望淋纲。 院中可真熱鬧劳闹,春花似錦、人聲如沸洽瞬。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,936評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)伙窃。三九已至菩颖,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間为障,已是汗流浹背晦闰。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,054評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鳍怨,地道東北人呻右。 一個(gè)月前我還...
    沈念sama閱讀 48,224評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像京景,于是被迫代替她去往敵國(guó)和親窿冯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子骗奖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評(píng)論 2 355

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

  • 面試題 block的原理是怎樣的确徙?本質(zhì)是什么? __block的作用是什么执桌?有什么使用注意點(diǎn)鄙皇? block的屬性修...
    xx_cc閱讀 13,440評(píng)論 10 77
  • 1. Block的底層結(jié)構(gòu) 以下是一個(gè)沒(méi)有參數(shù)和返回值的最簡(jiǎn)單的Block: 為了探索Block的底層結(jié)構(gòu),需要將...
    再好一點(diǎn)點(diǎn)閱讀 466評(píng)論 0 4
  • 摘要block是2010年WWDC蘋(píng)果為Objective-C提供的一個(gè)新特性仰挣,它為我們開(kāi)發(fā)提供了便利伴逸,比如GCD...
    西門(mén)吹雪123閱讀 916評(píng)論 0 4
  • Block在iOS開(kāi)發(fā)中的用途非常廣,今天我們就來(lái)一起探索一下Block的底層結(jié)構(gòu)膘壶。 1. Block的底層結(jié)構(gòu) ...
    雪山飛狐_91ae閱讀 3,933評(píng)論 16 28
  • 今日浮躁颓芭,或者說(shuō)自己間間斷斷浮躁了很久顷锰。 拿起手機(jī),點(diǎn)開(kāi)百度云亡问,搜索絕望主婦官紫,上床,充電寶,看劇束世。 時(shí)間就跟不要錢(qián)...
    愫_17f4閱讀 198評(píng)論 0 2