【iOS重學(xué)】Block底層原理(一)

寫(xiě)在前面

關(guān)于Block的底層原理相關(guān)東西幼驶,需要了解的東西比較多,所以博主這里打算用兩篇文章來(lái)詳細(xì)分析一下OC里面的Block韧衣,這是第一篇盅藻,內(nèi)容主要包含:

  • Block的基本使用
  • Block的底層數(shù)據(jù)結(jié)構(gòu)
  • Block對(duì)變量的捕獲
  • Block的類(lèi)型
    好了,廢話(huà)少說(shuō)畅铭,現(xiàn)在跟著博主開(kāi)始從頭了解Block吧氏淑。

Block的基本使用

// 定義一個(gè)Block
void(^WWBlock)(void) = ^{
    NSLog(@"This is a block...");
};
        
// 調(diào)用Block
WWBlock();

以上是最簡(jiǎn)單的一個(gè)Block,調(diào)用Block()會(huì)打铀敦:This is a block...假残。

// Person 類(lèi)
@interface Person : NSObject

- (void)fetchDataWithSuccess:(void(^)(NSString *message))successBlock;

@end

@implementation Person

- (void)fetchDataWithSuccess:(void(^)(NSString *message))successBlock {
    if (successBlock) {
        successBlock(@"這是一個(gè)成功的回調(diào)...");
    }
}

@end

// 使用
Person *person = [[Person alloc] init];
[person fetchDataWithSuccess:^(NSString *message) {
    NSLog(@"---- %@", message);
}];

// 打印結(jié)果:
2022-12-06 19:30:08.775142+0800 BlockDemo[53402:5642770] ---- 這是一個(gè)成功的回調(diào)...

相信這種類(lèi)似的Block大家在日常項(xiàng)目中會(huì)看到很多很多,我們這里就不再一一列舉了炉擅。

Block的底層數(shù)據(jù)結(jié)構(gòu)

// 定義一個(gè)Block
void(^WWBlock)(void) = ^{
    NSLog(@"This is a block...");
};
        
// 調(diào)用Block
WWBlock();

我們來(lái)看一下上面最簡(jiǎn)單的Block的底層C++結(jié)構(gòu)是什么樣的辉懒,使用命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp轉(zhuǎn)為C++代碼如下:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        // 定義Block
        void(*WWBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
                            
        // 調(diào)用Block               
        ((void (*)(__block_impl *))((__block_impl *)WWBlock)->FuncPtr)((__block_impl *)WWBlock);
    }
    return 0;
}

把相應(yīng)的強(qiáng)制轉(zhuǎn)換給去掉能夠幫助我們更清晰的理解Block的結(jié)構(gòu),去掉一些強(qiáng)制轉(zhuǎn)換之后如下:

// 定義Block
void(*WWBlock)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
// 調(diào)用Block      
WWBlock->FuncPtr(WWBlock);

可以看到跟Block相關(guān)的有:__main_block_impl_0坑资、__main_block_func_0耗帕、__main_block_dec_0_DATA這幾個(gè)類(lèi),在生成的C++文件里面找到相關(guān)的結(jié)構(gòu)如下:

1.png

從上面的圖里面我們可以看到袱贮,Block的結(jié)構(gòu)里面也有一個(gè)isa指針仿便,所以Block的本質(zhì)其實(shí)也是一個(gè)【OC對(duì)象】,是一個(gè)封裝了函數(shù)調(diào)用及其調(diào)用環(huán)境的OC對(duì)象攒巍。
對(duì)應(yīng)關(guān)系如下圖所示:
2.png

在調(diào)用的時(shí)候WWBlock->FuncPtr(WWBlock)嗽仪,相當(dāng)于是拿到WWBlock結(jié)構(gòu)里面的函數(shù)指針FuncPtr去調(diào)用對(duì)應(yīng)的方法,這個(gè)函數(shù)指針里面其實(shí)存放的就是Block代碼塊的地址柒莉。

Block對(duì)變量的捕獲

為什么Block會(huì)對(duì)變量進(jìn)行捕獲闻坚?
因?yàn)樵贐lock里面可能需要訪(fǎng)問(wèn)Block外部變量的值,所以需要捕獲(capture)變量保證我們能正確訪(fǎng)問(wèn)到變量兢孝。
我們現(xiàn)在分別來(lái)分析【auto變量】窿凤、【static變量】仅偎、【全局變量】三種不同的變量,Block對(duì)其捕獲是不是也不一樣呢雳殊?

auto變量

auto變量:離開(kāi)當(dāng)前作用域就會(huì)銷(xiāo)毀的變量橘沥。

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

// 定義一個(gè)Block
int age = 10;
void(^WWBlock)(void) = ^{
    NSLog(@"對(duì)auoto變量的捕獲 - %d",age);
};
age = 20;

// 調(diào)用Block
WWBlock();

// 打印結(jié)果:
2022-12-06 20:18:28.957166+0800 BlockDemo[54326:5689813] 對(duì)auoto變量的捕獲 - 10

底層C++代碼如下:

3.png

跟前面我們沒(méi)有捕獲變量相比夯秃,會(huì)把age傳遞到Block里面:
4.png

