Block是C語言的擴(kuò)充功能,是帶有自動變量的匿名函數(shù)。block 將同一邏輯的代碼放在一個塊,使代碼更簡潔緊湊,易于閱讀,比函數(shù)使用更方便,代碼更美觀,開發(fā)中受到廣泛的使用。
block 的底層實(shí)現(xiàn)
將main.m中的代碼通過clang編譯成main.cpp代碼:
int main(int argc, const char * argv[]) {
// insert code here...
void (^blk)(void) = ^{ printf("FlyElephant---Block\n"); };
blk();
return 0;
}
main.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) {
printf("FlyElephant---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, const 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;
}
block通過__main_block_impl_0初始化,后序工作通過 __block_impl ,__main_block_desc_0實(shí)現(xiàn)。
__block_impl
__block_impl結(jié)構(gòu)體代碼如下:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
- isa 指向?qū)嵗龑ο螅琤lock 本身也是一個 Objective-C 對象。block 的三種類型:_NSConcreteStackBlock、_NSConcreteGlobalBlock、_NSConcreteMallocBlock,即當(dāng)代碼執(zhí)行時,isa 有三種取值 :
impl.isa = &_NSConcreteStackBlock;
impl.isa = &_NSConcreteMallocBlock;
impl.isa = &_NSConcreteGlobalBlock;
- Flags 按位承載 block 的附加信息;
- Reserved 保留變量;
- FuncPtr 函數(shù)指針,指向 Block 要執(zhí)行的函數(shù)
以上的四個字段均在初始化的時候完成:
__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;
}
其中FuncPtr指針指向的block函數(shù):
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("FlyElephant---Block\n"); }
__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;
}
};
- impl block 實(shí)現(xiàn)的結(jié)構(gòu)體變量;
- Desc 描述 block 的結(jié)構(gòu)體變量;
- __main_block_impl_0 結(jié)構(gòu)體的構(gòu)造函數(shù),初始化結(jié)構(gòu)體變量 impl、Desc;
__main_block_desc_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)};
- reserved 結(jié)構(gòu)體信息保留字段
- Block_size 結(jié)構(gòu)體大小
整個實(shí)現(xiàn)過程就是初始化__main_block_impl_0,返回impl,執(zhí)行impl->FuncPtr.
block 捕獲外部變量
int main(int argc, const char * argv[]) {
// insert code here...
int localValue = 1;
void (^blk)(void) = ^{ printf("FlyElephant = %d\n", localValue); };
blk();
return 0;
}
block捕獲外部變量編譯之后的cpp代碼如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int localValue;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _localValue, int flags=0) : localValue(_localValue) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int localValue = __cself->localValue; // bound by copy
printf("FlyElephant = %d\n", localValue); }
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[]) {
int localValue = 1;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, localValue));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
__block_impl定義不變:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
block通過參數(shù)傳遞獲取到了localValue,保存到結(jié)構(gòu)體中的同名變量中。賦值的時候通過__cself來賦值:
int localValue = __cself->localValue; // bound by copy
不過暫時還不能還不能修改值,如果修改localValue會報錯:
Variable is not assignable (missing __block type specifier)
內(nèi)存區(qū)域
討論block的存儲區(qū)域我們先了解一下C/C++編譯的程序在內(nèi)存中的分布情況 :
1.棧區(qū)(stack)— 程序運(yùn)行時由編譯器自動分配,存放函數(shù)的參數(shù)值,局部變量的值等。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧。程序結(jié)束時由編譯器自動釋放。
2.堆區(qū)(heap) — 在內(nèi)存開辟另一塊存儲區(qū)域。一般由程序員分配釋放, 若程序員不釋放,程序結(jié)束時可能由OS回收 。注意它與數(shù)據(jù)結(jié)構(gòu)中的堆是不一樣,分配方式類似于鏈表。用malloc, calloc, realloc等分配內(nèi)存的函數(shù)分配得到的就是在堆上。
3.全局區(qū)(靜態(tài)區(qū))(static)—編譯器編譯時即分配內(nèi)存。全局變量和靜態(tài)變量的存儲是放在一塊的。對于C語言初始化的全局變量和靜態(tài)變量在一塊區(qū)域, 未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域。而C++則沒有這個區(qū)別 - 程序結(jié)束后由系統(tǒng)釋放。
4.文字常量區(qū) —常量字符串就是放在這里的。 程序結(jié)束后由系統(tǒng)釋放
5.程序代碼區(qū)—存放函數(shù)體的二進(jìn)制代碼。
block訪問變量有兩種方式一種是靜態(tài)變量,全局變量和__block形式。
靜態(tài)變量,全局變量
block使用靜態(tài)變量,全局變量,全局靜態(tài)變量代碼:
int global_val = 1;
static int static_global_val = 2;
int main(int argc, const char * argv[]) {
// insert code here...
static int static_val = 3;
void(^blk)(void) = ^ {
global_val = 2;
static_global_val = 3;
static_val = 4;
};
return 0;
}
編譯之后的代碼:
int global_val = 1;
static int static_global_val = 2;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_val = __cself->static_val; // bound by copy
global_val = 2;
static_global_val = 3;
(*static_val) = 4;
}
對靜態(tài)全局變量和全局變量訪問和轉(zhuǎn)換之前一樣,對靜態(tài)變量的方式是將指針保存了起來。block對于其自動變量而言沒有將指針保存起來,是因?yàn)樽詣幼兞?局部變量)超出其作用域之后就會被廢棄。
__block
通過__block看下代碼:
__block int localValue = 0;
void (^blk)(void) = ^{
localValue = 1;
};
編譯之后代碼:
struct __Block_byref_localValue_0 {
void *__isa;
__Block_byref_localValue_0 *__forwarding;
int __flags;
int __size;
int localValue;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_localValue_0 *localValue; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_localValue_0 *_localValue, int flags=0) : localValue(_localValue->__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_localValue_0 *localValue = __cself->localValue; // bound by ref
(localValue->__forwarding->localValue) = 1;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->localValue, (void*)src->localValue, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->localValue, 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[]) {
__attribute__((__blocks__(byref))) __Block_byref_localValue_0 localValue = {(void*)0,(__Block_byref_localValue_0 *)&localValue, 0, sizeof(__Block_byref_localValue_0), 0};
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_localValue_0 *)&localValue, 570425344));
return 0;
}
相對之前的代碼多了不少代碼,主要新增代碼:
- __Block_byref_localValue_0 結(jié)構(gòu)體:用于封裝 __block 修飾的外部變量。
- _Block_object_assign 函數(shù):當(dāng) block 從棧拷貝到堆時,調(diào)用此函數(shù)。
- _Block_object_dispose 函數(shù):當(dāng) block 從堆內(nèi)存釋放時,調(diào)用此函數(shù)。
OC源碼中的 __block localValue 翻譯后變成了 __Block_byref_intValue_0 結(jié)構(gòu)體指針變量 intValue,通過指針傳遞到 block 內(nèi),與靜態(tài)變量的指針傳遞是一致的。__Block_byref_intValue_0 這個結(jié)構(gòu)體的字段需要注意:
struct __Block_byref_localValue_0 {
void *__isa;
__Block_byref_localValue_0 *__forwarding;
int __flags;
int __size;
int localValue;
};
結(jié)構(gòu)體里面還多了個 __forwarding 指向自己的指針變量,這與block的類型有關(guān)系。
block類型
block 有三種類型 NSConcreteGlobalBlock,NSConcreteStackBlock和NSConcreteMallocBlock。
NSConcreteGlobalBlock
_NSConcreteGlobalBlock 類型的 block 處于內(nèi)存的 ROData 段,不捕獲局部變量,運(yùn)行不依賴上下文,內(nèi)存管理比較簡單。
*block 字面量寫在全局作用域時,編譯之后也是_NSConcreteGlobalBlock類型。
void (^blk)(void) = ^{ printf("Global Block\n"); };
int main(int argc, const char * argv[]) {
blk();
return 0;
}
struct __blk_block_impl_0 {
struct __block_impl impl;
struct __blk_block_desc_0* Desc;
__blk_block_impl_0(void *fp, struct __blk_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blk_block_func_0(struct __blk_block_impl_0 *__cself) {
printf("Global Block\n"); }
NSConcreteStackBlock
NSConcreteStackBlock 類型的 block 處于內(nèi)存的棧區(qū)。global block 由于處在 data 段,可以通過指針安全訪問,但 stack block 處在內(nèi)存棧區(qū),如果其變量作用域結(jié)束,這個 block 就被廢棄,block 上的 __block 變量也同樣會被廢棄。
為了解決這個問題,block 提供了 copy 的功能,將 block 和 __block 變量從棧拷貝到堆,也就是 _NSConcreteMallocBlock。
_NSConcreteMallocBlock
當(dāng) block 從棧拷貝到堆后,當(dāng)棧上變量作用域結(jié)束時,仍然可以繼續(xù)使用 block,在開啟 ARC 時,大部分情況下編譯器通常會將創(chuàng)建在棧上的 block 自動拷貝到堆上,例如:
- block 作為函數(shù)返回值返回時,編譯器自動將 block 作為 _Block_copy 函數(shù),效果等同于 block 直接調(diào)用 copy 方法;
- block 被賦值給 __strong id 類型的對象或 block 的成員變量時,編譯器自動將 block 作為 _Block_copy 函數(shù),效果等同于 block 直接調(diào)用 copy 方法;
- block 作為參數(shù)被傳入方法名帶有 usingBlock 的 Cocoa Framework 方法或 GCD 的 API 時。這些方法會在內(nèi)部對傳遞進(jìn)來的 block 調(diào)用 copy 或 _Block_copy 進(jìn)行拷貝;
編譯器不會自動調(diào)用 copy 方法:
- block 作為方法或函數(shù)的參數(shù)傳遞時;
typedef int (^blk_t)(int);
blk_t func(int rate)
{
return ^(int count){return rate * count;};
}
上面的 block 獲取了外部變量,所以是創(chuàng)建在棧上,當(dāng) func 函數(shù)返回給調(diào)用者時,脫離了局部變量 rate 的作用范圍,如果調(diào)用者使用這個 block 就會出問題。那 ARC 開啟的情況呢?運(yùn)行這個 block 一切正常,編譯器編譯之后的代碼:
blk_t func(int rate)
{
blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);
tmp = objc_retainBlock(tmp);
return objc_autoreleaseReturnValue(tmp);
}
objc_retainBlock本質(zhì)上調(diào)用的是_Block_copy函數(shù):
id objc_retainBlock(id x) {
return (id)_Block_copy(x);
}
objc_autoreleaseReturnValue 本質(zhì)上調(diào)用的是objc_autorelease函數(shù):
id
objc_autoreleaseReturnValue(id obj)
{
if (prepareOptimizedReturn(ReturnAtPlus1)) return obj;
return objc_autorelease(obj);
}
block類型拷貝:
Block類型 | 源拷貝區(qū)域 | Copy結(jié)果 |
---|---|---|
_NSConcreteStackBlock | 棧 | 從棧復(fù)制到堆 |
_NSConcreteGlobalBlock | 程序的數(shù)據(jù)區(qū)域 | 什么都不做 |
_NSConcreteMallocBlock | 堆 | 引用計數(shù)加1 |
block 內(nèi)存管理
當(dāng) block 從棧內(nèi)存被拷貝到堆內(nèi)存時,__block 變量的變化如下圖。需要說明的是,當(dāng)棧上的 block 被拷貝到堆上,堆上的 block 再次被拷貝時,對 __block 變量已經(jīng)沒有影響了。
__forwarding
block 從棧被拷貝到堆時,__forwarding 指針變量也會指向堆區(qū)的結(jié)構(gòu)體。
block 獲取局部變量,當(dāng)要在其他地方(超出局部變量作用范圍)使用這個 block 的時候,由于訪問局部變量異常,導(dǎo)致程序崩潰,因此需要將block從棧區(qū)拷貝到堆區(qū)。
將 block 拷貝到堆上的同時,將 __forwarding 指針指向堆上結(jié)構(gòu)體。后面如果要想使用 __block 變量,只要通過 __forwarding 訪問堆上變量,就不會出現(xiàn)程序崩潰了。
簡單講就是“不管__block變量配置在棧上還是堆上,都能正確的訪問該變量。”
__block int val = 0;
void (^blk)(void) = [^{++val;} copy];
++val;
blk();
NSLog(@"%d", val);
??代碼中 ^{++val;} 和 ++val; 都會被轉(zhuǎn)換成 ++(val.__forwarding->val);,堆上的 val 被加了兩次,最后打印堆上的 val 為 2。
block 循環(huán)引用
如果在Block使用附有__strong修飾符的對象類型自動變量,那么當(dāng)Block從棧復(fù)制到堆上時,該對象為Block所持有。
經(jīng)典的循環(huán)引用是self與block之間的相互引用:
typedef void (^blk_t)(void);
@interface User()
{
blk_t blk_;
}
@end
@implementation User
- (id)init
{
self = [super init];
blk_ = ^{NSLog(@"self = %@", self);};
return self;
}
- (void)dealloc
{
NSLog(@"dealloc");
}
@end
編譯器會提示警告:
Capturing 'self' strongly in this block is likely to lead to a retain cycle
如果block中使用self中的屬性或者成員變量:
@interface User()
{
blk_t blk_;
}
@property (assign, nonatomic) NSInteger age;
@end
@implementation User
- (id)init
{
self = [super init];
blk_ = ^{
NSLog(@"FlyElephant--%ld", (long)self.age);
};
return self;
}
- (void)dealloc
{
NSLog(@"dealloc");
}
@end
編譯會警告提示循環(huán)引用:
Capturing 'self' strongly in this block is likely to lead to a retain cycle
__weak修飾符解決循環(huán)引用問題:
__weak typeof(User) *weakself = self;
blk_ = ^{
NSLog(@"FlyElephant--%ld", (long)weakself.age);
};
return self;
__block避免循環(huán)引用:
typedef void (^blk_t)(void);
@interface User : NSObject
{
blk_t blk_;
}
@end
@implementation User
- (id)init
{
self = [super init];
__block id tmp = self;
blk_ = ^{
NSLog(@"self = %@", tmp);
tmp = nil;
};
return self;
}
- (void)execBlock
{
blk_();
}
- (void)dealloc
{
NSLog(@"dealloc");
}
@end
int main()
{
id object = [[User alloc] init];
[object execBlock];
return 0;
}
該代碼沒有引起循環(huán)引用。但是如果不執(zhí)行execBlock實(shí)例方式,即不執(zhí)行復(fù)試給成員變量blk_的Block,會循環(huán)引用并引起內(nèi)存泄漏。
參考鏈接
http://www.galloway.me.uk/2012/10/a-look-inside-blocks-episode-1/
http://www.galloway.me.uk/2012/10/a-look-inside-blocks-episode-2/