主要介紹block的類型和底層分析
block類型
block主要由三種類型
- NSGlobalBlock:全局block妇多,存儲在全局區(qū)
void(^block)(void) = ^{
NSLog(@"LTD");
};
NSLog(@"%@", block);
當(dāng)前block內(nèi)沒有捕獲外部變量,屬于全局block纸巷。
- NSMallocBlock:堆區(qū)block,因為block既是函數(shù)眶痰,也是對象
int a = 10;
void(^testBlock)(void) = ^{
NSLog(@"%d",a);
};
testBlock();
NSLog(@"%@",testBlock);
當(dāng)前block會捕獲外界變量瘤旨,是堆區(qū)block。
- NSStackBlock:棧區(qū)block
int a = 10;
void(^__weak testBlock)(void) = ^{
NSLog(@"%d",a);
};
NSLog(@"%@",testBlock);
如果不用__weak
修飾變量竖伯,那么該變量就是NSMallocBlock
存哲。但是通過__weak
修飾變量后,那該變量類型就是棧區(qū)block
七婴。
總結(jié):
- block沒有捕獲變量到block內(nèi)祟偷,那block直接存儲在
全局區(qū)
。 - 如果block捕獲變量
- 此時block被
強引用
打厘,則block存儲在堆區(qū)修肠,即堆區(qū)block
。 - 此時block被
弱引用(通過__weak修飾
)户盯,則block存儲在棧區(qū)嵌施,即棧區(qū)block
饲化。
- 此時block被
block底層分析
通過clang分析不同類型的block底層數(shù)據(jù)結(jié)構(gòu),怎么捕獲外部變量艰管。
數(shù)據(jù)結(jié)構(gòu)
創(chuàng)建一個block
int a = 10;
void(^testBlock)(void) = ^{
NSLog(@"%d",a);
};
NSLog(@"%@",testBlock);
通過 clang -rewrite-objc main.m -o mian.cpp 生成main.cpp文件滓侍,查看編譯后的數(shù)據(jù)格式
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int a = 10;
void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_sg_hb1bwpx16m9dprh1bryw63940000gp_T_main_12e473_mi_1,testBlock);
}
return 0;
}
去除類型轉(zhuǎn)換的括號,簡化為
void(*testBlock)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a);
block的數(shù)據(jù)類型為__main_block_impl_0
牲芋,是一個結(jié)構(gòu)體
撩笆,同時可以說明block
是一個__main_block_impl_0
類型的對象
,這也是為什么block能夠%@打印的原因缸浦。
//block結(jié)構(gòu)體類型
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
//block代碼塊的描述
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方法的結(jié)構(gòu)體類型
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_sg_hb1bwpx16m9dprh1bryw63940000gp_T_main_12e473_mi_0,a);
}
//block代碼塊的結(jié)構(gòu)體類型
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
總結(jié):
block
的本質(zhì)是一個結(jié)構(gòu)體
夕冲,其內(nèi)部包含block基本的數(shù)據(jù)結(jié)構(gòu)
、代碼塊的結(jié)構(gòu)體
裂逐、方法訪問的外部變量
歹鱼。因為block數(shù)據(jù)結(jié)構(gòu)內(nèi)部有isa
指針,所以block本質(zhì)也是對象卜高。由于block函數(shù)沒有名稱弥姻,也被稱為匿名函數(shù)
。
1掺涛、block為什么需要調(diào)用
block
底層的類型為__main_block_impl_0
結(jié)構(gòu)體庭敦,通過其同名構(gòu)造函數(shù)創(chuàng)建,第一個傳入的block的內(nèi)部實現(xiàn)代碼塊
薪缆,即__main_block_func_0
秧廉,用fp
表示,然后賦值給impl
的FuncPtr
屬性拣帽,然后在main中進行了調(diào)用疼电。如果不調(diào)用,block內(nèi)部實現(xiàn)的代碼塊將無法執(zhí)行减拭,可以總結(jié)為以下兩點
-
函數(shù)聲明
:即block
內(nèi)部實現(xiàn)聲明成了一個函數(shù)__main_block_func_0
-
執(zhí)行具體的函數(shù)實現(xiàn)
:通過調(diào)用block
的FuncPtr
指針蔽豺,調(diào)用block
的代碼塊執(zhí)行
2、block是如何獲取外界變量的
block捕獲外部值類型
捕獲外部int是不能進行修改
int a = 10;
void(^testBlock)(void) = ^{
NSLog(@"%d",a);
};
NSLog(@"%@",testBlock);
編譯后代碼分析
int a = 10;
// 初始化block結(jié)構(gòu)體 傳入a
void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
//去除類型轉(zhuǎn)換簡化后 void(*testBlock)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a);
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__main_block_impl_0
初始化時拧粪,使用初始化列表來初始化字段設(shè)置結(jié)構(gòu)體捕獲的變量a值為10修陡。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy 值拷貝,即 a = 10既们,此時的a與傳入的__cself的a并不是同一個
printf("CJL - %d", a);
}
執(zhí)行block代碼塊濒析,其內(nèi)部重新初始變量a賦值正什。
總結(jié):
block捕獲外界變量時啥纸,在內(nèi)部會自動生成同一個屬性來保存,代碼塊內(nèi)部會copy該屬性執(zhí)行婴氮。
block捕獲外部對象
初始化一個數(shù)組斯棒,block捕獲修改數(shù)組成功
編譯后代碼分析
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSMutableArray *array;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *_array, int flags=0) : array(_array) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSMutableArray *array = __cself->array; // bound by copy
((void (*)(id, SEL, ObjectType _Nonnull, NSUInteger))(void *)objc_msgSend)((id)array, sel_registerName("setObject:atIndexedSubscript:"), (id _Nonnull)((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 1), (NSUInteger)0);
}
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSMutableArray * array = ((NSMutableArray *(*)(id, SEL, ObjectType _Nonnull, ...))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("initWithObjects:"), (id _Nonnull)((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 8), ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 9), ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 10), __null);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_sg_hb1bwpx16m9dprh1bryw63940000gp_T_main_5ba1be_mi_0,array);
void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344));
((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_sg_hb1bwpx16m9dprh1bryw63940000gp_T_main_5ba1be_mi_1,array);
}
return 0;
}
-
__main_block_impl_0
初始化是傳入數(shù)組盾致,賦值給屬性array
。 -
__main_block_func_0
代碼塊內(nèi)操作捕獲數(shù)組進行修改荣暮。
總結(jié):
- 捕獲的外界對象(強引用)庭惜,操作原對象。
__block的原理
對變量a
進行__block
修飾穗酥,然后在block中對a進行++操作
__block int a = 10;
void(^testBlock)(void) = ^{
a++;
NSLog(@"%d",a);
};
testBlock();
NSLog(@"%@",testBlock);
通過底層編譯如下
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a)++;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_sg_hb1bwpx16m9dprh1bryw63940000gp_T_main_3f1057_mi_0,(a->__forwarding->a));
}
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_sg_hb1bwpx16m9dprh1bryw63940000gp_T_main_3f1057_mi_1,testBlock);
}
return 0;
}
- 在main函數(shù)內(nèi)部先初始化一個
__Block_byref_a_0
結(jié)構(gòu)體护赊,__Block_byref_a_0
初始時,將&a
強轉(zhuǎn)類型為__Block_byref_a_0
結(jié)構(gòu)體砾跃,再賦值到__forwarding
骏啰。 - 在
__main_block_impl_0
初始化時傳入__Block_byref_a_0(&a)
,賦值給__Block_byref_a_0 *a
屬性(強引用)
。 - 在
__main_block_func_0
代碼塊內(nèi)抽高,通過__cself->a
讀取捕獲的參數(shù)對象判耕,再讀取屬性結(jié)構(gòu)體內(nèi)__forwarding(&a)
,取值進行處理。實則是操作捕獲同一個內(nèi)存空間
翘骂。
總結(jié):
- 外界變量會生成
__Block_byref_a_0
結(jié)構(gòu)體 - 結(jié)構(gòu)體用來保存
原始變量的指針和值
- 將變量生成的結(jié)構(gòu)體對象的指針地址傳遞給代碼塊壁熄,代碼塊內(nèi)操作的就是原變量地址。