寫(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)如下:
從上面的圖里面我們可以看到袱贮,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)系如下圖所示:
在調(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++代碼如下:
跟前面我們沒(méi)有捕獲變量相比夯秃,會(huì)把
age
傳遞到Block里面:從底層結(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)代碼如下:
對(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++代碼如下:
從底層結(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++代碼如下:
從底層結(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é)
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)存分配
程序區(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ù)博客逛逛厢蒜。