Block 是一種帶有自動變數值的匿名函式。
Block 在 iOS 日常開發中會頻繁使用到,使用起來也十分方便,而它的實現原理和機制很多小夥伴卻一無所知。 Block 是一種帶有自動變數值的匿名函式,它能夠自動捕獲函式內使用到的引數,本文將從細節分析 Block 的實現原理。
一、Block 的實現
在探尋 Block 實現原理中,命令列工具Clang
是非常實用的,它可以將其轉換成 C++ 原始碼,方便我們瞭解其中的實現原理。
clang -rewrite-objc main.m
複製程式碼
我們可以利用上面的命令,嘗試將下面這段程式碼轉換成 C++ 原始碼,進而分析 Block 的具體實現:
int main(int argc, const char * argv[]) {
@autoreleasepool {
int tempVar = 1;
void (^blk)(void) = ^() {
printf("Block var:%d\n", tempVar);
};
blk();
}
return 0;
}
複製程式碼
轉換並剔除多餘程式碼後如下:
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;
int tempVar;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _tempVar, int flags=0) : tempVar(_tempVar) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int tempVar = __cself->tempVar; // bound by copy
printf("Block var:%d\n", tempVar);
}
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;
int tempVar = 1;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, tempVar));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
return 0;
}
複製程式碼
轉換後得到了一大串程式碼,接下來我們一一分析這段程式碼的實際意義。
第一部分是__block_impl
,它是Block
實現的最底層的結構體:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
複製程式碼
- isa:表明具有它和物件一樣特性。
- Flag:為狀態標誌位。
- Reserved:升級預留記憶體大小。
- FuncPtr:函式指標。
第二部分__main_block_desc_0
是一個管理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)};
複製程式碼
- Reserved:升級預留記憶體大小。
- Block_size:Block 的大小。
第三部分為Block
實現結構體:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int tempVar;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _tempVar, int flags=0) : tempVar(_tempVar) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製程式碼
該結構體的命名邏輯為__函式名_block_impl_函式內順序
,接下來檢視結構體成員:
- impl:為
__block_impl
型別結構體,參考第一部分。 - Desc:為
__main_block_impl_0
結構體例項大小。 - tempVar:捕獲的自動變數值。
- __main_block_impl_0:為
__main_block_impl_0
結構體的建構函式。
第四部分為Block
的函式指標指向的函式__main_block_func_0
:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int tempVar = __cself->tempVar; // bound by copy
printf("Block var:%d\n", tempVar);
}
複製程式碼
__main_block_impl_0
結構體中將捕獲的自動變數值作為成員變數,呼叫時先獲取結構體成員變數的值,然後複製使用。
第五部分為main
函式轉換後原始碼:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int tempVar = 1;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, tempVar));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
return 0;
}
複製程式碼
除了一個區域性變數tempVar
之外,另外 2 行程式碼分別是Block
的的初始化部分和呼叫部分。去除部分型別強轉程式碼後如下:
void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
(blk->FuncPtr)(blk);
複製程式碼
第一行程式碼是將前面宣告的__main_block_func_0
和&__main_block_desc_0_DATA
傳入建構函式,得到blk
結構體例項。
第二行程式碼則是呼叫blk
的函式指標。
二、捕獲變數值
2.1 自動變數
在 Block 中使用外部的區域性變數時,會自動捕獲該變數並且成為 Block 結構體的成員變數,以便在 Block 內部訪問該變數。除此之外,有其他幾種方式可以訪問外部變數,下面是變數型別和對應的作用域:
- 自動變數:捕獲至 Block 內。
- 靜態變數:作用域內可用。
- 全域性變數:整個程式可用。
- 靜態全域性變數:當前檔案可用。
通過將下面的程式碼轉換至 C++ 程式碼,分析 Block 中各種型別變數的訪問方式:
static char globalVar[] = {"globalVar"};
static char globalStaticVar[] = {"globalStaticVar"};
void catchVar() {
int var1 = 1;
int var2 = 2;
static char staticVar[] = {"staticVar"};
void (^blk)(void) = ^{
printf("%d\n", var1);
printf("%s\n", staticVar);
printf("%s\n", globalVar);
printf("%s\n", globalStaticVar);
};
blk();
}
複製程式碼
上面的程式碼分別使用了區域性變數、靜態變數、全域性變數和靜態全域性變數,其轉換後的程式碼:
struct __catchVar_block_impl_0 {
struct __block_impl impl;
struct __catchVar_block_desc_0* Desc;
int var1; // 區域性變數
char (*staticVar)[10]; // 靜態變數
__catchVar_block_impl_0(void *fp, struct __catchVar_block_desc_0 *desc, int _var1, char (*_staticVar)[10], int flags=0) : var1(_var1), staticVar(_staticVar) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __catchVar_block_func_0(struct __catchVar_block_impl_0 *__cself) {
int var1 = __cself->var1; // bound by copy
char (*staticVar)[10] = __cself->staticVar; // bound by copy
printf("%d\n", var1);
printf("%s\n", (*staticVar));
printf("%s\n", globalVar);
printf("%s\n", globalStaticVar);
}
static struct __catchVar_block_desc_0 {
size_t reserved;
size_t Block_size;
} __catchVar_block_desc_0_DATA = { 0, sizeof(struct __catchVar_block_impl_0)};
void catchVar() {
int var1 = 1;
int var2 = 2;
static char staticVar[] = {"staticVar"};
void (*blk)(void) = ((void (*)())&__catchVar_block_impl_0((void *)__catchVar_block_func_0, &__catchVar_block_desc_0_DATA, var1, &staticVar));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
複製程式碼
通過__catchVar_block_func_0
函式和 Block 建構函式可以知道各種變數的訪問方式:
- 全域性變數 和 靜態全域性變數 因其作用域內都可以直接訪問。
- 靜態變數 成為成員變數,但是從建構函式傳入的是一個記憶體地址,然後通過地址訪問。
- 區域性變數 成為成員變數,從建構函式直接傳入變數的值並賦值給成員變數,然後通過成員變數訪問。
2.2 物件
下面的程式碼中,Block 內使用了外部的一個物件,這種情況下 Block 內部是如何捕獲該物件的呢?
void catchObject() {
id obj = [NSObject new];
void (^blk)(void) = ^{
printf("%d\n", [obj hash]);
};
blk();
}
複製程式碼
我們將上面的程式碼轉換成 C++ 程式碼後分析其中實現原理:
struct __catchObject_block_impl_0 {
struct __block_impl impl;
struct __catchObject_block_desc_0* Desc;
__strong id obj;
__catchObject_block_impl_0(void *fp, struct __catchObject_block_desc_0 *desc, __strong id _obj, int flags=0) : obj(_obj) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __catchObject_block_func_0(struct __catchObject_block_impl_0 *__cself) {
__strong id obj = __cself->obj; // bound by copy
printf("%d\n", ((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)obj, sel_registerName("hash")));
}
static void __catchObject_block_copy_0(struct __catchObject_block_impl_0*dst, struct __catchObject_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __catchObject_block_dispose_0(struct __catchObject_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __catchObject_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __catchObject_block_impl_0*, struct __catchObject_block_impl_0*);
void (*dispose)(struct __catchObject_block_impl_0*);
} __catchObject_block_desc_0_DATA = { 0, sizeof(struct __catchObject_block_impl_0), __catchObject_block_copy_0, __catchObject_block_dispose_0};
void catchObject() {
id obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
void (*blk)(void) = ((void (*)())&__catchObject_block_impl_0((void *)__catchObject_block_func_0, &__catchObject_block_desc_0_DATA, obj, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
複製程式碼
首先來看catchObject()
函式,在構建 blk 時,傳入了物件obj
和十進位制標誌位570425344
。
void (*blk)(void) = ((void (*)())&__catchObject_block_impl_0((void *)__catchObject_block_func_0, &__catchObject_block_desc_0_DATA, obj, 570425344));
複製程式碼
Block 結構體中成員變數obj
為__strong
修飾符,傳入的物件obj
直接賦值給成員變數,說明是直接使用原物件並且使引用計數 +1 。
其次是原始碼中新增了兩個方法,分別是__catchObject_block_copy_0
和__catchObject_block_dispose_0
,而這兩個方法又分別呼叫了_Block_object_assign
和_Block_object_dispose
方法,這兩個方法是用來管理 Block 中變數儲存的,後面會進行分析。
static void __catchObject_block_copy_0(struct __catchObject_block_impl_0*dst, struct __catchObject_block_impl_0*src) {
_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __catchObject_block_dispose_0(struct __catchObject_block_impl_0*src) {
_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
複製程式碼
2.3 Block
將一個 Block 作為另外一個 Block 內的引數來使用,接下來分析這種情況下 Block 的實現。
void catchBlock() {
void (^block)(void) = ^{};
void (^blk)(void) = ^{
block;
};
blk();
}
複製程式碼
轉換後程式碼如下:
struct __catchBlock_block_impl_0 {
struct __block_impl impl;
struct __catchBlock_block_desc_0* Desc;
__catchBlock_block_impl_0(void *fp, struct __catchBlock_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __catchBlock_block_func_0(struct __catchBlock_block_impl_0 *__cself) {
}
static struct __catchBlock_block_desc_0 {
size_t reserved;
size_t Block_size;
} __catchBlock_block_desc_0_DATA = { 0, sizeof(struct __catchBlock_block_impl_0)};
struct __catchBlock_block_impl_1 {
struct __block_impl impl;
struct __catchBlock_block_desc_1* Desc;
struct __block_impl *block;
__catchBlock_block_impl_1(void *fp, struct __catchBlock_block_desc_1 *desc, void *_block, int flags=0) : block((struct __block_impl *)_block) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __catchBlock_block_func_1(struct __catchBlock_block_impl_1 *__cself) {
void (*block)() = (void (*)())__cself->block; // bound by copy
block;
}
static void __catchBlock_block_copy_1(struct __catchBlock_block_impl_1*dst, struct __catchBlock_block_impl_1*src) {_Block_object_assign((void*)&dst->block, (void*)src->block, 7/*BLOCK_FIELD_IS_BLOCK*/);}
static void __catchBlock_block_dispose_1(struct __catchBlock_block_impl_1*src) {_Block_object_dispose((void*)src->block, 7/*BLOCK_FIELD_IS_BLOCK*/);}
static struct __catchBlock_block_desc_1 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __catchBlock_block_impl_1*, struct __catchBlock_block_impl_1*);
void (*dispose)(struct __catchBlock_block_impl_1*);
} __catchBlock_block_desc_1_DATA = { 0, sizeof(struct __catchBlock_block_impl_1), __catchBlock_block_copy_1, __catchBlock_block_dispose_1};
void catchBlock() {
void (*block)(void) = ((void (*)())&__catchBlock_block_impl_0((void *)__catchBlock_block_func_0, &__catchBlock_block_desc_0_DATA));
void (*blk)(void) = ((void (*)())&__catchBlock_block_impl_1((void *)__catchBlock_block_func_1, &__catchBlock_block_desc_1_DATA, (void *)block, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
複製程式碼
同樣的先看catchBlock()
函式,其中 Block 建構函式中傳入了block
和標誌位570425344
,賦值給在__catchBlock_block_impl_1
結構體中的一個__block_impl
型別的結構體成員變數block
。
void (*blk)(void) = ((void (*)())&__catchBlock_block_impl_1((void *)__catchBlock_block_func_1, &__catchBlock_block_desc_1_DATA, (void *)block, 570425344));
複製程式碼
在這段程式碼中同樣有__catchObject_block_copy_0
和__catchObject_block_dispose_0
兩個方法,不同的是呼叫_Block_object_assign
和_Block_object_dispose
方法時最後一個入參為7 /*BLOCK_FIELD_IS_BLOCK*/
,之前的捕獲物件時傳入的引數是3 /*BLOCK_FIELD_IS_OBJECT*/
。
static void __catchBlock_block_copy_1(struct __catchBlock_block_impl_1*dst, struct __catchBlock_block_impl_1*src) {
_Block_object_assign((void*)&dst->block, (void*)src->block, 7/*BLOCK_FIELD_IS_BLOCK*/);
}
static void __catchBlock_block_dispose_1(struct __catchBlock_block_impl_1*src) {
_Block_object_dispose((void*)src->block, 7/*BLOCK_FIELD_IS_BLOCK*/);
}
複製程式碼
2.4 __block 修飾的變數
Block 將外部的變數捕獲後,可以在內部訪問外部的變數,但是還不能修改外部變數的值(靜態變數、全域性變數和靜態全域性變數可以直接修改)。這個時候需要使用 __block 修飾符,使得在 Block 內部也可以修改 __block 修飾符修飾的變數。
下面通過轉換原始碼來分析實現原理:
void catchBlockVar() {
__block int blockVar = 1;
void (^blk)(void) = ^{
blockVar = 2;
printf("%d\n", blockVar);
};
blk();
}
複製程式碼
轉換後:
struct __Block_byref_blockVar_0 {
void *__isa;
__Block_byref_blockVar_0 *__forwarding;
int __flags;
int __size;
int blockVar;
};
struct __catchBlockVar_block_impl_0 {
struct __block_impl impl;
struct __catchBlockVar_block_desc_0* Desc;
__Block_byref_blockVar_0 *blockVar; // by ref
__catchBlockVar_block_impl_0(void *fp, struct __catchBlockVar_block_desc_0 *desc, __Block_byref_blockVar_0 *_blockVar, int flags=0) : blockVar(_blockVar->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __catchBlockVar_block_func_0(struct __catchBlockVar_block_impl_0 *__cself) {
__Block_byref_blockVar_0 *blockVar = __cself->blockVar; // bound by ref
(blockVar->__forwarding->blockVar) = 2;
printf("%d\n", (blockVar->__forwarding->blockVar));
}
static void __catchBlockVar_block_copy_0(struct __catchBlockVar_block_impl_0*dst, struct __catchBlockVar_block_impl_0*src) {_Block_object_assign((void*)&dst->blockVar, (void*)src->blockVar, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __catchBlockVar_block_dispose_0(struct __catchBlockVar_block_impl_0*src) {_Block_object_dispose((void*)src->blockVar, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __catchBlockVar_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __catchBlockVar_block_impl_0*, struct __catchBlockVar_block_impl_0*);
void (*dispose)(struct __catchBlockVar_block_impl_0*);
} __catchBlockVar_block_desc_0_DATA = { 0, sizeof(struct __catchBlockVar_block_impl_0), __catchBlockVar_block_copy_0, __catchBlockVar_block_dispose_0};
void catchBlockVar() {
__attribute__((__blocks__(byref))) __Block_byref_blockVar_0 blockVar = {(void*)0,(__Block_byref_blockVar_0 *)&blockVar, 0, sizeof(__Block_byref_blockVar_0), 1};
void (*blk)(void) = ((void (*)())&__catchBlockVar_block_impl_0((void *)__catchBlockVar_block_func_0, &__catchBlockVar_block_desc_0_DATA, (__Block_byref_blockVar_0 *)&blockVar, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
複製程式碼
和其他型別 Block 最大的不同就是多了一個結構體__Block_byref_blockVar_0
,經過__block
修飾符修飾的變數都會自動生成一個這樣的結構體。
struct __Block_byref_blockVar_0 {
void *__isa;
__Block_byref_blockVar_0 *__forwarding;
int __flags;
int __size;
int blockVar;
};
複製程式碼
- __isa:具體和物件一樣的特性。
- __forwarding:在棧區且未被複制時指向自己,被複制到堆區後指向堆區的結構體。
- __flags:標誌位。
- __size:結構體佔用記憶體大小。
- blockVar:原變數值。
在catchBlockVar
方法轉換後,之前的int
型別變數blockVar
變成__Block_byref_blockVar_0
型別結構體,然後將此結構體地址傳入 Block 的建構函式中,因此 Block 自動生成的成員變數也為__Block_byref_blockVar_0
型別。
void catchBlockVar() {
__Block_byref_blockVar_0 blockVar = {(void*)0,(__Block_byref_blockVar_0 *)&blockVar, 0, sizeof(__Block_byref_blockVar_0), 1};
void (*blk)(void) = (&__catchBlockVar_block_impl_0((void *)__catchBlockVar_block_func_0, &__catchBlockVar_block_desc_0_DATA, (__Block_byref_blockVar_0 *)&blockVar, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
複製程式碼
在被 Block 被呼叫時,通過下面的程式碼可以發現,訪問blockVar
並不直接訪問,而是通過其__forwarding
來訪問其原變數的值。這樣可以在它被複制到堆區時,訪問堆區中的結構體。為什麼要優先訪問堆區的結構體?棧區的物件在超出其作用域後會被釋放,如果希望在作用域外使用就需要複製到堆區中。
static void __catchBlockVar_block_func_0(struct __catchBlockVar_block_impl_0 *__cself) {
__Block_byref_blockVar_0 *blockVar = __cself->blockVar; // bound by ref
(blockVar->__forwarding->blockVar) = 2;
printf("%d\n", (blockVar->__forwarding->blockVar));
}
複製程式碼
在這段程式碼中的__catchObject_block_copy_0
和__catchObject_block_dispose_0
兩個方法中傳入的引數是8 /*BLOCK_FIELD_IS_BYREF*/
。
static void __catchBlockVar_block_copy_0(struct __catchBlockVar_block_impl_0*dst, struct __catchBlockVar_block_impl_0*src) {
_Block_object_assign((void*)&dst->blockVar, (void*)src->blockVar, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __catchBlockVar_block_dispose_0(struct __catchBlockVar_block_impl_0*src) {
_Block_object_dispose((void*)src->blockVar, 8/*BLOCK_FIELD_IS_BYREF*/);
}
複製程式碼
2.5 __block 修飾的物件
在 Block 使用__block
修飾的變數和__block
修飾的物件,其中內部實現是有一些細微區別的,通過下面的程式碼來進行分析。
void catchBlockObject() {
__block NSObject *obj = [[NSObject alloc] init];
blk_t block = ^ {
obj;
};
}
複製程式碼
轉換後:
struct __Block_byref_obj_1 {
void *__isa;
__Block_byref_obj_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *obj;
};
struct __catchBlockObject_block_impl_0 {
struct __block_impl impl;
struct __catchBlockObject_block_desc_0* Desc;
__Block_byref_obj_1 *obj; // by ref
__catchBlockObject_block_impl_0(void *fp, struct __catchBlockObject_block_desc_0 *desc, __Block_byref_obj_1 *_obj, int flags=0) : obj(_obj->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __catchBlockObject_block_func_0(struct __catchBlockObject_block_impl_0 *__cself) {
__Block_byref_obj_1 *obj = __cself->obj; // bound by ref
(obj->__forwarding->obj);
}
static void __catchBlockObject_block_copy_0(struct __catchBlockObject_block_impl_0*dst, struct __catchBlockObject_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __catchBlockObject_block_dispose_0(struct __catchBlockObject_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __catchBlockObject_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __catchBlockObject_block_impl_0*, struct __catchBlockObject_block_impl_0*);
void (*dispose)(struct __catchBlockObject_block_impl_0*);
} __catchBlockObject_block_desc_0_DATA = { 0, sizeof(struct __catchBlockObject_block_impl_0), __catchBlockObject_block_copy_0, __catchBlockObject_block_dispose_0};
void catchBlockObject() {
__attribute__((__blocks__(byref))) __Block_byref_obj_1 obj = {(void*)0,(__Block_byref_obj_1 *)&obj, 33554432, sizeof(__Block_byref_obj_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
blk_t block = ((void (*)())&__catchBlockObject_block_impl_0((void *)__catchBlockObject_block_func_0, &__catchBlockObject_block_desc_0_DATA, (__Block_byref_obj_1 *)&obj, 570425344));
}
複製程式碼
在宣告的結構體__Block_byref_obj_1
中,和之前不一樣的是多了__Block_byref_id_object_copy
和__Block_byref_id_object_dispose
兩個管理記憶體的方法。
struct __Block_byref_obj_1 {
void *__isa;
__Block_byref_obj_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *obj;
};
複製程式碼
再看到catchBlockObject()
函式中,被__block
修飾符修飾的obj
物件轉換成__Block_byref_obj_1
型別結構體。其中copy
和dispose
兩個方法傳入的__Block_byref_id_object_copy_131
、__Block_byref_id_object_dispose_131
兩個靜態方法。
void catchBlockObject() {
__Block_byref_obj_1 obj = {(void*)0,(__Block_byref_obj_1 *)&obj, 33554432, sizeof(__Block_byref_obj_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
blk_t block = (&__catchBlockObject_block_impl_0((void *)__catchBlockObject_block_func_0, &__catchBlockObject_block_desc_0_DATA, (__Block_byref_obj_1 *)&obj, 570425344));
}
複製程式碼
靜態方法如下,在最後一個引數傳入的是131
,其實就是3 + 128
。
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
複製程式碼
通過下面這個列舉可以明白,為什麼捕獲不同型別的變數,需要不同的入參。根據入參不同,對捕獲的變數複製和釋放的操作都是不同的。131
則表示BLOCK_FIELD_IS_BYREF
|BLOCK_BYREF_CALLER
。
// Runtime support functions used by compiler when generating copy/dispose helpers
// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
// see function implementation for a more complete description of these fields and combinations
BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ...
BLOCK_FIELD_IS_BLOCK = 7, // a block variable
BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers
BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.
};
複製程式碼
Block 本身的 Copy / Dispose 方法入參還是8 /*BLOCK_FIELD_IS_BYREF*/
。
三、Block 的儲存域
Block 的儲存域分為 3 種,分別為_NSConcreteStackBlock
、_NSConcreteGlobalBlock
和_NSConcreteMallocBlock
。
- _NSConcreteStackBlock:棧區
- _NSConcreteGlobalBlock:資料區域(.data 區)
- _NSConcreteMallocBlock:堆區
3.1 _NSConcreteStackBlock
正常情況下,定義在類內部的 Block 在捕獲了自動變數的情況下都是在棧區,可以通過下面的程式碼列印出其型別。但是在實際使用中都會定義後都會賦值給一個變數,這會導致實際使用用這個 Block 的時候已經變成_NSConcreteMallocBlock
型別。
// block 使用了捕獲的變數 tempVar
int main(int argc, const char * argv[]) {
@autoreleasepool {
int tempVar = 1;
NSLog(@"Stack Block:%@\n", ^() {
printf("Stack Block! %d\n", tempVar);
});
}
return 0;
}
// printf:Stack Block:<__NSStackBlock__: 0x7ffeefbff4a0>
複製程式碼
3.2 _NSConcreteGlobalBlock
在定義全域性變數的區域定義的 Block 型別為_NSConcreteGlobalBlock
,另外還有一種情況就是定義在類內部的 Block 在沒有捕獲任何自動變數時,也是_NSConcreteGlobalBlock
型別。
// block 內未使用外部變數
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Global Block:%@\n", ^() {
printf("Global Block!\n");
});
}
return 0;
}
// printf:Global Block:<__NSGlobalBlock__: 0x1000021c8>
複製程式碼
3.3 _NSConcreteMallocBlock
因為 Block 捕獲的 __block 變數存在棧區時,超出其作用域後則被釋放。為了避免這種情況,Block 實現機制將 Block 從棧區複製到堆區,這樣即使超出其作用域,堆區的 Block 和 __block 變數依然存在。
在未被複制時,__block 變數__forwarding
指向自身,被複制後指向堆區中的 __block 變數,這種機制使其無論是在堆區還是棧區都可以正確訪問。
下面這些場景下編譯器會自動處理將 Block 拷貝到堆上:
- ARC 有效時 block 作為函式或方法的返回值會自動被拷貝到堆上
- Cocoa 框架中的方法名包含 usingBlock 等時
- GCD 的 API
- 將 block 賦值給 __Strong 修飾符 id 型別物件或 Block 型別的成員變數時
以下場景需要手動拷貝至堆上
- 手動呼叫 block 例項方法
- 將 block 作為方法中的引數時需要開發者手動拷貝
- 當將 block 放入陣列並作為返回值時需要手動拷貝
四、 __block 變數的儲存域
當 Block 從棧區被複制到堆區時,對應的__block
修飾符修飾的變數也相應地被複制到堆區。
在前面的內容我們分析到__block
修飾的變數會轉換成一個結構體,結構體中含有成員變數__forwarding
,複製到堆區後可以在 Block 變數超出其作用域使用,這個時候棧區結構體成員變數__forwarding
指向堆區的結構體(在未被複制時指向自身結構體)。
當多個 Block 使用同一個 __block 變數時,複製已經在堆上的 __block 變數引用計數會增加,當釋放時也是將減引用計數減至 0 後才廢棄該 __block 變數。
五、_Block_object_assign 和 _Block_object_dispose
在第二章節中介紹了捕獲不同型別的變數時,Block 呼叫_Block_object_assign
函式的入參flags
都不一樣,下面來看看具體實現。
5.1 _Block_object_assign
直接看實現原始碼:
void _Block_object_assign(void *destArg, const void *object, const int flags) {
const void **dest = (const void **)destArg;
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_OBJECT:
/*******
id object = ...;
[^{ object; } copy];
********/
_Block_retain_object(object);
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
/*******
// copy the onstack __block container to the heap
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__block ... x;
__weak __block ... x;
[^{ x; } copy];
********/
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
/*******
// copy the actual field held in the __block container
// Note this is MRC unretained __block only.
// ARC retained __block is handled by the copy helper directly.
__block id object;
__block void (^object)(void);
[^{ object; } copy];
********/
*dest = object;
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
/*******
// copy the actual field held in the __block container
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__weak __block id object;
__weak __block void (^object)(void);
[^{ object; } copy];
********/
*dest = object;
break;
default:
break;
}
}
複製程式碼
由上面程式碼可知_Block_object_assign
方法根據入參flags
做了不同的處理,下面一一分析不同入參的處理方式:
5.1.1 BLOCK_FIELD_IS_OBJECT:複製物件
// 預設_Block_retain_object 被賦值為 _Block_retain_object_default 即什麼都不做
_Block_retain_object(object);
// 指標指向原物件記憶體地址。
*dest = object;
複製程式碼
_Block_retain_object
方法在 _Block_use_RR2
被執行時才有實際意義。
void _Block_use_RR2(const Block_callbacks_RR *callbacks) {
_Block_retain_object = callbacks->retain;
_Block_release_object = callbacks->release;
_Block_destructInstance = callbacks->destructInstance;
}
複製程式碼
5.1.2 BLOCK_FIELD_IS_BLOCK:複製 Block
分析_Block_copy
的實現:
//複製或碰撞引用計數。如果確實要複製,請呼叫複製助手(如果存在)。
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return NULL;
aBlock = (struct Block_layout *)arg;
// 已複製 則增加引用計數
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
// 如果是全域性 Block 則直接返回
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
// 進行復制
else {
// Its a stack block. Make a copy.
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);
if (!result) return NULL;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;
#endif
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
_Block_call_copy_helper(result, aBlock);
result->isa = _NSConcreteMallocBlock;
return result;
}
}
複製程式碼
5.1.3 BLOCK_FIELD_IS_BYREF:複製 _block 變數
分析_Block_byref_copy
的實現:
static struct Block_byref *_Block_byref_copy(const void *arg) {
struct Block_byref *src = (struct Block_byref *)arg;
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// 複製 Block_byref
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
// byref value 4 is logical refcount of 2: one for caller, one for stack
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
// 複製 Block_byref2,含有 copy / dispose 方法的變數需要執行這部分程式碼
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;
if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
// 複製 Block_byref3
struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
copy3->layout = src3->layout;
}
(*src2->byref_keep)(copy, src);
}
else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
// 已經複製到堆上的 引用計數 +1
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
return src->forwarding;
}
複製程式碼
5.1.4 其他
在其他情況下都是直接指標指向原物件地址:
*dest = object;
複製程式碼
5.2 _Block_object_dispose
下面是 Block 捕獲變數的釋放邏輯:
void _Block_object_dispose(const void *object, const int flags) {
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
// get rid of the __block data structure held in a Block
_Block_byref_release(object);
break;
case BLOCK_FIELD_IS_BLOCK:
_Block_release(object);
break;
case BLOCK_FIELD_IS_OBJECT:
_Block_release_object(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
break;
default:
break;
}
}
複製程式碼
5.2.1 BLOCK_FIELD_IS_BYREF:釋放 _block 變數
分析_Block_byref_release
的實現:
static void _Block_byref_release(const void *arg) {
struct Block_byref *byref = (struct Block_byref *)arg;
// dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
byref = byref->forwarding;
// 判斷是否被複制到堆上
if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
os_assert(refcount);
// 引用計數 -1 後判斷是否要進行釋放操作
if (latching_decr_int_should_deallocate(&byref->flags)) {
// 判斷這個變數是否有 copy / dispose 方法
if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
(*byref2->byref_destroy)(byref);
}
free(byref);
}
}
}
複製程式碼
需要根據 _block 變數成員變數和標誌位來判斷釋放步驟。
5.2.2 BLOCK_FIELD_IS_BLOCK:釋放 Block
分析_Block_release
的實現:
void _Block_release(const void *arg) {
struct Block_layout *aBlock = (struct Block_layout *)arg;
if (!aBlock) return;
if (aBlock->flags & BLOCK_IS_GLOBAL) return;
if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;
// 全域性 Block 和 棧上的 Block 直接返回
// Block 引用計數 -1 後判斷是否需要進行釋放
if (latching_decr_int_should_deallocate(&aBlock->flags)) {
_Block_call_dispose_helper(aBlock);
_Block_destructInstance(aBlock);
free(aBlock);
}
}
複製程式碼
5.2.3 BLOCK_FIELD_IS_OBJECT:釋放物件
_Block_release_object(object);
複製程式碼
_Block_release_object
方法在 ARC 環境下無操作,和_Block_retain_object
一樣。
六、迴圈引用
一個 Block 作為類的成員變數的同時,Block 內部還使用了類例項的情況下會引發迴圈引用。在這種情況下,類例項持有成員變數 block ,block 持有成員變數 __block 變數,__block 變數結構體持有類例項,形成一個三角迴圈引用關係。
__block id tmp = self;
blk = ^{
NSLog(@"self = %@", tmp);
}
複製程式碼
解決迴圈引用的方法有 2 種,一種是使用 __weak 修飾符,這種方法打破了__block 變數結構體持有類例項的關係,從而避免迴圈引用。
__weak id tmp = self;
blk = ^{
NSLog(@"self = %@", tmp);
}
複製程式碼
還有一種情況是使用 __block 修飾符,然後 blk 呼叫函式最後一行將tmp
手動置空,這種方法雖然也可以避免迴圈引用,但是一旦 blk 沒有被呼叫的話,同樣會造成迴圈引用。所以還是使用 __weak 修飾符的方式更為安全。
__block id tmp = self;
blk = ^{
NSLog(@"self = %@", tmp);
tmp = nil;
}
複製程式碼
總結
整片文章寫下來,最重要的幾個概念:
- 捕獲不同型別變數的 Block 會生成不同的邏輯。
- Block 的作用域的機制是為了 Block 和內部使用的變數在超出其作用域仍能使用。
- 針對不同型別的變數或 __block 變數結構體, 複製和釋放邏輯也不一樣。