深入探究Block
Block的實質
OC中宣告一個Block,並且執行
int(^example)(int) = ^(int a) {
return a;
};
example(2);
clang -rewrite-objc main.m
C++中對應的程式碼
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 int __main_block_func_0(struct __main_block_impl_0 *__cself, int a) {
return a;
}
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;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_s__6rlwkwm97z79ts2p8dbrlbx80000gn_T_main_f82934_mi_0);
int(*example)(int) = ((int (*)(int ))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((int (*)(__block_impl *, int))((__block_impl *)example)->FuncPtr)((__block_impl *)example, 2);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
挺噁心的?我看這麼一大塊也沒胃口
程式碼分塊 各個部分程式碼對應的功能
^(int a) {
return a;
};
對應的程式碼
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int a) {
return a;
}
通過Block使用的匿名函式實際上被作為簡單的C語言函式來處理。
通過BLOCK所屬的函式名和該Block語法在該函式中出現的順序值來給經Clang變換的函式命名。__main_block_func_0
函式引數cself相當於C++例項方法中指向例項自身的變數this,或是OC中的self
cself的型別 struct __main_block_impl_0的實現程式碼
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;
}
};
struct __main_block_impl_0 這個結構體就是examble Block的結構體,cself指向它
__block_impl的宣告 Block的implementation
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
__main_block_desc_0的description
static struct __main_block_desc_0 {
//版本升級所需的保留區域
size_t reserved;
//Block的大小
size_t Block_size;
}
__main_block_impl_0 block的建構函式
__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建構函式的呼叫
int(*example)(int) = ((int (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
去掉型別轉換的部分
int(*example)(int) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
該原始碼就是將__main_block_impl_0 的指標 &__main_block_impl_0 賦值給了 Block型別變數 example。
__main_block_func_0 由Block語法轉換的C函式指標
__main_block_desc_0_DATA 作為靜態全域性變數初始化的__main_block_desc_0結構體指標
__main_block_desc_0_DATA 作為靜態全域性變數初始化的程式碼
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_impl 的初始化
//建構函式
__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
((int (*)(__block_impl *, int))((__block_impl *)example)->FuncPtr)((__block_impl *)example, 2);
//去掉轉換部分
(*example->FuncPtr)(example, 2);
這就是簡單的使用函式指標呼叫函式。
_NSConcreteStackBlock;
impl.isa = &_NSConcreteStackBlock;
將Block指標賦值給Block結構體的成員變數isa.為了理解它,首先要理解OC類和物件的實質。其實,所謂Block就是OC物件。
OC物件
- 所有父類的成員變數和自己的成員變數都會存放在該物件所對應的儲存空間中。
- 物件有isa指標,指向類物件。
- 類物件結構體中存放著本物件的
1. 物件方法列表
2. 成員變數的列表
3. 屬性列表
objc_runtime_old.h
struct objc_class : objc_object {
Class superclass;
const char *name;
uint32_t version;
uint32_t info;
uint32_t instance_size;
struct old_ivar_list *ivars;//成員變數的列表
struct old_method_list **methodLists;//物件方法列表
Cache cache;
struct old_protocol_list *protocols;
// CLS_EXT only
const uint8_t *ivar_layout;
struct old_class_ext *ext;
...;
}
struct old_class_ext {
uint32_t size;
const uint8_t *weak_ivar_layout;
struct old_property_list **propertyLists;//屬性列表
};
回到結構體,impl.isa 指向類物件 就是&_NSConcreteStackBlock。
即_NSConcreteStackBlock相當於class_t結構體例項。在將Block作為OC的物件處理時,關於該類的資訊放置於_NSConcreteStackBlock中。
截獲自動變數
#import <Foundation/Foundation.h>
int b=5;
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
int c = 10;
int(^example)(int) = ^(int a) {
a = a + b + c;
return a;
};
example(2);
}
return 0;
}
用clang變換為C++原始碼
int b=5;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int c;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _c, int flags=0) : c(_c) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int a) {
int c = __cself->c; // bound by copy
a = a + b + c;
return a;
}
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;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_s__6rlwkwm97z79ts2p8dbrlbx80000gn_T_main_664ebf_mi_0);
int c = 10;
int(*example)(int) = ((int (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, c));
((int (*)(__block_impl *, int))((__block_impl *)example)->FuncPtr)((__block_impl *)example, 2);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
struct __main_block_impl_0 跟上一份我們沒有截獲自動變數的程式碼區別在哪呢?
//截獲自動變數int c;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
**int c;**
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _c, int flags=0) : c(_c) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//未截獲自動變數int c;
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;
}
};
可以看到Block語法表示式中的自動變數被作為成員變數新增到了__main_block_impl_0結構體中
#import <Foundation/Foundation.h>
int b=5;
有沒有注意到int b = 5;雖然在Block中使用了 但沒有被截獲。
Block只截獲在Block中使用的自動變數
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _c, int flags=0) : c(_c) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
建構函式中也相應的多了增加的引數
Block的值
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int a) {
int c = __cself->c; // bound by copy
a = a + b + c;
return a;
}
int c = __cself->c; // bound by copy
可以看到Block語法表示式中所使用的自動變數是被儲存到Block的結構體例項中的自動變數,而不是自動變數c.
__block
自動變數在Block中是不能被改寫的,這樣以來就無法在Block中儲存值了極其不方便。
解決這個問題有兩種方法
方法一:C中允許Block改變值的變數
- 靜態變數
- 全域性變數
- 靜態全域性變數
把這段程式碼轉為C++
#import <Foundation/Foundation.h>
int b=5;
static int static_global_d = 6;
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
static int static_e = 6;
int c = 10;
int(^example)(int) = ^(int a) {
a = a + b + c + static_e + static_global_d;
return a;
};
example(2);
}
return 0;
}
int b=5;
static int static_global_d = 6;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int c;
int *static_e;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _c, int *_static_e, int flags=0) : c(_c), static_e(_static_e) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int a) {
int c = __cself->c; // bound by copy
int *static_e = __cself->static_e; // bound by copy
a = a + b + c + (*static_e) + static_global_d;
return a;
}
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;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_s__6rlwkwm97z79ts2p8dbrlbx80000gn_T_main_d6e483_mi_0);
static int static_e = 6;
int c = 10;
int(*example)(int) = ((int (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, c, &static_e));
((int (*)(__block_impl *, int))((__block_impl *)example)->FuncPtr)((__block_impl *)example, 2);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
靜態全域性變數和全域性變數的訪問和轉換前完全相同
靜態區域性變數則發生了變化
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int c;
**int *static_e;**
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _c, int *_static_e, int flags=0) : c(_c), static_e(_static_e) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int a) {
int c = __cself->c; // bound by copy
**int *static_e = __cself->static_e; // bound by copy**
a = a + b + c + (*static_e) + static_global_d;
return a;
}
可以發現,Block儲存的是靜態區域性變數的指標!這是超出作用域使用變數的最簡單方法。
實際上在由Block語法生成的Block上,可以截獲自動變數的指標,但是自動變數超過作用域後,會自動被廢棄,截獲的指標就是野指標了
方法二:__block
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
__block int c = 10;
int(^example)(int) = ^(int a) {
c = 11;
a = a + c;
return a;
};
example(2);
}
return 0;
}
struct __Block_byref_c_0 {
void *__isa;
__Block_byref_c_0 *__forwarding;
int __flags;
int __size;
int c;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_c_0 *c; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_c_0 *_c, int flags=0) : c(_c->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int a) {
__Block_byref_c_0 *c = __cself->c; // bound by ref
(c->__forwarding->c) = 11;
a = a + (c->__forwarding->c);
return a;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->c, (void*)src->c, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->c, 8/*BLOCK_FIELD_IS_BYREF*/);}
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};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_s__6rlwkwm97z79ts2p8dbrlbx80000gn_T_main_9f6ec2_mi_0);
__attribute__((__blocks__(byref))) __Block_byref_c_0 c = {(void*)0,(__Block_byref_c_0 *)&c, 0, sizeof(__Block_byref_c_0), 10};
int(*example)(int) = ((int (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_c_0 *)&c, 570425344));
((int (*)(__block_impl *, int))((__block_impl *)example)->FuncPtr)((__block_impl *)example, 2);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
使用__block修飾的自動變數 轉換為c++程式碼後發生瞭如下變化
__attribute__((__blocks__(byref))) __Block_byref_c_0 c = {(void*)0,(__Block_byref_c_0 *)&c, 0, sizeof(__Block_byref_c_0), 10};
通過__block修飾後,自動變數竟然變成了結構體例項。
struct __Block_byref_c_0 {
void *__isa;
__Block_byref_c_0 *__forwarding;
int __flags;
int __size;
int c;
};
通過這段程式碼 int c; 也出現在結構體中,該結構體持有相當於原自動變數的成員變數。
使用Block的程式碼
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int a) {
__Block_byref_c_0 *c = __cself->c; // bound by ref
(c->__forwarding->c) = 11;
a = a + (c->__forwarding->c);
return a;
}
這段程式碼的意思基本上如圖所示
__forwarding 將在 Block的儲存域 中提到.