iOS block探究(二): 深入理解
你要知道的block都在這裡
上一篇文章iOS block探究(一):基礎詳解介紹了block
的基本原理和使用方法,以及相關修飾符詳解。
本文將會深入底層探究block
的本質。
三種block型別
NSGlobalBlock
如果block
不捕獲外部變數,那麼在ARC環境下就是建立一個全域性block
。全域性block
儲存在全域性記憶體中,不需要在每次呼叫的時候都在棧中建立,塊所使用的整個記憶體區在編譯期已經確定了,因此這種塊是一種單例,不需要多次建立。
NSMallocBlock
如果block捕獲外部變數,那麼在ARC環境下就是創了一個堆區block
。程式碼中最常用的block
block
,當堆區block
的引用計數為0時也會像普通物件一樣被銷燬,再也不能使用了。
NSStackBlock
在MRC環境下,預設建立棧區block,一般使用copy
函式拷貝到堆區再使用,否則block
可能會被釋放,在ARC環境下一般不考慮。
深入程式碼理解block
比較重要的定義程式碼如下:
/* Revised new layout. */
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
上述結構體中最重要的就是invoke
變數,從宣告中可以看出,這是一個函式指標,指向block
的執行程式碼,可以認為block
的執行程式碼是一個匿名函式,在建立block
invoke
變數。 struct Block_layout
結構體中有一個descriptor
變數,而struct Block_descriptor
比較重要的就是copy
函式和dispose
函式,從命名就可以看出,copy
函式用於捕獲變數並持有引用,而dispose
函式就是用於釋放捕獲的變數。 block
捕獲的變數都會儲存在結構體struct Block_layout
的後面,對於物件儲存的是指標,在invoke
函式執行之前全部讀出。 以上就是
block
大致的實現方式,可以看出,block
是一種替換函式指標的語法,相比使用函式指標更方法,寫法也更便捷。
接下來看一下具體程式碼的實現。
在進入正題之前,先介紹一個clang
編譯器的命令
clang -rewrite-objc main.m
這個命令用於clang
重寫.m檔案
為.cpp檔案
先實現一個最簡單的無引數無返回值的block
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^printBlock)() = ^{
NSLog(@"Hello World");
};
printBlock();
}
return 0;
}
使用上述命令生成.cpp檔案
後可以找到如下程式碼
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_1f_dz4kq57d4b19s4tfmds1mysh0000gn_T_main_cb4573_mi_0);
}
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 (*printBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)printBlock)->FuncPtr)((__block_impl *)printBlock);
}
return 0;
}
static __NSConstantStringImpl __NSConstantStringImpl__var_folders_1f_dz4kq57d4b19s4tfmds1mysh0000gn_T_main_cb4573_mi_0 __attribute__ ((section ("__DATA, __cfstring"))) = {__CFConstantStringClassReference,0x000007c8,"Hello World",11};
上述程式碼中__main_block_func_0
函式就是建立block
時定義的一個函式,當block
執行時就是執行了該函式,這個函式內部是呼叫了另一個函式,也就是block
裡寫的執行程式碼,可以看出,block
實際就是將我們定義的block
又封裝了一下,使用起來更方便。
接下來修改程式碼,讓block
捕獲一個物件
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSString *name = @"Jiaming Chen";
void (^printBlock)() = ^{
NSLog(@"Hello World %@", name);
};
printBlock();
}
return 0;
}
再次使用clang
重寫程式碼後可以看到如下定義
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSString *name;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSString *_name, int flags=0) : name(_name) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSString *name = __cself->name; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_1f_dz4kq57d4b19s4tfmds1mysh0000gn_T_main_e6fe7a_mi_1, name);
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->name, (void*)src->name, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->name, 3/*BLOCK_FIELD_IS_OBJECT*/);}
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;
NSString *name = (NSString *)&__NSConstantStringImpl__var_folders_1f_dz4kq57d4b19s4tfmds1mysh0000gn_T_main_e6fe7a_mi_0;
void (*printBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, name, 570425344));
((void (*)(__block_impl *))((__block_impl *)printBlock)->FuncPtr)((__block_impl *)printBlock);
}
return 0;
}
在struct __main_block_impl_0
中可以看到裡面儲存了被捕獲的物件,同時在__main_block_func_0
函式中將捕獲的物件賦值給了上述結構體變數。並且增加了__main_block_copy_0
函式和__main_block_dispose_0
函式,分別用於持有物件和釋放物件。
再看一下__block
的使用會有什麼區別。
修改程式碼如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block NSUInteger age = 22;
void (^printBlock)() = ^{
NSLog(@"Hello World %ld", age);
age = 100;
};
printBlock();
}
return 0;
}
重寫生成的程式碼如下:
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
NSUInteger age;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__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_age_0 *age = __cself->age; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders_1f_dz4kq57d4b19s4tfmds1mysh0000gn_T_main_ea236c_mi_0, (age->__forwarding->age));
(age->__forwarding->age) = 100;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 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;
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 22};
void (*printBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
((void (*)(__block_impl *))((__block_impl *)printBlock)->FuncPtr)((__block_impl *)printBlock);
}
return 0;
}
上述程式碼可以看出,使用__block
修飾後會生成一個struct __Block_byref_age_0
的結構體,可以看出,__block
修飾後會捕獲變數的引用而不是進行值拷貝,這也就是為什麼block
內部可以修改__block
修飾的變數以及外部變數修改後會影響block
內部捕獲變數的原因了。
備註
由於作者水平有限,難免出現紕漏,如有問題還請不吝賜教。