Blocks
含義:帶有自動變數值的匿名函式
Blocks 模式
Block 語法
^ 返回值型別 引數列表 表示式
與C語言函式的定義相比,不同點在於:沒有函式名,帶有^
(插入記號)
Block 型別變數
引數型別 (^ 引數名稱) (block引數列表)
自動變數的捕獲
int main() {
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (^blk)(void) = ^{printf (fmt, val); };
val = 2;
fmt = "These values were changed, val = %d\n";
blk();
return 0;
}
// 執行結果
// val = 10
複製程式碼
從上面的程式碼可以看出,Block表示式使用的變數值,是在block宣告時變數的瞬間值。也就是說block會捕獲自動變數的瞬時值。在之後哪怕更改了變數的值,也不會對block造成影響。
__block說明符
block所捕獲的值不能再block中進行修改或重新賦值。如果想要修改捕獲的值的話,需要在block外對該變數使用__block
進行修飾: __block int a = 0;
但是,對於oc物件的話,如果呼叫的是變更物件的方法的話,則沒有問題。比方說給捕獲到的陣列新增元素。
Blocks 的實現
實質
int main() {
void (^blk)(void) = ^{ printf("Block\n"); };
blk();
return 0;
}
複製程式碼
使用-rewrite-obj
將上面的程式碼,轉換為C++程式碼:
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;
__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) {
printf("Block\n");}
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, char * argv[]) {
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
複製程式碼
通過和原始碼的比對,可以看出對應的就是void (*blk) (void) = ((void(*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
這一句程式碼。這句程式碼的作用就是將棧上生成的__main_block_impl_0
結構體例項的指標賦值給blk。
__main_block_impl_0()
這個結構體的建構函式接受兩個引數,第一個是c語言的函式指標,第二個是__main_block_desc_0
的結構體例項指標。
換句話說,就是將__main_block_impl_0
中的__block_impl
初始化為:
isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
複製程式碼
而Block的呼叫的話,就是簡單的通過函式指標來呼叫函式:(*blk->impl.FuncPtr)(blk);
捕獲變數
int main(int argc, char * argv[]) {
int i = 1;
void (^blk)(void) = ^{
printf("%d", i);
};
blk();
return 0;
}
複製程式碼
同樣將上面的程式碼改寫為C++
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int i;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int i = __cself->i; // bound by copy
printf("%d", i);
}
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, char * argv[]) {
int i = 1;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
複製程式碼
可以看到__main_block_impl_0
的結構體中,block外的變數被作為成員變數新增到了結構體當中。然後其建構函式變成了__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i)
這樣:
impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
i = 1;
複製程式碼
因此可以看到,block的自動捕獲變數的功能,實際上是將該變數值儲存到block的結構體例項中(block自身)。
__block
在block中,靜態變數,靜態全域性變數和全域性變數這三種型別的變數是允許進行改寫的,除此之外,其他型別的變數,如果想在block中進行修改,就只能使用__block
變數。
為int i = 0;
加上__block
這個說明符,然後改寫成C++:
struct __Block_byref_i_0 {
void *__isa;
__Block_byref_i_0 *__forwarding;
int __flags;
int __size;
int i;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_i_0 *i; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__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_i_0 *i = __cself->i; // bound by ref
printf("%d", (i->__forwarding->i));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 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, char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 1};
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
複製程式碼
從程式碼我們看到,變數i變成了__Block_byref_i_0
這個結構體例項(__block
修飾的變數會被轉化為__Block_byref_ParameterName_0
這樣的結構體),該結構體的宣告如下:
struct __Block_byref_i_0 {
void *__isa;
__Block_byref_i_0 *__forwarding;
int __flags;
int __size;
int i; // 原變數
}
複製程式碼
如果在block中為__block
變數賦值的話,其轉換程式碼為:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_i_0 *i = __cself->i; // bound by ref
(i->__forwarding->i) = 2;
}
複製程式碼
__Block_byref_val_0
結構體例項的成員變數__forwarding
是個指向例項自身的指標:
並且__Block_byref_i_0
這個結構體並不在__main_block_impl_0
這個結構體的例項中,這是因為為了在多個block中使用__block
變數
Block 儲存域
Block實際上會轉換為__main_block_impl_0
這樣的結構體型別的自動變數,__block
變數會轉換為__Block_byref_para_0
這樣的結構體型別自動變數。(結構體型別自動變數指的是棧上生成該結構體的例項)
名稱 | 實質 |
---|---|
Block | 棧上Block的結構體例項 |
__block變數 | 棧上__block變數的結構體例項 |
Block作為OC物件來看時,其類是_NSConcreteStackBlock
,除了這個類之外,還有:
類 | 設定物件的儲存域 |
---|---|
_NSConcreteStackBlock | 棧 |
_NSConcreteGlobalBlock | 程式的資料區域 |
_NSConcreteMallocBlock | 堆 |
可以看到block的型別有三種,可是怎麼知道block的型別呢?
當block為全域性變數或者是未使用捕獲自動變數時,為__NSConcreteGlobalBlock
,其他情況為__NSConcreteStackBlock
。
使用__block
變數時,實際上是將block和變數從棧上覆制到堆上,此時,block的型別為__NSConcreteMallocBlock
Block的類 | 副本源的配置儲存域 | 複製效果 |
---|---|---|
_NSConcreteStackBlock | 棧 | 從棧複製到堆 |
_NSConcreteGlobalBlock | 程式的資料區域 | 什麼也不做 |
_NSConcreteMallocBlock | 堆 | 引用計數增加 |
而對於__block
變數的話:
__block變數的配置儲存域 | Block被複制到堆時的影響 |
---|---|
棧 | 從棧複製到堆並被Block持有 |
堆 | 被Block持有 |
在Block被複制到堆上的時候,原有的__forwarding
的值會指向堆上的Block的__block
變數的結構體例項的地址
捕獲OC物件
通過__main_block_copy_0
和__main_block_dispose_0
來改變物件的引用計數數值。
在以下幾種情況下,Block會被複制到堆上:
- 呼叫block的copy方法
- Block作為函式返回值返回時
- 將Block賦值給帶有
__strong
的id型別的類或者Block型別的成員變數 - 使用方法名稱含有
usingBlock
的Cocoa框架方法或者是GCD的block時
迴圈引用
解決block迴圈引用的方法有兩種,一種是__weak
,另一種是__block
.不過是引用__block
的話,必須執行block,且在block中要將變數賦值為nil, 使用__block
解決迴圈引用的優點在於可以控制物件的持有時間。