窺探block底層結(jié)構(gòu)
我們寫下一個(gè)最簡(jiǎn)單的block使用clang指令生成對(duì)應(yīng)的C\C++代碼
void (^block)(void) = ^{
NSLog(@"Hello, World!");
};
block();
截取關(guān)鍵代碼如下
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// 構(gòu)造函數(shù)(類似于OC的init方法),返回結(jié)構(gòu)體對(duì)象
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// 封裝了block執(zhí)行邏輯的函數(shù)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
//這一句就是打印"Hello, World!"
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_c60393_mi_0);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
// 定義block變量
void (*block)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA
);
// 執(zhí)行block內(nèi)部的代碼
block->FuncPtr(block);
從上面代碼可以看出厂财,block本質(zhì)上也是一個(gè)OC對(duì)象粹湃,內(nèi)部也有個(gè)isa指針他嚷,并且內(nèi)部封裝了函數(shù)調(diào)用墓赴。
block的變量捕獲
寫下一個(gè)訪問(wèn)外部變量的block
int age = 10;
void (^block)(void) = ^{
NSLog(@"age is %d",age);
};
block();
生成C\C++代碼钉赁,截取關(guān)鍵部分
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
};
//block內(nèi)部的函數(shù)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2w_5f0_0k152c5d825xmdf9ztmr0000gn_T_main_b9df52_mi_0,age);
}
int age = 10;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
我們可以觀察到block結(jié)構(gòu)體多了個(gè)age變量,并且在初始化block時(shí)郭厌,將外部的age變量賦值給了結(jié)構(gòu)體內(nèi)部這個(gè)age變量袋倔,當(dāng)函數(shù)執(zhí)行時(shí),直接打印的是結(jié)構(gòu)體內(nèi)部的age變量沪曙。
所以我們可以總結(jié)一下奕污,block就是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對(duì)象
為了保證block內(nèi)部能正常訪問(wèn)外部的變量萎羔,block有個(gè)變量捕獲機(jī)制
變量類型 | 是否捕獲 | 訪問(wèn)方式 |
---|---|---|
auto局部變量 | 是 | 值傳遞 |
static局部變量 | 是 | 指針傳遞 |
全局變量 | 否 | 直接訪問(wèn) |
局部變量不寫修飾默認(rèn)就是auto變量液走,其實(shí)從內(nèi)存上也很好理解block的捕獲機(jī)制。auto局部變量在棧區(qū)贾陷,函數(shù)調(diào)用完后資源會(huì)被釋放掉缘眶,而static局部變量是在程序運(yùn)行過(guò)程中一直存在的(存在數(shù)據(jù)段),所以用指針隨時(shí)可以找到髓废,而全局變量本來(lái)就是在哪都可訪問(wèn)巷懈,根本沒(méi)必要捕獲。
block的類型
block有三種類型慌洪,可以通過(guò)class方法或者isa指針查看具體的類型顶燕,它們最終都繼承自NSBlock
類型 | 判斷依據(jù) | 存儲(chǔ)區(qū)域 | 調(diào)用copy結(jié)果 |
---|---|---|---|
__NSGlobalBlock__ | 沒(méi)有訪問(wèn)auto變量 | 數(shù)據(jù)段 | 什么也不做 |
__NSStackBlock__ | 訪問(wèn)了auto變量 | 棧區(qū) | 從棧區(qū)復(fù)制到堆 |
__NSMallockBlock__ | __NSStackBlock__調(diào)用了copy | 堆區(qū) | 引用計(jì)數(shù)增加 |
在ARC環(huán)境下會(huì)根據(jù)情況自動(dòng)將棧上的block復(fù)制到堆上,比如以下情況
- block作為函數(shù)返回值時(shí)
- block賦值給__strong指針時(shí)(對(duì)象類型的默認(rèn)修飾就是__strong)
- block作為Cocoa API中方法名含有usingBlock參數(shù)時(shí)
- block作為GCD API的方法參數(shù)時(shí)
MRC下建議用copy修飾block屬性冈爹,ARC可以用strong和copy修飾block屬性
block訪問(wèn)對(duì)象類型的auto變量
寫下如下代碼涌攻,用clang指令生成C\C++
NSObject *obj = [[NSObject alloc] init];
void (^block)(void) = ^{
NSLog(@"%@",obj);
};
block();
截取部分關(guān)鍵代碼,可以看到
當(dāng)block訪問(wèn)對(duì)象類型的auto變量時(shí)频伤,內(nèi)部多了copy函數(shù)和dispose函數(shù)
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
//定義block代碼
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344));
- 當(dāng)block在棧上時(shí)恳谎,不會(huì)對(duì)auto變量產(chǎn)生強(qiáng)引用
- 當(dāng)block從棧上被拷貝到堆上時(shí)
會(huì)調(diào)用block內(nèi)部的copy函數(shù),copy函數(shù)會(huì)調(diào)用內(nèi)部的_Block_object_assign函數(shù)憋肖,該函數(shù)會(huì)根據(jù)auto變量的修飾符(__strong因痛、__weak、__unsafe_unretained)做出相應(yīng)的操作岸更,形成強(qiáng)引用或弱引用 - 當(dāng)block從堆上移除時(shí)
block會(huì)調(diào)用內(nèi)部的dispose函數(shù)鸵膏,dispose函數(shù)會(huì)調(diào)用內(nèi)部的_Block_object_dispose函數(shù),該函數(shù)會(huì)釋放引用的auto變量(release)
__block修飾符
__block可以用于解決block內(nèi)部無(wú)法修改auto變量問(wèn)題怎炊,__block不能用來(lái)修飾全局變量谭企,靜態(tài)變量(static)
__block int age = 10;
^{
age = 30;
}();
NSLog(@"age is %d",age);
生成C\C++代碼
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref
}
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
//__block int age = 10;
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344))();
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2w_5f0_0k152c5d825xmdf9ztmr0000gn_T_main_ad80c7_mi_0,(age.__forwarding->age));
當(dāng)我們使用__block修飾age變量時(shí)用僧,會(huì)將age變量包轉(zhuǎn)成age對(duì)象,age對(duì)象里的int age存儲(chǔ)著最初的age值赞咙,__forwarding指針是指向age對(duì)象自己责循,block捕獲的是age對(duì)象的地址值。從最后的一句可以看出攀操,當(dāng)我們使用__block修飾auto變量后院仿,訪問(wèn)age都變成了訪問(wèn)age對(duì)象里的age成員變量。
- __forwarding指針
當(dāng)block從棧上拷貝到堆上時(shí)速和,棧上對(duì)象的__forwarding會(huì)指向堆上的拷貝對(duì)象(block拷貝到堆上時(shí)歹垫,會(huì)將捕獲的對(duì)象變量一并copy到堆上)
block循環(huán)引用問(wèn)題
從上面我們可以看到,block訪問(wèn)對(duì)象類型的auto變量時(shí)有可能會(huì)產(chǎn)生強(qiáng)引用颠放,當(dāng)訪問(wèn)的auto變量又對(duì)block產(chǎn)生強(qiáng)引用時(shí)就會(huì)發(fā)生循環(huán)應(yīng)用排惨。舉例如下
typedef void(^MyBlock)(void);
@interface Person : NSObject
@property (nonatomic, copy) MyBlock myBlock;
@end
//main函數(shù)里面
Person *person = [[Person alloc] init];
person.myBlock = ^{
NSLog(@"%@",person);
};
在ARC環(huán)境下可以使用__weak、__unsafe_unretained解決(一般使用__weak碰凶,會(huì)自動(dòng)置nil)
Person *person = [[Person alloc] init];
//或者 __unsafe_unretained typeof(Person *) weakPerson = person;
__weak typeof(Person *) weakPerson = person;
person.myBlock = ^{
NSLog(@"%@",weakPerson);
};
在MRC環(huán)境下可以使用__unsafe_unretained解決