概要
block就是帶有自動(dòng)變量的匿名函數(shù)。
語法結(jié)構(gòu)如下:
^ 返回值類型 參數(shù)列表 表達(dá)式
其中返回值類型為void時(shí)可省略,同理參數(shù)列表;
block變量結(jié)構(gòu)同C語言函數(shù)指針類似,只是將*換為^符號(hào):
int (^block)(int) = ^(int)(int a){
NSLog(@"a:%d", a);
};
與通常的變量相同,block變量可賦值操作,也可作為函數(shù)的參數(shù)傳遞,也可作為返回值傳遞;
對(duì)于block類型變量,通常使用typedef定義,如下:
typedef int (^Block) (int);
//上面可修改為
Block block = ^(int)(int a){
NSLog(@"a:%d", a);
}
對(duì)于截獲自動(dòng)變量說明,類似c語言中的值傳遞拷貝:若想對(duì)截獲的自動(dòng)變量數(shù)值類型變量進(jìn)行同步修改,需要使用__block說明符。
原理分析
block本質(zhì)
通過clang(v1100.0.33.17)編譯器自帶的-rewirte-objc選項(xiàng)將objc代碼轉(zhuǎn)換為c++進(jìn)行分析,且都是基于ARC模式(添加-fobjc-arc),代碼如下:
void (^block)(void) = ^{
printf("block fired\n");
};
block();
轉(zhuǎ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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("block fired\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 (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
先從block實(shí)現(xiàn)最基本的結(jié)構(gòu)體說起,其結(jié)構(gòu)體信息如下:
struct __block_impl {
void *isa;//isa指針
int Flags;//標(biāo)志位
int Reserved;//保留位
void *FuncPtr;//函數(shù)指針
};
其中初始化構(gòu)造完成后的isa指針指向_NSConcreteStackBlock,FuncPtr函數(shù)指針指向具體的block實(shí)現(xiàn);如果對(duì)runtime原理熟悉的話,這個(gè)isa是不是似曾相識(shí),其指向的是類對(duì)象進(jìn)而指向元對(duì)象,最后指向root object;runtime通過isa指針使用c語言構(gòu)造了一套完整的面向?qū)ο髣?dòng)態(tài)語言。為保持完整的面向?qū)ο蟮捏w系,block也使用了isa來指向類對(duì)象,這里指向的是_NSConcreteStackBlock,后續(xù)會(huì)對(duì)該類對(duì)象重點(diǎn)分析,因此,block其實(shí)也是一個(gè)object對(duì)象;
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結(jié)構(gòu)體其實(shí)就是c++構(gòu)造的public對(duì)象,包含了__block_impl類實(shí)例對(duì)象及struct __main_block_desc_0結(jié)構(gòu)體對(duì)象(包含了類實(shí)例對(duì)象的大小);
block的調(diào)用函數(shù)如下,其中入?yún)⒛J(rèn)包含了struct __main_block_impl_0 *類型的__cself,與objc和c++中的self和this不謀而合,即是隱含傳遞了block的實(shí)例對(duì)象;
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("block fired\n");
}
具體的block()調(diào)用就是調(diào)用實(shí)例對(duì)象中指向的函數(shù)指針來實(shí)現(xiàn);
block對(duì)象源碼分析
在源碼Block_private.h中的定義如下:
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor;
// imported variables
};
typedef void(*BlockInvokeFunction)(void *, ...);
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
與c++重寫的結(jié)構(gòu)體其實(shí)是一樣的,結(jié)構(gòu)圖(較老版本)如下所示:

