本文屬筆記性質,主要針對本身理解不太透徹的地方進行記錄。ios
推薦系統直接學習小碼哥iOS底層原理班---MJ老師的課確實不錯,強推一波。c++
只有在末尾執行
()
纔會調用bash
^{
NSLog(@"this is a block!");
NSLog(@"this is a block!");
NSLog(@"this is a block!");
NSLog(@"this is a block!");
};
複製代碼
// 不帶參數的block。^(){能夠簡化成^{
void (^block)() = ^(){
};
block();
// 帶參數的block
void (^block)(int, int) = ^(int a , int b){
NSLog(@"a=%d,b=%d",a,b);
};
block(20, 10);
複製代碼
對於void (^block)(int, int)
的含義是返回值 (^名稱)(參數1, 參數2)
函數
封裝了函數調用以及調用環境的OC對象學習
int age = 20;
void (^block)(int, int) = ^(int a , int b){
NSLog(@"this is a block! -- %d", age);
};
block(10, 10);
複製代碼
block最終將會被轉化成以下格式的結構體(每一個block的細節不一樣)ui
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc; //block描述信息(大小等)
int age; //封裝了函數調用所需環境(內部定義了一個age變量)
//c++的構造函數 age(_age)表示_age將會自動賦值給age
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
int age = __cself->age; // 將block當初捕獲的變量,賦值給執行函數
//函數調用
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_0, age);
}
struct __block_impl {
void *isa; //代表block也屬於OC對象
int Flags;
int Reserved;
void *FuncPtr;
};
複製代碼
其中函數調用會被單獨封裝成__main_block_func_0
方法。在block定義時,傳入block結構體。this
void (*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
複製代碼
而在block結構體中,會被賦值給impl.FuncPtr = fp;
,將函數地址存儲在block內部。spa
最終,在調用block時,獲取FuncPtr
,傳入參數執行調用。3d
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
複製代碼
構建一個block結構體指針
void (*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
/**
去掉強轉的語法以後
將函數地址,block描述(大小等)信息,須要被捕獲的變量。
傳入構建block的方法`__main_block_impl_0`中進行構建
最後將`__main_block_impl_0`block結構體返回,並將其持有
**/
void (*block)(int, int) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, age));
複製代碼
調用block結構體
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
/**
去掉強轉的語法以後
從結構體中取出函數地址,傳入參數並調用。
傳入blcok是由於每個block對象內部所捕獲的變量不一樣
**/
block->FuncPtr(block, 10, 10);
複製代碼
爲了保證block內部可以正常訪問外部的變量,block有個變量捕獲機制
c語言中的局部變量,默認都爲auto變量。因此auto代指局部變量
因爲auto變量的生命超出做用域就會被銷燬。爲保證block可以正常執行,auto變量在被block捕獲時,會將
值
傳遞給block的構造函數。
int main(int argc, const char * argv[]) {
@autoreleasepool {
auto int a = 10;
void (^block)(void) = ^{
NSLog(@"age is %d", a); //age is 10
};
a = 20;
block();
}
return 0;
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc; //block信息(大小等)
int age; //封裝了函數調用所需環境(內部定義了一個age變量)
//c++的構造函數 age(_age)表示_age將會自動賦值給age
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
int age = __cself->age; // 將block當初捕獲的變量,賦值給執行函數
//函數調用
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_0, age);
}
// main中構建block。將a的值傳遞給構造函數
void (*block)() = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, a));
複製代碼
因爲靜態變量的生命常駐於內存,但使用僅限於做用域內部。因此靜態變量在被block捕獲時,只要將指向變量值的指針(地址)傳遞給構造函數,即能保證block正常執行。
int main(int argc, const char * argv[]) {
@autoreleasepool {
static int b = 10;
void (^block)(void) = ^{
NSLog(@"height is %d", b); //height is 20
};
b = 20;
block();
}
return 0;
}
struct __test_block_impl_0 {
struct __block_impl impl;
struct __test_block_desc_0* Desc;
int *b; //b爲指針類型
__test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int *_b, int flags=0) : b(_b) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
int *b = __cself->b; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_fd2a14_mi_0, (*b));
}
// main中構建block.將b的指針傳遞給構造函數
void (*block)() = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, &b));
複製代碼
因爲全局變量的生命常駐於內存,而且使用不受做用域限制。因此全局變量並不須要被捕獲,在執行block的函數調用時,直接使用全局變量,即能保證block正常執行。
int age_ = 10;
static int height_ = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^block)(void) = ^{
NSLog(@"age_ is %d ,height_ is %d", age_,height_);
};
age_ = 20;
height_ = 20;
block();
}
return 0;
}
int age_ = 10;
static int height_ = 10;
// block結構體內部,也並未聲明對應的變量
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
// 執行函數中,直接使用全局變量
NSLog((NSString *)&__NSConstantStringImpl__var_folders_tz_hcmmb5t57v1cr81ydm6s5s140000gn_T_main_6f323a_mi_0, age_,height_);
}
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)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); //在執行block構造函數時,並未將全局變量傳遞進去
age_ = 20;
height_ = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
複製代碼
經過class方法,也能夠證實block的本質是OC對象
void test()
{
void (^block)(void) = ^{
NSLog(@"Hello");
};
NSLog(@"%@", [block class]);//__NSGlobalBlock__ (其餘類型的block爲__NSStackBlock__或__NSMallocBlock__)
NSLog(@"%@", [[block class] superclass]);//__NSGlobalBlock
NSLog(@"%@", [[[block class] superclass] superclass]);//NSBlock
NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);//NSObject
}
複製代碼
在block結構體中,isa會被指定成
_NSConcreteXXXBlock
的值。這個值即是block的類型。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSObject *obj;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, int flags=0) : obj(_obj) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
block有3種類型,能夠經過調用class方法或者isa指針查看具體類型,最終都是繼承自NSBlock類型
NSGlobalBlock ( _NSConcreteGlobalBlock )
NSStackBlock ( _NSConcreteStackBlock )
NSMallocBlock ( _NSConcreteMallocBlock )
從上到下爲:低地址 --》高地址 非ARC下的block來源
在非ARC下
絕大部分
block
默認都是做爲__NSStackBlock__
存在於棧上(沒有訪問auto變量的block會存在於全局區)。而
__NSStackBlock__
在超出做用域後,block結構體有可能會被修改污染。這時須要將
__NSStackBlock__
進行copy
將其轉移到堆中進行管理。以後
__NSStackBlock__
將轉變爲__NSMallocBlock__
這也是爲何block做爲屬性都會聲明爲copy(不過ARC下聲明爲strong也會自動copy,文檔上說是爲了語義)
對於不一樣類型的block,調用copy會有不一樣操做
在ARC環境下,編譯器會根據狀況自動將棧上的block複製到堆上,好比如下狀況
MJBlock myblock()
{
return ^{
NSLog(@"---------");
};
}
複製代碼
MJBlock block = ^{
NSLog(@"---------%d", age);
};
複製代碼
[@[] enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
}];
複製代碼
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
複製代碼
(這裏都說ARC環境下)
在block捕獲對象類型的auto變量時。
__main_block_desc_0
結構體內部會多出__main_block_copy_0
和__main_block_dispose_0
函數,在block移動到堆空間時堆對象進行適當的return和release。
先貼一份cpp代碼(auto變量)
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [NSObject new]; //OC對象
void (^block)(void) = ^{
NSLog(@"obj is %@", obj); //捕獲
};
obj = nil;
block();
}
return 0;
}
//block結構體
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//若是外部爲__weak內部也會爲__weak
NSObject * __strong obj; //對於auto變量,結構體中保存變量與本來相同。
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, int flags=0) : obj(_obj) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSObject *obj = __cself->obj; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_tz_hcmmb5t57v1cr81ydm6s5s140000gn_T_main_d73acf_mi_0, obj);
}
//當block從棧移動到堆中時,執行此方法。
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
//會根據auto變量的修飾符(__strong、__weak、__unsafe_unretained)作出相應的操做,造成強引用(retain)或者弱引用
_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
//當block從堆中移除(釋放)時,執行此方法。
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
//release引用的變量
_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
//block描述。比基本類型多了兩個變量。就是上面兩個方法
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};
// main函數
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
//在構造block時,將OC對象傳入
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
複製代碼
基本也符合上面的規律,以局部變量爲例。
只是對於對象而言,傳遞的都是指針。 並且爲了保證OC對象在block內部正常訪問,會被強引用以延長對象生命週期。
若是block是在棧上,將不會對auto變量產生強引用
會執行內部的
_Block_object_assign
函數,結構體內所捕獲的auto對象的類型(__strong/__weak)決定如何對其進行引用。
會執行內部的
_Block_object_dispose
函數,將結構體內所捕獲的auto對象進行release。
編譯器會將__block變量包裝成一個對象(
__Block_byref_age_0
),被聲明的值做爲對象的屬性存在。
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
MJBlock block = ^{
__strong int myage = age;
age = 20;
NSLog(@"age is %d", age);
};
block();
}
return 0;
}
//__block對象結構體
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
//block結構體
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSObject *p;
//再也不是int age;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_p, __Block_byref_age_0 *_age, int flags=0) : p(_p), age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// block執行函數
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref
//從__Block_byref_age_0結構體中得到age變量,而且修改
(age->__forwarding->age) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_e2457b_mi_0);
}
//c++ main函數
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
//__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};
//等價於
__Block_byref_age_0 age = {0,
&age,
0,
sizeof(__Block_byref_age_0),
10};
//構建block結構體
MJBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, p, (__Block_byref_age_0 *)&age, 570425344));
//等價於
MJBlock block = &__main_block_impl_0(__main_block_func_0,
&__main_block_desc_0_DATA,
p,
&age,
570425344);
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
複製代碼
實際上都是對結構體中的age指針進行操做,而不是結構體age。
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
age = 50;
}
return 0;
}
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
(age.__forwarding->age) = 50;
}
return 0;
}
複製代碼
__forwarding
指向__block
所包裝的對象實體,以確保使用時的正確性。
__forwarding
所指實體爲棧上的__block
包裝對象。__block
包裝對象在堆中也會被複制一份。而兩者的__forwarding
指針都指向堆中的__block
包裝對象。OC對象的強弱引用不會體如今block結構體中(都是strong),而是體如今
__Block_byref
結構體中。
int main(int argc, const char * argv[]) {
@autoreleasepool {
MJPerson *person = [[MJPerson alloc] init];
__block __weak JSPerson *weakPerson = person;
MJBlock block = ^{
NSLog(@"%p", weakPerson);
};
block();
}
return 0;
}
struct __Block_byref_weakPerson_0 {
void *__isa; // 8
__Block_byref_weakPerson_0 *__forwarding; // 8
int __flags; // 4
int __size; // 4
void (*__Block_byref_id_object_copy)(void*, void*); // 當block被移動到堆,會對__Block_byref對象進行return(須要注意MRCblock進入堆中時不會retain該變量)
void (*__Block_byref_id_object_dispose)(void*); // 當block從堆中移除,會對__Block_byref對象進行release
MJPerson *__weak weakPerson; //__block包裝的結構體中實際爲weak引用。
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_weakPerson_0 *weakPerson; // // 依舊是strong引用
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_weakPerson_0 *_weakPerson, int flags=0) : weakPerson(_weakPerson->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
三個已知的內存地址段
NSLog(@"數據段:age %p", &age);
NSLog(@"棧:a %p", &a);
NSLog(@"堆:obj %p", [[NSObject alloc] init]);
複製代碼