從底層結(jié)構(gòu)我們也看到了:Block會(huì)對(duì)auto變量進(jìn)行捕獲座咆,訪(fǎng)問(wèn)的方式是【值傳遞】。

2仓洼、對(duì)象類(lèi)型的auto變量

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^WWBlock)(void);
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            WWBlock = ^{
                NSLog(@"person's age is %d",person.age);
            };
        }
        NSLog(@"-----");
        WWBlock();
    }
    return 0;
}

// 打印結(jié)果:
2022-12-07 15:45:31.593895+0800 BlockDemo[2248:6132111] -----
2022-12-07 15:45:31.594542+0800 BlockDemo[2248:6132111] person's age is 10
2022-12-07 15:45:31.594693+0800 BlockDemo[2248:6132111] -[Person dealloc]

底層結(jié)構(gòu)代碼如下:

11.png

12.png

對(duì)照上面的底層數(shù)據(jù)結(jié)構(gòu)發(fā)現(xiàn):多了兩個(gè)函數(shù)__main_block_copy_0__main_block_dispose_0介陶。
當(dāng)Block被拷貝到堆上時(shí),會(huì)調(diào)用Block內(nèi)部的copy函數(shù)__main_block_copy_0色建,函數(shù)里面調(diào)用_Block_object_assign哺呜,在_Block_object_assign里面根據(jù)auto變量的修飾符做出相應(yīng)的操作:強(qiáng)引用或弱引用。
當(dāng)Block從對(duì)上移除時(shí)镀岛,會(huì)調(diào)用Block內(nèi)部的dispose函數(shù)__main_block_dispose_0弦牡,函數(shù)里面調(diào)用_Block_object_dispose_Block_object_dispose去對(duì)引用的auto變量進(jìn)行一次release操作漂羊。

在使用clang轉(zhuǎn)換OC為C++代碼時(shí),如果有__weak可能會(huì)出現(xiàn)下面報(bào)錯(cuò):

13.png

讓其支持ARC卸留、指定運(yùn)行時(shí)系統(tǒng)版本即可走越,比如:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-14.0.0 main.m

static變量

static變量:離開(kāi)當(dāng)前作用域不會(huì)銷(xiāo)毀的變量。

// 定義一個(gè)Block
static int age = 10;
void(^WWBlock)(void) = ^{
    NSLog(@"對(duì)static變量的捕獲 - %d",age);
};
age = 20;

// 調(diào)用Block
WWBlock();

// 打印結(jié)果:
2022-12-06 20:27:55.716204+0800 BlockDemo[54594:5700817] 對(duì)static變量的捕獲 - 20

底層C++代碼如下:


5.png

6.png

從底層結(jié)構(gòu)我們也看到了:Block會(huì)對(duì)static變量進(jìn)行捕獲耻瑟,訪(fǎng)問(wèn)的方式是【指針傳遞】旨指。

全局變量

int age;
int main(int argc, const char * argv[]) {
  @autoreleasepool {
      // 定義一個(gè)Block
      age = 10;
      void(^WWBlock)(void) = ^{
          NSLog(@"age is %d",age);
      };
      age = 20;
      // 調(diào)用Block
      WWBlock();

  }
  return 0;
}

// 打印結(jié)果:
2022-12-07 13:39:45.012065+0800 BlockDemo[197:6032120] age is 20

底層C++代碼如下:


7.png

從底層結(jié)構(gòu)我們發(fā)現(xiàn):Block不會(huì)對(duì)全局變量進(jìn)行捕獲,直接訪(fǎng)問(wèn)全局變量即可喳整。

思考
1谆构、為什么Block不會(huì)捕獲全局變量,但是局部變量需要捕獲框都?
全局變量在任何時(shí)候任何地方都是可以訪(fǎng)問(wèn)到的搬素,因此不需要捕獲可以直接訪(fǎng)問(wèn)。
局部變量因?yàn)樽饔糜虻膯?wèn)題需要被捕獲魏保,保證Block內(nèi)部能夠正確訪(fǎng)問(wèn)到該變量熬尺。
2、Block會(huì)對(duì)捕獲self嗎谓罗?
會(huì)粱哼,說(shuō)明self是個(gè)局部變量。

- (void)test {
self.age = 10;
void(^WWBlock)(void) = ^{
NSLog(@"age is %d", self.age);
};
WWBlock();
}

底層C++代碼如下:

9.png

self_cmd其實(shí)是兩個(gè)隱式參數(shù)檩咱,所以我們能在里面正常訪(fǎng)問(wèn)self揭措、_cmd胯舷。

Block捕獲變量總結(jié)

8.png

Block的類(lèi)型

void(^block1)(void) = ^{
   NSLog(@"This is block...");
};

int age = 10;
void(^block2)(void) = ^{
   NSLog(@"age is %d",age);
};

NSLog(@"%@ %@ %@",[block1 class],[block2 class],[^{NSLog(@"age is %d", age);} class]);
NSLog(@"%@ %@ %@",[[block1 class] superclass], [[block2 class] superclass], [[^{NSLog(@"age is %d", age);} class] superclass]);