__block說明符
對(duì)于block中具有截獲的自動(dòng)變量值未使用__block說明符時(shí),其實(shí)就是在__main_block_impl_0中添加相應(yīng)的實(shí)例成員,并通過構(gòu)造函數(shù)時(shí)通過值傳遞捕獲自動(dòng)變量值;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;//截獲的自動(dòng)變量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {//構(gòu)造時(shí)初始化實(shí)例變量
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
printf("block fired, a=%d\n", 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[]) {
int a = 1;
//值傳參
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
對(duì)用使用__block說明符后的轉(zhuǎn)換代碼如下:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__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_a_0 *a = __cself->a; // bound by ref
printf("block fired, a=%d\n", (a->__forwarding->a));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 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_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 1};
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
與不含__block說明符不同之處在于:__main_block_impl_0結(jié)構(gòu)體中使用了struc __Block_byref_a_0對(duì)象及__block對(duì)象__main_block_copy_0及__main_block_dispose_0對(duì)象copy/dispose拷貝/釋放函數(shù),替換了簡(jiǎn)單地對(duì)應(yīng)的實(shí)例變量成員;并且struc __Block_byref_a_0結(jié)構(gòu)體中的成員__Block_byref_a_0 *__forwarding初始化指向了block實(shí)例對(duì)象成員自身(這個(gè)為啥多了該成員變量且指向自身后文闡述),如下圖所示

Block_private.h中__block源碼實(shí)現(xiàn)如下:
struct Block_byref {
void *isa;
struct Block_byref *forwarding;
volatile int32_t flags; // contains ref count
uint32_t size;
};
與c++轉(zhuǎn)換的實(shí)質(zhì)的一樣的;
上面截獲的自動(dòng)變量需要使用__block說明符來修改其值,但對(duì)于靜態(tài)變量、靜態(tài)全局變量及全局變量如何呢?
可以從值傳遞拷貝的原理分析,block匿名函數(shù)主要就是用于保存block函數(shù)內(nèi)部變量值用于后續(xù)調(diào)用,因此存在作用域的問題,對(duì)于靜態(tài)全局變量及全局變量而言,因此不存在訪問不到這些變量的情況,即block不會(huì)主動(dòng)去截獲這些變量,也就不需要使用__block說明符;但對(duì)于靜態(tài)變量而言,超過作用域就無法訪問,因此需要截獲此類型變量的地址,具體如下:
//源碼如下:
{
static int a = 1;
void (^block)(void) = ^{
printf("block fired, a=%d\n", a);
};
}
//轉(zhuǎn)換后的c++代碼如下
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
block存儲(chǔ)域
以上分析的都是局部變量類型的block變量,其類對(duì)象類型為_NSConcreteStackBlock,若是靜態(tài)類型或者全局類型則如何,是否與變量類型存儲(chǔ)類型一致。
直接上代碼并clang轉(zhuǎn)換分析:
typedef void (^Block)(int);
Block g_block1 = ^(int count){
printf("block1, count:%d", count);
};
Block g_block2, g_block3;
int main(int argc, const char * argv[]) {
int a = 1, b = 2;
g_block2 = ^(int count) {
printf("block2, count:%d, b:%d", count, b);
};
g_block3 = ^(int count){
printf("block3, count:%d", count);
};
g_block1(a);
g_block2(a);
return 0;
}
轉(zhuǎn)換后的關(guān)鍵結(jié)構(gòu)體如下:
//g_block1對(duì)應(yīng)的結(jié)構(gòu)體
struct __g_block1_block_impl_0 {
struct __block_impl impl;
struct __g_block1_block_desc_0* Desc;
__g_block1_block_impl_0(void *fp, struct __g_block1_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//g_block2對(duì)應(yīng)的結(jié)構(gòu)體
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int b;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _b, int flags=0) : b(_b) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//g_block3對(duì)應(yīng)的結(jié)構(gòu)體
struct __main_block_impl_1 {
struct __block_impl impl;
struct __main_block_desc_1* Desc;
__main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
g_block2 = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, b));
g_block3 = ((void (*)(int))&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA));
都是全局變量類型block但是其類對(duì)象類型不同,對(duì)于未截獲自動(dòng)變量的g_block1則為_NSConcreteGlobalBlock,對(duì)于需要截獲自動(dòng)變量的g_block2則為_NSConcreteStackBlock;但block類對(duì)象類型與《objective-C 高級(jí)編程 iOS與OSX多線程和內(nèi)存管理》存在差異,并且轉(zhuǎn)換后的c++代碼未發(fā)現(xiàn)引用計(jì)數(shù)相關(guān)的函數(shù)調(diào)用,如_objc_retainBlock,帶著疑惑將源碼轉(zhuǎn)換為匯編實(shí)現(xiàn)(通過xcode->Product->Perform Action->Assemble "xxxxx")一探究竟:


