Blocks是“帶有自動變量值的匿名函數(shù)”僚饭。本文通過Blocks的實現(xiàn)來理解Blocks呛谜。
本文目錄
- Blocks的實質(zhì)
- 截獲自動變量
- 修改Block外部變量的兩種方式
- Block存儲域
- 截獲對象
- __block變量和對象
- Block循環(huán)引用
- copy/release
使用工具:clang(LLVM編譯器)將OC代碼轉(zhuǎn)換成可讀的源代碼。
clang -rewrite-objc 源代碼文件名
Block的實質(zhì)
Block的實質(zhì)就是OC對象傍衡,Block函數(shù)代碼實際上被作為簡單的C語言函數(shù)來處理辛慰。
首先寫一段最簡單的Blocks代碼
int main(int argc, const char * argv[]) {
//聲明并定義一個block對象
void (^blk)(void) = ^{
printf("Hello,world!\n");
};
//調(diào)用block對象
blk();
return 0;
}
轉(zhuǎn)換成C代碼之后是這樣区匠,Block實際上是由結(jié)構(gòu)體聲明的。
struct __block_impl {
void *isa;//這里與OC中類對象一樣,指針指向的是類對象
int Flags;//標(biāo)志
int Reserved;//版本升級所需要的區(qū)域
void *FuncPtr;//函數(shù)指針
};
static struct __main_block_desc_0 {
size_t reserved;//保留區(qū)域
size_t Block_size;//Block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
//結(jié)構(gòu)體__block_impl和__main_block_desc_0組成了最簡單的block結(jié)構(gòu)體__main_block_impl_0
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//構(gòu)造函數(shù)驰弄,第一個參數(shù)是函數(shù)指針麻汰,第二個參數(shù)是描述信息結(jié)構(gòu)體
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;//定義block的類型,總共有三種戚篙,全局的靜態(tài)block五鲫,棧中的block,堆中的block岔擂。
impl.Flags = flags;//初始化flag
impl.FuncPtr = fp;//傳遞函數(shù)地址
Desc = desc;//初始化__main_block_desc_0結(jié)構(gòu)體
}
};
//block中的函數(shù)聲明
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Hello,world!\n");
}
//main函數(shù)
int main(int argc, const char * argv[]) {
//定義一個void *的blk指針位喂,等號右邊是使用__main_block_impl_0的構(gòu)造函數(shù)進行初始化,傳入的第一個參數(shù)是函數(shù)指針乱灵,第二個參數(shù)是描述信息結(jié)構(gòu)體
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
//調(diào)用函數(shù)指針塑崖,執(zhí)行函數(shù)
//(void (*)(__block_impl *))這部分是將FuncPtr轉(zhuǎn)換成該類型的函數(shù)指針,返回值為void *痛倚,傳參為void规婆,編譯器會在傳參前添加一個傳遞結(jié)構(gòu)體自身的指針
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
通過上面的源代碼,可以很清晰的了解到Block是如何實現(xiàn)的蝉稳,而在Block的結(jié)構(gòu)體__block_impl中聋呢,發(fā)現(xiàn)了isa指針,與基于objc_object結(jié)構(gòu)體的OC類對象結(jié)構(gòu)體一樣颠区,所以削锰,Block其實就是一個OC對象。
截獲自動變量
再來看稍微復(fù)雜一點的代碼
int main(int argc, const char * argv[]) {
int dmy = 256;//沒有被block使用到的變量
int val = 10;//被block捕獲的變量
const char *fmt = "val = %d\n";//被block捕獲的變量
void (^blk)(void) = ^{
printf(fmt, val);//使用fmt字符串打印val變量
};
//定義完block對象后毕莱,首先修改val變量器贩,看修改后block區(qū)塊里捕獲的對象是否也修改
val = 2;
//同樣修改fmt指針指向的常量字符串
fmt = "These values were changed. val = %d\n";
//調(diào)用block函數(shù)blk()
blk();
return 0;
}
轉(zhuǎn)換之后的C代碼
struct __main_block_impl_0 {
struct __block_impl impl;//block基本信息結(jié)構(gòu)體
struct __main_block_desc_0* Desc;//描述結(jié)構(gòu)體
//這里可以看到兩個函數(shù)內(nèi)的局部變量,被聲明到了block的結(jié)構(gòu)體中
const char *fmt;
int val;
//構(gòu)造函數(shù)朋截,其中構(gòu)造函數(shù)也初始化了fmt和val變量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//block函數(shù)定義
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; //拷貝block結(jié)構(gòu)體里的fmt變量
int val = __cself->val; //拷貝val變量
printf(fmt, val);
}
int main(int argc, const char * argv[]) {
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
//block對象blk的聲明和定義蛹稍,傳入函數(shù)指針和描述結(jié)構(gòu)體,以及fmt和val兩個變量部服,這里的傳值是拷貝
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
val = 2;
printf("val = %d\n", val);
fmt = "These values were changed. val = %d\n";
//調(diào)用blk函數(shù)
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
通過上面的代碼唆姐,可以發(fā)現(xiàn),Block在函數(shù)內(nèi)捕獲的fmt和val兩個自動變量均在Block結(jié)構(gòu)體內(nèi)重新聲明了相同類型和名稱的變量廓八,并且在構(gòu)造函數(shù)中通過拷貝的方式將值傳遞進來奉芦,也就是說,在定義blk對象的這條語句時剧蹂,就已經(jīng)將fmt和val的值傳遞進了Block結(jié)構(gòu)體實例對象內(nèi)声功,此時Block對象中保存的值是此時此刻fmt和val的值的拷貝,之后無論如何修改main函數(shù)中fmt和val的內(nèi)容宠叼,都不會影響到block中的保存的fmt和val的副本先巴。
當(dāng)然,若在函數(shù)內(nèi)創(chuàng)建一個指針變量,例如在上邊的代碼添加
int *p = &val;//將val的地址賦值給指針p
此時伸蚯,Block的結(jié)構(gòu)體內(nèi)也會申請一個int指針p摩渺,在構(gòu)造函數(shù)的參數(shù)列表,傳遞的也是指針類型剂邮,所以在Block對象里修改p的地址對應(yīng)的內(nèi)容時摇幻,main函數(shù)中的指針p和val的值也會被修改。
在Blocks中抗斤,截獲自動變量的方法并沒有實現(xiàn)對C語言數(shù)組的截獲囚企,使用指針可以解決該問題丈咐。
const char text[] = "Hello";
改成 const char *text = "Hello";
修改Block外部變量的兩種方式
Block類型變量
- 自動變量
- 函數(shù)參數(shù)
- 靜態(tài)變量
- 靜態(tài)全局變量
- 全局變量
如果想修改一個外部變量瑞眼,有兩種方式可以實現(xiàn)。
方法一:靜態(tài)變量棵逊、靜態(tài)全局變量伤疙、全局變量
這三種變量,靜態(tài)全局變量和全局變量的訪問方式?jīng)]有任何改變辆影,Blocks可以直接使用徒像。靜態(tài)變量則是通過指針的方式傳遞。
寫一段包括這三種變量的代碼
int g_val = 1;//全局變量
static int gs_val = 2;//全局靜態(tài)變量
int main(int argc, const char * argv[]) {
//靜態(tài)變量
static int s_val = 3;
//block代碼塊蛙讥,截獲這三種變量
void (^blk)(void) = ^{
g_val *= 1;
gs_val *= 2;
s_val *= 3;
};
轉(zhuǎn)換后的代碼如下:
int g_val = 1;
static int gs_val = 2;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *s_val;//對于靜態(tài)變量s_val锯蛀,使用s_val的指針對其進行訪問
//在構(gòu)造函數(shù)里初始化s_val
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_s_val, int flags=0) : s_val(_s_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 *s_val = __cself->s_val; // bound by copy
g_val *= 1;//對全局變量和全局靜態(tài)變量的訪問與轉(zhuǎn)換前沒有任何區(qū)別
gs_val *= 2;//...
(*s_val) *= 3;//使用指針的方式訪問s_val
}
int main(int argc, const char * argv[]) {
static int s_val = 3;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &s_val));
return 0;
}
對于靜態(tài)變量s_val,使用指針對其訪問次慢,保存靜態(tài)變量s_val的指針旁涤,傳遞給__main_block_impl_0結(jié)構(gòu)體的構(gòu)造函數(shù)并保存。這是超出作用域使用變量的最簡單方法迫像。
而對于自動變量來說劈愚,如果使用指針的方式進行訪問,當(dāng)變量作用域結(jié)束的同時闻妓,原來的自動變量被廢棄菌羽,此時Block中超過變量作用域的變量則不能通過指針訪問,訪問結(jié)果是未定義的由缆。解決這個問題注祖,則可以使用__block說明符。
方法二:__block存儲域類說明符(__block storage-class-specifier)
C語言中有一下存儲域類說明符:
- typedef
- extern
- static
- auto
- register
__block說明符類似于static均唉、auto和register說明符氓轰,它們用于指定將變量值設(shè)置到哪個存儲域中。如浸卦,auto表示作為自動變量存儲在棧中署鸡,static表示作為靜態(tài)變量存儲在數(shù)據(jù)區(qū)域中。
下面來寫一段__block說明符的代碼。
int main(int argc, const char * argv[]) {
//為val變量添加__block說明符
__block int val = 10;
//現(xiàn)在可以在Block代碼塊中為val正確賦值
void (^blk)(void) = ^{
val = 1;
};
return 0;
}
轉(zhuǎn)換后的源代碼如下:
//新的結(jié)構(gòu)體靴庆,用來保存用__block修飾的對象
struct __Block_byref_val_0 {
void *__isa;//結(jié)構(gòu)體對象
__Block_byref_val_0 *__forwarding;//指向自身
int __flags;
int __size;
int val;//保存被__block修飾的val變量的值
};
//Block的結(jié)構(gòu)體
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; //保存了用__block修飾的變量的結(jié)構(gòu)體
//構(gòu)造函數(shù)时捌,使用_val->__forwarding來初始化val,這個設(shè)計的用意在后邊說明
__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;
}
};
//Block代碼塊的內(nèi)容炉抒,該例為對val進行賦值
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) = 1;
}
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*/);}
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[]) {
//使用__Block_byref_val_0結(jié)構(gòu)體來創(chuàng)建val對象奢讨,并對每一個值進行初始化
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
//定義Block對象
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
return 0;
}
上面的代碼可以看到,使用__block說明符修飾的自動變量焰薄,在源代碼中實際上是一個__Block_byref_val_0的結(jié)構(gòu)體實例拿诸,在__main_block_impl_0中以指針的形式持有,對該變量的修改實際上是通過結(jié)構(gòu)體中的__forwarding指針指向的結(jié)構(gòu)體實例塞茅,對val值進行修改亩码。
Block存儲域
在之前的代碼中,可以看到在__main_block_impl_0結(jié)構(gòu)體的構(gòu)造函數(shù)中野瘦,將結(jié)構(gòu)體中的isa變量賦值為_NSConcreteStackBlock描沟,而與之類似的還有_NSConcreteGlobalBlock和_NSConcreteMallocBlock類。
- _NSConcreteStackBlock:該類的對象設(shè)置在棧上
- _NSConcreteGlobalBlock:該類對象設(shè)置在程序的數(shù)據(jù)區(qū)域(.data區(qū))中
- _NSConcreteMallocBlock:該類對象則設(shè)置在由malloc函數(shù)分配的內(nèi)存塊(堆)中
其中鞭光,當(dāng)全局變量區(qū)域定義Block代碼塊和Block語法的表達式不使用截獲的自動變量時吏廉,Block即是_NSConcreteGlobalBlock對象。
雖然通過clang轉(zhuǎn)換的源代碼通常是_NSConcreteStackBlock對象惰许,但實現(xiàn)上卻有不同席覆。
將Block從棧復(fù)制到堆上
當(dāng)Block語法記述的變量作用域結(jié)束時,棧上的Block和__block變量都會被廢棄汹买,此時通過Blocks提供的復(fù)制方法佩伤,將Block和__block變量從棧上復(fù)制到堆上,那么即使記述Block變量的作用域結(jié)束卦睹,堆上的Block還可以繼續(xù)存在畦戒。
復(fù)制到堆上的Block將_NSConcreteMallocBlock類對象寫入Block用結(jié)構(gòu)體實例的成員變量isa。
首先看ARC有效時结序,自動生成的將Block從棧復(fù)制到堆上的代碼障斋。
typedef int (^blk_t)(int);
blk_t func(int rate){
return ^(int count){return rate * count;};
}
轉(zhuǎn)換過后的源代碼:
blk_t func(int rate){
blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);
//objc_retainBlock函數(shù)實際上就是_Block_copy函數(shù),該函數(shù)將棧上的Block復(fù)制到堆上徐鹤,復(fù)制后垃环,將堆上的地址作為指針賦值給變量tmp。
//這里tmp是typedef int (^blk_t)(int)返敬,它的源代碼實際上是typedef int (*blk_t)(int)函數(shù)指針遂庄,所以可以存儲指針。
tmp = objc_retainBlock(tmp);
//將堆上的Block作為Objective-C對象劲赠,注冊到autoreleasepool中涛目,然后返回該對象秸谢。
return objc_autoreleaseReturnValue(tmp);
}
上面是編譯器自動生成的代碼,而編譯器在以下情況無法自動生成復(fù)制到堆上的代碼
- 向方法或函數(shù)的參數(shù)傳遞Block時
以下方法或函數(shù)不用手動賦值
- Cocoa框架的方法且方法名中含有usingBlock等時霹肝,如NSArray類的enumerateObjectsUsingBlock實例方法以及dispatch_async函數(shù)時估蹄,但在NSArray的initWithObjects方法中不能自動生成。
- Grand Central Dispath的API
程序員可以手動調(diào)用Block的copy方法沫换。
如
typedef int (^blk_t)(int);
blk_t blk = ^(int count){return rate * count;};
blk = [blk copy];
對于在不同區(qū)域的Block調(diào)用copy方法所進行的動作如下表:
Block的類 | 副本源的配置存儲域 | 復(fù)制效果 |
---|---|---|
_NSConcreteStackBlock | 棧 | 從棧復(fù)制到堆 |
_NSConcreteGlobalBlock | 程序的數(shù)據(jù)區(qū)域 | 什么也不做 |
_NSConcreteMallocBlock | 堆 | 引用計數(shù)增加 |
不管Block配置在何處臭蚁,用copy方法復(fù)制都不會引起任何問題。在不確定時調(diào)用copy方法即可讯赏。
__block變量存儲域與__forwarding指針
前面提到垮兑,__block對象會被定義為__Block_byref_val_0的結(jié)構(gòu)體實例,并成為__main_block_impl_0結(jié)構(gòu)體實例的一個成員變量漱挎,所以當(dāng)Block被從棧復(fù)制到堆上時系枪,__block變量也會受到影響∈队#總結(jié)如下表:
__block變量的配置存儲域 | Block從棧復(fù)制到堆時的影響 |
---|---|
棧 | 從棧復(fù)制到堆并被Block持有 |
堆 | 被Block持有 |
在一個Block中使用__block變量嗤无,當(dāng)該Block從棧復(fù)制到堆時震束,這些__block變量也全部從棧復(fù)制到堆怜庸。此時,Block持有__block變量垢村。
__forwarding指針
在__block的結(jié)構(gòu)體中割疾,有一個__forwarding成員變量,在轉(zhuǎn)換成C的源代碼中可以看到嘉栓,所有對__block變量的操作宏榕,均是通過__forwarding變量來操作,使用該變量侵佃,不管__block變量配置在棧上還是堆上麻昼,都能夠正確地訪問該變量。
最初建__block變量時:
- __forwarding指針指向的是該結(jié)構(gòu)體實例自身馋辈。
當(dāng)Block從棧復(fù)制到堆上時抚芦, __block變量也從棧復(fù)制到堆上,此時:
- 會將成員變量__forwarding的值替換為堆上的__block變量的結(jié)構(gòu)體實例的地址迈螟。
- Block語法外的__block變量的__forwarding指針也會指向復(fù)制到堆中的__block變量的結(jié)構(gòu)體實例地址叉抡。
整個過程如下圖所示:
通過該功能,無論是在Block語法中答毫、Block語法外使用__block變量褥民,還是__block變量配置在棧上或堆上,都可以順利地訪問同一個__block變量洗搂。
截獲對象
在Block語法中調(diào)用語法外的對象消返,如下代碼:
blk_t blk;
{
id array = [[NSMutableArray alloc] init];
blk = [^(id obj) {
[array addObject:obj];
NSLog(@"array count = %ld", [array count]);
} copy];
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
執(zhí)行結(jié)果為
array count = 1
array count = 2
array count = 3
在執(zhí)行最后三行代碼的時候载弄,array已經(jīng)跑出了變量作用域,此時array對象被廢棄撵颊,但結(jié)果運行正常侦锯。
轉(zhuǎn)換后的源代碼如下,這里僅放了部分源代碼:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id array;//強持有
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 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};
實際上在轉(zhuǎn)換后的源代碼的Block用結(jié)構(gòu)體中秦驯,聲明了一個id array成員對象尺碰,默認是__strong修飾,該成員對象以強持有的方式持有Block語法外的array變量译隘,這就保證了在array變量被廢棄時亲桥,Block的成員對象array仍然持有著NSMutableArray變量,所以代碼可以正常運行固耘。
copy和dispose
上面的源代碼中新增了兩個函數(shù)题篷,__main_block_copy_0和__main_block_dispose_0,runtime通過這兩個函數(shù)在運行過程中將Block從棧復(fù)制到堆上以及將堆上的Block廢棄厅目。
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src)
{
_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
這里邊的_Block_object_assign函數(shù)相當(dāng)于retain函數(shù)番枚,將對象賦值在對象類型的結(jié)構(gòu)體成員變量中。
_Block_object_dispose相當(dāng)于release函數(shù)损敷,釋放賦值在對象類型的結(jié)構(gòu)體成員變量中的對象葫笼。
copy和dispose函數(shù)的調(diào)用時機
函數(shù) | 調(diào)用時機 |
---|---|
copy函數(shù) | 棧上的Block復(fù)制到堆時 |
dispose函數(shù) | 堆上的Block被廢棄時 |
Block從棧復(fù)制到堆的時機
- 調(diào)用Block的copy實例方法時
- Block作為函數(shù)返回值返回時
- 將Block賦值給附有__strong修飾符id類型的類或Block類型成員變量時
- 在方法名中含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API中傳遞Block時
在ARC有效時,上述的2與3情況拗馒,編譯器會自動地將對象的Block作為參數(shù)并調(diào)用_Block_copy函數(shù)路星,這與調(diào)用Block的copy實例方法效果相同。在4的情況下诱桂,在調(diào)用的方法內(nèi)部會對傳遞來的Block調(diào)用Block的copy實例方法或者_Block_copy函數(shù)洋丐。
這里的第3條,在ARC有效的情況下挥等,如執(zhí)行這樣的語句
void (^blk)(void) = ^{
...
};
NSLog(@"%@", blk);
上面定義的blk對象友绝,實際上就是附有__strong修飾符的id類型,所以NSLog語句執(zhí)行的結(jié)果就是blk是一個NSConcreteMallocBLock肝劲。
BLOCK_FIELD_IS_OBJECT
在上面兩個函數(shù)中迁客,可以注意到一個參數(shù)BLOCK_FIELD_IS_OBJECT,在前面使用__block的例子中生成的源代碼里涡相,也出現(xiàn)了類似的參數(shù)BLOCK_FIELD_IS_BYREF哲泊。
- BLOCK_FIELD_IS_OBJECT --> 對象
- BLOCK_FIELD_IS_BYREF --> __block變量
編譯器通過該參數(shù)區(qū)分copy函數(shù)和dispose函數(shù)的對象類型是對象還是__block變量。
這里通過runtime的源代碼中可以查看到枚舉的全部定義
enum {
// see function implementation for a more complete description of these fields and combinations
BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ...
BLOCK_FIELD_IS_BLOCK = 7, // a block variable
BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers
BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.
};
在ARC有效的情況下催蝗,編譯器也會有不自動調(diào)用copy的情況切威,除了以下幾種情況外,推薦手動調(diào)用copy實例方法丙号。
- Block作為函數(shù)返回值返回時
- 將Block賦值給類的附有__strong修飾符的id類型或Block類型成員變量時
- 向方法名中含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API中傳遞Block時
__block變量和對象
__block說明符可指定任何類型的自動變量先朦。如下例子所示:
__block id __strong obj = [[NSObject alloc] init];
轉(zhuǎn)換后的源代碼:
struct __Block_byref_obj_0 {
void *__isa;
__Block_byref_obj_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
id obj;
};
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
//dst的地址加40,包括4個指針(isa缰冤,__forwarding,兩個函數(shù)指針)32個字節(jié)喳魏,兩個int為8個字節(jié)棉浸,將指針移動到obj的起始地址
//131是上面枚舉中BLOCK_FIELD_IS_OBJECT和BLOCK_BYREF_CALLER取或的結(jié)果
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
int main(int argc, const char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_obj_0 obj = {
(void*)0,
(__Block_byref_obj_0 *)&obj,
33554432,
sizeof(__Block_byref_obj_0),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
return 0;
}
在__block變量為附有__strong修飾符的id類型或?qū)ο箢愋妥詣幼兞康那樾蜗聲l(fā)生與在Block中使用附有__strong修飾符的id類型或?qū)ο箢愋妥詣幼兞康那闆r下相同的過程。當(dāng)__block變量從棧復(fù)制到堆時刺彩,使用_Block_object_assign函數(shù)迷郑,廢棄時使用_Block_object_dispose函數(shù)。
使用__weak說明符的__block變量
當(dāng)使用__weak說明符修飾的變量在作用域結(jié)束后创倔,即是Block代碼塊中使用了該變量嗡害,也會被釋放、廢棄畦攘,變量會變成nil霸妹。
__unsafe_retained說明符
__unsafe_retained說明符表明不歸編譯器管理對象,被它修飾的變量不會像__strong或__weak修飾符那樣處理知押,注意不要通過懸垂指針訪問已被廢棄的對象叹螟。
不能同時使用__autoreleasing和__block
Block循環(huán)引用
這個很容易理解,在類中的Block語法中直接調(diào)用self或者調(diào)用類的成員對象台盯,會導(dǎo)致Block強持有類罢绽,但同時類也強持有Block對象,導(dǎo)致循環(huán)引用爷恳。
解決方法1
在Block語法外聲明一個self的弱引用有缆,該方法只在ARC有效的情況下游泳
__weak typeof(self) weakSelf = self;
上述方法讓Block以weak的方式持有self象踊,這樣就不會引起循環(huán)引用温亲,導(dǎo)致內(nèi)存泄漏。
而這樣會引發(fā)另一個問題杯矩,Block中的代碼不是立即執(zhí)行栈虚,在執(zhí)行的時候可能該weak指針已經(jīng)被銷毀了,所以self會變成nil史隆,這樣需要在Block語法內(nèi)部再添加一局
__strong typeof(weakSelf) strongSelf = weakSelf;
在Block語法內(nèi)部使用strongSelf來獲取self的相關(guān)屬性和方法魂务,當(dāng)外部self被release后粘姜,strongSelf在block的語法局部還持有該self孤紧,當(dāng)Block語法執(zhí)行完畢后,strongSelf的生命周期結(jié)束被release拒秘,此時self的引用計數(shù)為0号显,對象被銷毀臭猜。
解決方法2
使用__block蔑歌,在成員方法內(nèi)
__block id tmp = self;
blk_ = ^ {
NSLog(@"self = %@", tmp);
tmp = nil;
};
但該方法有局限性,必須保證Block對象的代碼執(zhí)行一次。
使用__block變量的優(yōu)點如下:
- 通過__block變量可控制對象的持有期間
- 在不能使用__weak修飾符的環(huán)境中不使用__unsafe_unretained修飾符即可
copy/release
在ARC無效時,需要手動將Block從棧復(fù)制到堆上绊汹,同樣需要手動釋放Block。
推薦使用copy實例方法代替retain實例方法。
使用release方法釋放Block浑此。
同樣可以使用C語言的Block_copy函數(shù)和Block_release函數(shù),它與copy和release方法效果相同滞详。
ARC無效時的__block
在ARC無效時凛俱,__block說明符被用來避免Block中的循環(huán)引用。當(dāng)Block從棧復(fù)制到堆時料饥,若Block使用的變量為附有__block說明符的id類型或?qū)ο箢愋偷淖詣幼兞科讶粫籸etain(不會被retain則意味著不會導(dǎo)致循環(huán)引用),而若沒有被__block說明符修飾岸啡,則會被retain原叮。