// 打印結(jié)果:
2022-12-07 15:40:29.626370+0800 BlockDemo[2188:6126897] __NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__
2022-12-07 15:40:29.627050+0800 BlockDemo[2188:6126897] NSBlock NSBlock NSBlock

Block有三種類(lèi)型:__NSGlobalBlock____NSMallocBlock__绊含、__NSStackBlock__桑嘶,都繼承自NSBlock

注意:編譯完的Block類(lèi)型和運(yùn)行時(shí)的Block類(lèi)型會(huì)有一些區(qū)別艺挪。

應(yīng)用程序的內(nèi)存分配

10.png

程序區(qū)域(代碼段):存放的就是我們寫(xiě)的一些代碼不翩。
數(shù)據(jù)區(qū)域:一般是存放一些全局變量。
堆:動(dòng)態(tài)分配內(nèi)存麻裳,自己管理內(nèi)存口蝠。
棧:系統(tǒng)自動(dòng)分配內(nèi)存,不需要自己管理內(nèi)存津坑。

Block類(lèi)型總結(jié)

1妙蔗、只要沒(méi)有訪(fǎng)問(wèn)auto變量的Block都是__NSGlobalBlock__類(lèi)型的。
2疆瑰、訪(fǎng)問(wèn)了auto變量的Block是__NSStackBlock__類(lèi)型的眉反,因?yàn)槲覀兪窃贏RC環(huán)境下,會(huì)自動(dòng)進(jìn)行copy操作穆役,所以是__NSMallocBlock__類(lèi)型的寸五。
3、__NSGlobalBlock__類(lèi)型的Block調(diào)用了copy還是__NSGlobalBlock__類(lèi)型耿币。

Block的copy操作

在ARC環(huán)境下梳杏,編譯器會(huì)根據(jù)情況自動(dòng)將棧上的Block復(fù)制到堆上,比如以下情況:

  • block作為函數(shù)返回值
  • 將block賦值給__strong指針時(shí)
  • block作為Cocoa API中方法名含有usingBlock的方法參數(shù)時(shí)
  • block作為GCD API的方法參數(shù)時(shí)

注意:MRC下Block使用copy關(guān)鍵字修飾淹接,ARC下Block使用strong或copy都可以十性,建議使用copy,與MRC下保持一致塑悼。

寫(xiě)在最后

關(guān)于Block底層原理的第一篇就分享到這里劲适,如有錯(cuò)誤請(qǐng)多多指教,歡迎大家去我的個(gè)人技術(shù)博客逛逛厢蒜。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末霞势,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子郭怪,更是在濱河造成了極大的恐慌支示,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,946評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鄙才,死亡現(xiàn)場(chǎng)離奇詭異颂鸿,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)攒庵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)嘴纺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)败晴,“玉大人,你說(shuō)我怎么就攤上這事栽渴〖饫ぃ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,716評(píng)論 0 364
  • 文/不壞的土叔 我叫張陵闲擦,是天一觀的道長(zhǎng)慢味。 經(jīng)常有香客問(wèn)我,道長(zhǎng)墅冷,這世上最難降的妖魔是什么纯路? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,222評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮寞忿,結(jié)果婚禮上驰唬,老公的妹妹穿的比我還像新娘。我一直安慰自己腔彰,他們只是感情好叫编,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,223評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著霹抛,像睡著了一般搓逾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上杯拐,一...
    開(kāi)封第一講書(shū)人閱讀 52,807評(píng)論 1 314
  • 那天恃逻,我揣著相機(jī)與錄音,去河邊找鬼藕施。 笑死,一個(gè)胖子當(dāng)著我的面吹牛凸郑,可吹牛的內(nèi)容都是我干的裳食。 我是一名探鬼主播,決...
    沈念sama閱讀 41,235評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼芙沥,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼诲祸!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起而昨,我...
    開(kāi)封第一講書(shū)人閱讀 40,189評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤救氯,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后歌憨,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體着憨,經(jīng)...
    沈念sama閱讀 46,712評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,775評(píng)論 3 343
  • 正文 我和宋清朗相戀三年务嫡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了甲抖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漆改。...
    茶點(diǎn)故事閱讀 40,926評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖准谚,靈堂內(nèi)的尸體忽然破棺而出挫剑,到底是詐尸還是另有隱情,我是刑警寧澤柱衔,帶...
    沈念sama閱讀 36,580評(píng)論 5 351
  • 正文 年R本政府宣布樊破,位于F島的核電站,受9級(jí)特大地震影響唆铐,放射性物質(zhì)發(fā)生泄漏哲戚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,259評(píng)論 3 336
  • 文/蒙蒙 一或链、第九天 我趴在偏房一處隱蔽的房頂上張望惫恼。 院中可真熱鬧,春花似錦澳盐、人聲如沸祈纯。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,750評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)腕窥。三九已至,卻和暖如春筛婉,著一層夾襖步出監(jiān)牢的瞬間簇爆,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,867評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工爽撒, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留入蛆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,368評(píng)論 3 379
  • 正文 我出身青樓硕勿,卻偏偏與公主長(zhǎng)得像哨毁,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子源武,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,930評(píng)論 2 361

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