而實(shí)際的匯編實(shí)現(xiàn)
block類對(duì)象類型為_NSConcreteGlobalBlock,并且對(duì)于ARC模式下自動(dòng)添加了_objc_retainBlock/_objc_release函數(shù)調(diào)用來實(shí)現(xiàn)自動(dòng)引用計(jì)數(shù),實(shí)際的xcode編譯環(huán)節(jié)選項(xiàng)中也不存在-rewrite-objc中間生成c++代碼的過程(可能老的編譯器存在),因此,clang -rewrite-objc轉(zhuǎn)換后的代碼可用于參考內(nèi)部實(shí)現(xiàn),具體要以實(shí)際生成的匯編代碼為準(zhǔn);
【補(bǔ)充說明】在ARC模式下,_NSConcreteStackBlock類型的block對(duì)象都變成了_NSConcreteMallocBlock類型(官方說明中也提到Transitioning to ARC Release Notes),也可以通過demo打印block對(duì)象以驗(yàn)證;
#import <Foundation/Foundation.h>
typedef void (^Block)(int);
int main(int argc, const char * argv[])
{
int a = 1;
Block blk = ^(int count){
printf("%d\n", a);
};
blk(a);
NSLog(@"%@", blk);
__weak Block blk1 = ^(int count){
printf("%d\n", count);
};
NSLog(@"%@", blk1);
__weak Block blk2 = ^(int count){
printf("%d\n", a);
};
NSLog(@"%@", blk2);
NSLog(@"%@", ^{printf("%d\n", a);});
return 0;
}
打印結(jié)果如下:

因此,對(duì)于ARC模式下,
id類型以及對(duì)象類型變量隱含著__strong修飾符(默認(rèn)使用了Block_copy函數(shù)拷貝),若block變量表達(dá)式含有外部變量,則為_NSConcreteMallocBlock;若表達(dá)式不含有外部變量,則為_NSConcreteGlobalBlock;若使用了__weak修飾符或者未賦值給隱含__strong修飾符的變量時(shí),則為_NSConcreteStackBlock;
具體生成_NSConcreteGlobalBlock類對(duì)象類型的block場(chǎng)景如下:
- 全局
block變量且定義表達(dá)式內(nèi)部不使用應(yīng)被截獲的自動(dòng)變量; -
block表達(dá)式中不使用應(yīng)被截獲的自動(dòng)變量
與之_NSConcreteStackBlock相對(duì)應(yīng)的存儲(chǔ)類型,包括如下:
- _NSConcreteStackBlock
- _NSConcreteGlobalBlock
- _NSConcreteMallocBlock
存儲(chǔ)類型如圖:

那何種場(chǎng)景會(huì)是_NSConcreteMallocBlock類型呢,這個(gè)不難想象。對(duì)于上面的存儲(chǔ)類型,若是_NSConcreteStackBlock棧類型,若超過其作用域,則內(nèi)存會(huì)被釋放,即無法再使用;要是需要超過其作用域調(diào)用,則需要定義為_NSConcreteGlobalBlock或者_NSConcreteMallocBlock,但_NSConcreteGlobalBlock類型受限于定義位置使用不能截獲自動(dòng)變量,因此_NSConcreteMallocBlock堆類型應(yīng)運(yùn)而生。
借用《objective-C 高級(jí)編程 iOS與OSX多線程和內(nèi)存管理》書的插圖就很容易理解:

