本文主要整理了Objective-C的Block實(shí)現(xiàn)方式。
iOS 其他相關(guān)博文鏈接iOS-day-by-day
目錄
- 1.Objective-C與其他語言中對比
- 2.Block模式
- 3.Block底層實(shí)現(xiàn)
一. Objective-C和其他語言對比
在其他許多編程語言中,也存在Block被稱為閉包(Closure)、lambda計(jì)算等。
程序語言 | Block的名稱 |
---|---|
C+ Blocks | Block |
Smalltalk | Block |
Ruby | Block |
LISP | Lambda |
Python | Lambda |
C++ | Lambda |
Javascript | Anonymous function |
二.Block常用術(shù)語
1.Block語法結(jié)構(gòu)
Block是帶有自動(dòng)變量值得匿名函數(shù)。
例如:
// 1
^ int (int count) { return count + 1;}
// 2
^ (int count) {printf("Block");}
// 3
^{printf("Block");}
Block字面值的寫法:
^ (double firstValue, double secondValue) {
return firstValue *secondValue;
}
上面寫法省略了返回值的類型,可以顯示的指出返回值類型。通過使用typedef
可以聲明“blk_t”類型變量
typedef double (^blk_t)(double, double);
blk_t blk = ^(double firstValue, double secondValue){
return firstValue * secondValue;
};
NSLog(@"%f", blk(3, 4));
Block也是一個(gè)Objective-C對象,可以用于賦值,當(dāng)參數(shù)傳遞,也可以放在集合中。
2.截獲自動(dòng)變量值
int val = 10;
void (^blk_t) (void) = ^ {
NSLog(@"val = %d", val);
};
val = 2;
blk_t(); // val = 10
執(zhí)行結(jié)果,不是val = 2,而是val = 10。捕獲的是執(zhí)行Block語法時(shí)的的瞬間值。這就是自動(dòng)變量值的捕獲。
3.__block說明符
自動(dòng)變量值截獲只能保存執(zhí)行Block語法瞬間值。保存后就不能修改該值。否則,會產(chǎn)生編譯錯(cuò)誤。如果想要修改截獲的自動(dòng)變量,需要使用__block
修飾。
__block int val = 10;
void (^blk_t) (void) = ^ {
val = 11;
NSLog(@"val = %d", val );
};
blk_t(); // val = 11
使用附有__block
說明符的自動(dòng)變量可在Block中賦值,該變量稱為_block
變量。
三.Block底層實(shí)現(xiàn)
1. Block的實(shí)質(zhì)
Block是“帶有自動(dòng)變量值得匿名函數(shù)”。但Block究竟是什么呢?接下來我們一起來分析。
為了研究編譯器是如何實(shí)現(xiàn)block的,需要使用clang命令,可以將Objective-C的源碼改寫成C++源代碼,命令如下:
clang -rewrite-objc xxx.c
新建一個(gè)main.c的源文件:
int main(int argc, const char * argv[]) {
void (^blk) (void) = ^ {
NSLog(@"Hello, Block!");
};
blk();
return 0;
}
在文件所在的文件夾,使用命令:clang -rewrite-objc main.m
,在目錄中得到一個(gè)名為main.cpp
的文件,區(qū)區(qū)七八行代碼,轉(zhuǎn)換后變成10萬行,其中關(guān)鍵代碼:
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;
}
};
//實(shí)現(xiàn)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_d3_9wynvv910yz9wv20fw56jz0h0000gn_T_main_dd5e8c_mi_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)};
// main
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;
}
接下來我們分析一下,這幾行關(guān)鍵代碼是什么意思?
從int main()
中開始。簡化一下發(fā)現(xiàn):
// block
void (*blk)(void) = __main_block_impl_0(_main_block_func_0, &_mian_block_desc_0_DATA);
//調(diào)用
(((*blk)->FuncPtr)blk);
第一條語句中出現(xiàn)的__main_block_impl_0()
,__main_block_func_0
和__mian_block_desc_0_DATA
是什么意思?別急,我們一條條來分析。
__main_block_impl_0()
實(shí)際上是__main_block_impl_0
結(jié)構(gòu)體的構(gòu)造函數(shù)。結(jié)構(gòu)體聲明如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
};
第一個(gè)成員變量是impl
。先看看__block_impl
結(jié)構(gòu)體的聲明:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
從這里我們可能會想到某種標(biāo)記,預(yù)留區(qū)域和函數(shù)指針。具體內(nèi)容我們會在后面詳細(xì)講解。第二變量是Desc
指針, __main_block_desc_0
聲明如下:
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
}
預(yù)留區(qū)域和Block的大小。接下來是構(gòu)造函數(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;
}
通過前面調(diào)用構(gòu)造函數(shù),可以簡化為:
isa = &__NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
其中:
isa = &__NSConcreteStackBlock;
將Block指針賦給Block的結(jié)構(gòu)體成員變量isa。其實(shí),Block就是Objective-C對象。想要理解上句代碼的意思,需要理解Objective-C類和對象的實(shí)質(zhì)。
id 類型
typedef struct objc_object *id;
typedef struct objc_class *Class;
id為objc_object
結(jié)構(gòu)體的指針類型。Class 為objc_class
結(jié)構(gòu)體的指針類型。objc_class
結(jié)構(gòu)體在objc4/runtime.h
中聲明:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
兩者很相似,objc_object
結(jié)構(gòu)體和objc_class
結(jié)構(gòu)體歸根結(jié)底實(shí)在各個(gè)對象和類的實(shí)現(xiàn)中使用的最基本的結(jié)構(gòu)體。
下面通過簡單的MyObject
來看一下。
@implementation MyObject
{
int val0;
int val1;
}
@end
轉(zhuǎn)換為C++源代碼,MyObject結(jié)構(gòu)體
struct MyObject_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int val0;
int val1;
};
實(shí)例變量val0和val1被直接聲明為結(jié)構(gòu)體的成員。上述中,結(jié)構(gòu)體都是基于objc_class結(jié)構(gòu)體的class_t結(jié)構(gòu)體。class_t聲明如下:
struct _class_t {
struct _class_t *isa;
struct _class_t *superclass;
void *cache;
void *vtable;
struct _class_ro_t *ro;
};
該結(jié)構(gòu)體實(shí)例中,持有聲明的成員變量、方法的名稱、方法的實(shí)現(xiàn)、屬性以及父類的指針。回到
isa = &__NSConcreteStackBlock;
__NSConcreteStackBlock
相當(dāng)于class_t結(jié)構(gòu)體實(shí)例,關(guān)于該類的信息都存放在__NSConcreteStackBlock中。
2. 截獲自動(dòng)變量
int main(int argc, const char * argv[]) {
int val = 10;
void (^blk) (void) = ^ {
printf("%d", val);
};
blk();
return 0;
}
使用clang命令轉(zhuǎn)為C++代碼,看看和之前的有什么區(qū)別。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int val; // <== val
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_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 val = __cself->val; // bound by copy
printf("%d", val);
}
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 val = 10;
void (*blk) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
對比之前的代碼發(fā)現(xiàn),Block語法表達(dá)式中使用的自動(dòng)變量被作為成員變量追加到__main_block_impl_0
結(jié)構(gòu)體中。并且類型完全相同,另外注意,未使用的變量并不會追加。
__main_block_impl_0
結(jié)構(gòu)體實(shí)例初始化:
isa = &__NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
val = 10;
這時(shí),在__main_block_impl_0
結(jié)構(gòu)體實(shí)例中,自動(dòng)變量值被截獲。
總的來說,所謂“截獲自動(dòng)變量值”意味著在執(zhí)行Block語法時(shí),Block語法表達(dá)式所使用的自動(dòng)變量值被保存到Block的結(jié)構(gòu)體實(shí)例中。
3.__block說明符
在Block中修改捕獲的外部變量,需要使用__block修飾符(__block存儲域類說明符)。在C中存儲域類:typedef、extern、static、auto、register。__block與他們的作用相同,用來指定將變量值設(shè)置到哪個(gè)存儲域中。
下面看一個(gè)例子:
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int val = 10;
void (^blk) (void) = ^{
val = 11;
};
blk();
}
return 0;
}
使用clang命令,轉(zhuǎn)為C++源碼如下:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref <=======注意
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__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_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 11;
}
// <====== 相當(dāng)于retain
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
// <====== 相當(dāng)于release
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 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_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
void (*blk) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
上面是含有__block修飾符的代碼。對比一下和之前的差別。
__block int val = 10;
__block修飾的變量變成結(jié)構(gòu)體
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding; //
int __flags;
int __size;
int val; // <=== val 作為結(jié)構(gòu)體的成員變量
};
變量val
被作為結(jié)構(gòu)體的成員變量。_forwarding指針什么作用?后面在分析。
那變量賦值的代碼呢?
^{val = 11;}
如下:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 11;
}
變量的復(fù)制
- 對于block外部變量的引用,block默認(rèn)是將其復(fù)制到其數(shù)據(jù)結(jié)構(gòu)中來實(shí)現(xiàn)訪問的。
- 對于__block修飾的外部變量引用,block是復(fù)制其引用地址來實(shí)現(xiàn)訪問的。
另外,可以參考《招聘一個(gè)靠譜的iOS (下)13、14題》。
ARC,MRC 對Block類型的影響
在ARC開啟的情況下,將只會有NSConcreteGlobalBlock和NSConcreteMallocBlock類型的block。原本NSConreteStackBlock被NSConcreteMallocBlock類型替代。
在Block中,如果只使用全局或靜態(tài)變量或者不是用外部變量,那么Block塊的代碼會存儲在全局區(qū)。
在ARC中
- 如果使用外部變量,Block塊的代碼會存儲在堆區(qū)。
在MRC中
- 如果使用外部變量,Block塊的代碼會存儲在棧區(qū)。
Block默認(rèn)情況下不能修改外部變量,只能讀取外部變量。
在ARC中
- 外部變量在堆中,這個(gè)變量在Block塊內(nèi)與在Block塊外地址相同。
- 外部變量在棧中,這個(gè)變量會被copy到為Block代碼塊所分配的堆中。
在MRC中
- 外部變量在堆中,這個(gè)變量在Block塊內(nèi)與在Block塊外地址相同。
- 外部變量在棧中,這個(gè)變量會被copy到為Block代碼塊所分配的棧中。
如果需要修改外部變量,需要在外部變量前聲明__block。
在ARC中
- 外部變量存在堆中,這個(gè)變量在Block塊內(nèi)與Block塊外地址相同。
- 外部變量存在棧中,這個(gè)變量會被轉(zhuǎn)移到堆中,不是復(fù)制。
在MRC中
- 外部變量存在堆中,這個(gè)變量在Block塊內(nèi)與Block塊外地址相同。
- 外部變量存在棧中,這個(gè)變量在Block塊內(nèi)與Block塊外地址相同。
關(guān)于Block的面試題
- 使用block時(shí)什么情況會發(fā)生引用循環(huán),如何解決?
- 在block內(nèi)如何修改block外部變量?
- 使用系統(tǒng)的某些block api(如UIView的block版本寫動(dòng)畫時(shí)),是否也考慮引用循環(huán)問題?
參考鏈接
談Objective-C block的實(shí)現(xiàn)
Block教程系列
對Objective-C中Block的追探
iOS中block實(shí)現(xiàn)的探究
Block 編程