那何時(shí)
block變量會(huì)被從棧上復(fù)制到堆上?總結(jié)如下:
-
使用
copy方法如上面demo中使用
__weak修飾符且表達(dá)式中會(huì)被截獲自動(dòng)變量作為參數(shù)傳遞時(shí) -
block作為函數(shù)參數(shù)返回時(shí)如
return ^{}; -
將
block變量賦值給__strong修飾符id類型的類或者block成員變量時(shí)對(duì)于未賦值的
block對(duì)象默認(rèn)是棧類型,(ARC模式)賦值給__strong修飾符id類型的類或者block成員變量會(huì)自動(dòng)copy到堆上; GCD相關(guān)的API
Cocoa框架方法中含有
usingBlock時(shí)
對(duì)于手動(dòng)調(diào)用copy方法時(shí),若重復(fù)調(diào)用會(huì)如何?

__block變量存儲(chǔ)域
上面說明了block變量存儲(chǔ)域,對(duì)于其表達(dá)式中持有__block變量時(shí),__block類型變量(隱含為__strong類型)是否也會(huì)被復(fù)制到堆上?答案:是,且上文中提到的__forwarding成員變量會(huì)指向堆中的結(jié)構(gòu)體實(shí)例,因此無論棧上或者堆上的__block變量都可以訪問同一個(gè)__block變量,如圖所示:

參考
clang官方文檔也可以說明__block變量會(huì)自動(dòng)調(diào)用Block_copy()函數(shù)拷貝到堆上:
In garbage collected environments, the
__weakvariable is set to nil when the object it references is collected, as long as the__blockvariable resides in the heap (either by default or viaBlock_copy()). The initial Apple implementation does in fact start__blockvariables on the stack and migrate them to the heap only as a result of aBlock_copy()operation.
但對(duì)于使用__weak修飾符的__block變量,則不會(huì)進(jìn)行拷貝;
循環(huán)引用
ARC末實(shí)現(xiàn)block表達(dá)式會(huì)自動(dòng)持有外部對(duì)象,若外部對(duì)象又持有該block對(duì)象,就會(huì)導(dǎo)致”循環(huán)引用“問題,如圖所示:
#import <Foundation/Foundation.h>
typedef void (^Block)(int);
@interface MyObject : NSObject
@property (nonatomic, strong) Block blk;
@end
@implementation MyObject
- (id)init {
self = [super init];
if (self) {
_blk = nil;
}
return self;
}
- (void)dealloc {
NSLog(@"dealloc");
}
@end
int main(int argc, const char * argv[]) {
MyObject *obj = [[MyObject alloc]init];
//方式一:使用__weak修飾符避免引用計(jì)數(shù)增加
__weak MyObject *weakObj = obj;
Block blk = ^(int count){
NSLog(@"count:%d, blk:%@", count, weakObj.blk);
//方式二:手動(dòng)nil釋放obj對(duì)象,解決循環(huán)引用
// NSLog(@"count:%d, blk:%@", count, obj.blk);
// obj.blk = nil;
};
NSLog(@"blk:%@", blk);
obj.blk = blk;
obj.blk(1);
return 0;
}

解決方法:
- 使用
__weak弱引用修飾符避免增加引用計(jì)數(shù) -
block表達(dá)式內(nèi)部手動(dòng)置持有對(duì)象為nil
小知識(shí)
xcode關(guān)閉arc
對(duì)于單個(gè)源文件關(guān)閉arc使用fno-objc-arc編譯標(biāo)志,對(duì)于整個(gè)工程則修改Objective-C Automatic Reference Counting修改為No;
Reference
《objective-C 高級(jí)編程 iOS與OSX多線程和內(nèi)存管理》
談Objective-C block的實(shí)現(xiàn)