Block源碼解析和深入理解
Block的本質(zhì)
Block是"帶有自動(dòng)變量值的匿名函數(shù)".
我們通過(guò)Clang(LLVM編譯器)來(lái)將OC的代碼轉(zhuǎn)換成C++源碼的形式,通過(guò)如下命令:
clang -rewrite-objc 源代碼文件名
下面,我們要轉(zhuǎn)換的Block語(yǔ)法
int main(int argc, const char * argv[]) {
void (^blk)(void) = ^{
printf("Block\n");
};
blk();
return 0;
}
該源代碼通過(guò)Clang 可變換為以下形式:
/*
__block_impl (block)結(jié)構(gòu)體聲明
*/
struct __block_impl {
void *isa; // isa 指針,指向父類的實(shí)例。void * 相當(dāng)于 id 是個(gè)實(shí)例。
int Flags; //
int Reserved;
void *FuncPtr; //函數(shù)指針 指向block代碼塊的實(shí)現(xiàn)函數(shù)
};
/*
__main_block_impl_0 匿名的block 結(jié)構(gòu)體聲明和實(shí)現(xiàn)
*/
struct __main_block_impl_0 {
struct __block_impl impl;//block 的結(jié)構(gòu)體實(shí)例
struct __main_block_desc_0* Desc; //block des的指針 指向block的詳情
/*
__main_block_impl_0 結(jié)構(gòu)體構(gòu)造函數(shù)實(shí)現(xiàn)
*/
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock; // 初始化 block 實(shí)例屬性 isa ,表示該block 是 _NSConcreteStackBlock (棧)類型的代碼塊
impl.Flags = flags;
impl.FuncPtr = fp;// block 具體的函數(shù)實(shí)現(xiàn)指針
Desc = desc;//desc 指針
}
};
/*
匿名block 具體的函數(shù)實(shí)現(xiàn)
*/
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Block\n");
}
/*
匿名block desc 指針的具體函數(shù)實(shí)現(xiàn),對(duì)block(__main_block_impl_0) 結(jié)構(gòu)體實(shí)例的大小進(jìn)行初始化
*/
static struct __main_block_desc_0 {
size_t reserved; // 升級(jí)所需區(qū)域
size_t Block_size;//block 實(shí)際內(nèi)存大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
/*
把多余的轉(zhuǎn)換去掉,看起來(lái)就比較清楚了:
第一部分:block的初始化
__main_block_func_0: 參數(shù)一 是block語(yǔ)法轉(zhuǎn)換的C語(yǔ)言函數(shù)指針。
__main_block_desc_0_DATA: 參數(shù)二 作為靜態(tài)全局變量初始化的 __main_block_desc_0 結(jié)構(gòu)體實(shí)例指針
struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;
第二部分:
block的執(zhí)行: blk()
去掉轉(zhuǎn)化部分:
(*blk -> imp.FuncPtr)(blk);
這就是簡(jiǎn)單地使用函數(shù)指針調(diào)用函數(shù)。由Block語(yǔ)法轉(zhuǎn)換的 __main_block_func_0 函數(shù)的指針被賦值成員變量FuncPtr中,另外 __main_block_func_0的函數(shù)的參數(shù) __cself 指向Block的值,通過(guò)源碼可以看出 Block 正式作為參數(shù)進(jìn)行傳遞的。
*/
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;
}
針對(duì)源碼的解釋 大部分在代碼中都注釋了。需要特別指出的是:
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
中的參數(shù) __cself 是指向 __main_block_impl_0 的指針,及匿名block 自身。
擴(kuò)展:該句源碼類似如 OC 中的方法消息傳遞,OC中每個(gè)方法都默認(rèn)帶兩個(gè)參數(shù) 一個(gè)是指向自身的實(shí)例self 一個(gè)是該方法的SEL 對(duì)象。
例如:
- (void) method: (int)argc{
NLog(@"%p %d \n",self,arg)
}
Objective - C 編譯器同C++的方法一樣,也將該方法作為C語(yǔ)言的函數(shù)來(lái)處理.源碼如下:
/*
方法中 在轉(zhuǎn)換成源碼后 自動(dòng)的添加了self, _cmd兩個(gè)參數(shù)
*/
void _I_MyObjct_method_(struct Myobject *self,SEL _cmd, int arg){
NSLog (@"%p %d \n",self,arg);
}
截獲自動(dòng)變量值(局部變量)
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int val; //局部變量跟block外的類型一直
const char *fmt; //跟block外的類型一致
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _dmy, int _val, const char *_fmt, int flags=0) : dmy(_dmy), val(_val), fmt(_fmt) {
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 //block 調(diào)用外部的局部變量 實(shí)際上 相當(dāng)于Copy 了一份 所以不會(huì)影響 局部變量的值 也不能修改值
const char *fmt = __cself->fmt; // bound by copy
printf("Block\n .. ,%d %s",dmy,val,fmt);
}
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 dmy = 256; //局部變量
int val = 10; // 局部變量
const char *fmt = "val = %d \n"; //局部變量
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val, fmt));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
源碼解析:block 在調(diào)用 外部局部變量的時(shí)候 其實(shí)是將外部局部變量 copy了一份 使用的 所以在沒(méi)有任何修飾符的時(shí)候是不可以修改外部局部變量的。
__block 說(shuō)明符
之前的分析中,block 無(wú)法改變被截獲的自動(dòng)變量的值。這樣極為不便:
解決這個(gè)問(wèn)題有兩種方法,
第一種:C 語(yǔ)言中有一個(gè)變量,允許block改成值。具體如下:
- 靜態(tài)變量
- 靜態(tài)全局變量
- 全局變量
雖然Block語(yǔ)法的匿名函數(shù)部分簡(jiǎn)單的轉(zhuǎn)換為了C語(yǔ)言函數(shù),但從這個(gè)C語(yǔ)言函數(shù)中訪問(wèn)靜態(tài)全局,全局變量并沒(méi)有任何改變,可直接使用。
但靜態(tài)變量的情況,轉(zhuǎn)換后的函數(shù)原本就設(shè)置在含有Block語(yǔ)法的函數(shù)外,所以無(wú)法從變量作用域訪問(wèn)。
看看這段代碼的源碼:
int global_val = 1;
static int static_global_val = 2;
int main(int argc, const char * argv[]) {
static int static_val = 3;
void (^blk)(void) = ^{
global_val += 1;
static_global_val += 2;
static_val += 3;
};
blk();
return 0;
}
該源代碼中使用了Block 改寫(xiě)靜態(tài)變量 靜態(tài)全局變量 全局變量。該源代碼轉(zhuǎn)換后如下:
int global_val = 1; //全局變量
static int static_global_val = 2; //靜態(tài)全局變量
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_val;//局部靜態(tài)變量 ---> 可以看出 跟局部變量不同 這邊是接受的指針
__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 // 改代碼跟局部變量 相似,實(shí)際上改變的是一個(gè) 復(fù)制后的指針.但該指針最終指向的 還是最初的變量值。
global_val += 1;
static_global_val += 2;
(*static_val) += 3;
}
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[]) {
static int static_val = 3;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
分析該源碼:發(fā)現(xiàn)無(wú)論是全局 還是 靜態(tài)全局 都可以在Block中直接訪問(wèn) 修改變量值。
然而,靜態(tài)局部變量,貌似也可以正常訪問(wèn),其調(diào)用原理,跟之前的局部變量的調(diào)用相似,唯一的不同是,在Block中調(diào)用的是 指向該變量的指針,并且是賦值了一份指針(但還是最終指向原來(lái)的變量)。所以我們可以在Block中改變?cè)碜兞康闹怠?br>
這樣就有個(gè)疑問(wèn),我們?yōu)槭裁床皇褂渺o態(tài)局部變量,來(lái)使用去自動(dòng)變量(局部變量)的訪問(wèn)呢?
原因:在該靜態(tài)局部變量,有變量作用域,當(dāng)block超出了該作用域,執(zhí)行的時(shí)候,其內(nèi)部調(diào)用的靜態(tài)局部變量會(huì)被廢棄,我們就無(wú)法調(diào)用到。因此Block中超出變量作用域而存在的變量同靜態(tài)變量一樣,將不能通過(guò)指針訪問(wèn)原來(lái)的自動(dòng)變量。
解決Block 中不能保存值這一問(wèn)題的第二個(gè)方法是使用__block
int main(int argc, const char * argv[]) {
__block int val = 3;
void (^blk)(void) = ^{
val = 1;
};
blk();
return 0;
}
將上面代碼用 clang 轉(zhuǎn)化后如下:
/*
__block 轉(zhuǎn)化成了結(jié)構(gòu)體
*/
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding; //相當(dāng)于一個(gè)指向源變量的指針
int __flags;
int __size;
int val; //相當(dāng)于源變量
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref //持有源變量的結(jié)構(gòu)體實(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; // block 為棧類型
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 ;類似于 靜態(tài)局部變量 都是copy 一份指向源變量的結(jié)構(gòu)體指針。
(val->__forwarding->val) = 1;//通過(guò)訪問(wèn) __block 結(jié)構(gòu)體 成員變量 __forwarding 來(lái)訪問(wèn)源變量
}
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[]) {
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 3};
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_byref_val_0 結(jié)構(gòu)體實(shí)例的成員變量__forwarding持有指向該實(shí)例自身的指針。通過(guò)成員變量__forwarding訪問(wèn)成員變量val。(成員變量val是該實(shí)例自身持有的變量,它相當(dāng)于原自動(dòng)變量)
如圖所示:

Block存儲(chǔ)域
Block 是Objective-C對(duì)象。上面我們所創(chuàng)建的block類 都為_(kāi)NSConcreteStackBlock.
由上面我們提到的源碼可以知道:
impl.isa = &_NSConcreteStackBlock
根據(jù) block 結(jié)構(gòu)體實(shí)例的 isa 指針進(jìn)行分類:
- _NSConcreteStackBlock //不難看出 其存儲(chǔ)域在棧上
- _NSConcreteGlobalBlock // 其存儲(chǔ)域 在全局
- _NSConcreteMallocBlock // 其存儲(chǔ)域 在堆上
詳細(xì)分類如圖所示:
_NSConcreteGlobalBlock: 存在的情況:
- 記述全局變量的地方有Block語(yǔ)法時(shí)
- Block語(yǔ)法的表達(dá)式中不使用應(yīng)截獲的自動(dòng)變量時(shí)
以上情況Block 為 全局類對(duì)象。除此之外Block語(yǔ)法生成的Block為棧類對(duì)象,
例如(一):
/*
在下面的block中由于for循環(huán)的值 一直在變 所以Block截獲的局部變量一直在變。
*/
typedef int (^blk_t)(int);
for (int rate = 0;rate < 10; ++rate){
blk_t blk = ^(int count){
return rate * count;
}
}
轉(zhuǎn)化為源碼如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int rate;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _rate, int flags=0) : rate(_rate) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
由此可見(jiàn) 雖然block 聲明在全局中,但由于block初始化的時(shí)候調(diào)用了局部變量,所以該block創(chuàng)建成棧類型的。
_NSConcreteMallocBlock :存在的情況
在分析之前我們看下之前遺留的問(wèn)題:
- Block 超出變量作用域可存在的原因
- __block變量用結(jié)構(gòu)體成員變量__forwarding存在的原因
配置在全局變量上的Block,從變量作用域外也可以通過(guò)指針安全的使用。但設(shè)置在棧上的Blcok,如果其變量作用域結(jié)束,該Block就被廢棄,同樣的__block也配置在棧上,所以其所屬的變量作用域結(jié)束,則該__block變量也會(huì)被廢棄。
Block提供了將Block和__block變量從棧上復(fù)制到堆上的方法來(lái)解決這個(gè)問(wèn)題
而__block 變量用結(jié)構(gòu)體成員變量__forwarding可以實(shí)現(xiàn)無(wú)論__block變量配置在棧上還是堆上都能夠正確的訪問(wèn)__block變量。
深入理解blocks提供的復(fù)制方法究竟是啥?
實(shí)際上當(dāng)ARC有效時(shí),編譯器會(huì)進(jìn)行判斷自動(dòng)的將block從棧上復(fù)制到堆上
如:
typedef int (^blk_t)(int);
blk_t func (int count){
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
);
tmp = objc_retainBlock(tmp);
return objc_autoreleaseReturnValue(tmp);
}
分析源碼:從源碼來(lái)看 在ARC狀態(tài)下 block復(fù)制到堆上 實(shí)際上其引用計(jì)數(shù)增加了。
__block變量的存儲(chǔ)域
當(dāng)block從棧中 復(fù)制到堆上時(shí),由于block持有__block變量,所以其__blcok變量也會(huì)從棧中復(fù)制到堆上,所以當(dāng)block超出作用域調(diào)用__block變量也可以成功。這是和靜態(tài)局部變量最大的區(qū)別。而靜態(tài)局部變量,在block從棧中復(fù)制到堆上時(shí),由于block不持有變量,所以靜態(tài)局部變量不 會(huì)復(fù)制到堆上,其作用域沒(méi)變。故出作用域調(diào)用會(huì)崩潰。
如圖所示:
截獲對(duì)象
下面我們將id對(duì)象類型的局部變量 在block中調(diào)用。id類型的對(duì)象 默認(rèn)修飾符 都是__strong類型的。
typedef void (^blk_t)(id);
blk_t blk;
int main(int argc, const char * argv[]) {
{
id array = [[NSMutableArray alloc]init]; // __strong 類型修改的局部變量
blk = [^(id objc){
[array addObject:objc];
NSLog(@"array count = %ld",[array count]);
} copy];
}
blk(@"ww");
return 0;
}
分析 :按理來(lái)說(shuō) array 對(duì)象出了大括號(hào)作用域,強(qiáng)引用失效 其對(duì)象就會(huì)廢棄。但改代碼運(yùn)行正常。那么就意味著,array對(duì)象出大括號(hào)作用域時(shí),沒(méi)有被廢棄 ,仍能正常訪問(wèn)。那么是什么原因呢,我們看下Clang之后的源碼.
typedef void (*blk_t)(id);
blk_t blk;
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_func_0(struct __main_block_impl_0 *__cself, id objc) {
id array = __cself->array; // bound by copy //復(fù)制一份指針 賦值
((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)objc);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_0b_9hq6xqxs5gjcxx5j_skhh8n00000gn_T_main_1808b3_mi_0,((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
}
/*
關(guān)鍵方法:該方法 相當(dāng)于ARC 中的 retain方法,將對(duì)象的引用計(jì)數(shù)加一。但該方法除引用計(jì)數(shù)加一外,還有一個(gè)操作就是將block 從棧上復(fù)制到堆上,從而可以出作用域,調(diào)用id __strong修飾類型的對(duì)象。
*/
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*/);}
/*
dispose 相當(dāng)于ARC 模式下的 release 將對(duì)象的引用計(jì)數(shù)減一。引用計(jì)數(shù)減一得同時(shí),將堆上的block 廢棄掉。
*/
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};
int main(int argc, const char * argv[]) {
{
id array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
blk = (blk_t)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344)), sel_registerName("copy"));///必須調(diào)用block 的copy 方法才能正常運(yùn)行
}
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, (NSString *)&__NSConstantStringImpl__var_folders_0b_9hq6xqxs5gjcxx5j_skhh8n00000gn_T_main_1808b3_mi_1);
return 0;
}
//從上面的源碼可以發(fā)現(xiàn):前提:當(dāng)block調(diào)用copy方法,從棧中復(fù)制到對(duì)象,當(dāng)Block調(diào)用的局部變量是個(gè)id對(duì)象的時(shí)候,該對(duì)象在block中自動(dòng)的引用計(jì)數(shù)加一,并且該block持有該對(duì)象,也就是說(shuō),對(duì)象出了作用域也能被調(diào)用,知道block 從堆上廢棄掉為止。如果block 的最后沒(méi)有調(diào)用copy,那么該對(duì)象值,也會(huì)隨著作用域的結(jié)束而被廢棄。
總結(jié):
什么時(shí)候棧上的Block會(huì)復(fù)制到堆上呢?
- 調(diào)用Block的copy實(shí)例方法時(shí)。
- Block作為函數(shù)返回值返回時(shí)。
- 將Block賦值給附有__strong修飾符id類型的類或者Block類型成員變量時(shí)。
- 在方法名中含有usingBlock的cocoa框架方法或者GCD的API中傳遞Block時(shí)。
對(duì)象和__block的區(qū)別?
- 如果調(diào)用對(duì)象的Block,沒(méi)有調(diào)用Copy 或者不在棧上,那么該對(duì)象出作用域就會(huì)被釋放。
- 如果調(diào)用對(duì)象的Block,調(diào)用了Copy,或者Block在堆上,那么該對(duì)象的作用域跟使用__block修飾的變量的作用域一直,都會(huì)被Block所持有,并且生命周期,會(huì)隨著B(niǎo)lock的廢除,而釋放。
因此當(dāng)Block中使用對(duì)象類型的自動(dòng)變量時(shí),除以下情形外,推薦調(diào)用Block的copy實(shí)例方法??!
- block作為函數(shù)返回值返回時(shí)。
- Block賦值給類的附加__strong修飾符的id類型或者Block類型的成員變量時(shí)。
- 向方法名中含有usingBlock的Cocoa框架方法或者GCD的API中傳遞Block時(shí)。
__block變量和對(duì)象
從前面我們看到__block可以修飾任意類型:
- 當(dāng)然包括id對(duì)象__strong類型了,其原理是相同的:
當(dāng) block 從棧上復(fù)制到 堆上時(shí),__block 所修飾的自動(dòng)變量也會(huì)從棧上復(fù)制到堆上,使用_Block_objct_assign函數(shù),持有賦值給__block變量的對(duì)象。當(dāng) block 廢棄時(shí),__block所修飾的自動(dòng)變量,也會(huì)通過(guò)函數(shù)_Block_objct_dispose ,釋放掉__block變量的對(duì)象。 - 當(dāng)__weak修飾符修飾時(shí),由于__weak修飾的自動(dòng)變量出作用域后會(huì)廢棄 自動(dòng)置nil,所以當(dāng)block調(diào)用的時(shí)候,其實(shí)是調(diào)用的nil對(duì)象所以不會(huì)崩潰,但取不到值。
- 當(dāng)__block __weak 同時(shí)修飾自動(dòng)變量時(shí),還是因?yàn)開(kāi)_weak(不持有對(duì)象)的原因,當(dāng) block 從棧上復(fù)制到堆上時(shí),__block變量復(fù)制到堆上的是一個(gè)nil值,所以對(duì)該變量進(jìn)行的操作都是無(wú)效的。
- 當(dāng)__block 和 __unsafe__unretained 同時(shí)修飾變量時(shí),跟__weak不同,當(dāng)__unsafe__unretained,所修飾的對(duì)象邊nil 時(shí) 該變量不會(huì)自動(dòng)置nil,而是變成野指針,所以當(dāng)block 從棧上復(fù)制到堆上時(shí),實(shí)際上__block變量是一個(gè)野指針,所以當(dāng)調(diào)用的時(shí)候回出錯(cuò),導(dǎo)致程序崩潰
- __block 和 __autoreleasing 修飾跟 上面的__unsafe__unretained是一樣的。
Block 循環(huán)引用
存在循環(huán)引用的情況:當(dāng)block對(duì)象 作為類的 屬性或者成員變量,并且在block初始化的時(shí)候,調(diào)用了self或者self相關(guān)類的成員變量。都會(huì)引起引用循環(huán)。
解決方法:
- 使用__weak 修飾要截取的自動(dòng)變量,
- 當(dāng)在MRC 中時(shí),可以使用__unsafe_unretained(弊端 不會(huì)自動(dòng)置nil 容易出現(xiàn)野指針) 修飾。
- 可以使用__block 修飾,前提是 必須 執(zhí)行block代碼塊,而且可以適當(dāng)?shù)卦诖a塊中 手動(dòng)的把__block變量置nil
以下是相關(guān)解決方法的實(shí)例:
實(shí)例一:
typedef void (^blk_t)(void);
@interface Myobject : NSObject
{
blk_t blk_; //成員變量
id _objc;//成員變量
}
@end
@implementation MyObject
- (id)init
{
self = [super init];
/*
分析改代碼會(huì)出現(xiàn)兩種情況的引用循環(huán):
* 一種是:成員變量block 調(diào)用 self,self中持有block ,block中也持有self,導(dǎo)致引用循環(huán),解決方法在之前 加入
__weak typeof(self) weakSelf = self;
* 第二中,雖然成員變量block沒(méi)有直接調(diào)用self ,但其調(diào)用了成員變量_objc,所以也會(huì)造成引用循環(huán):
解決方法: __weak id weakObjc = _objc;
*/
blk_ = ^{
NSLog(@"self = %@, objc = %@",self,_objc);
}
return self;
}
實(shí)例二:
typedef void (^blk_t)(void);
@interface Myobject : NSObject
{
blk_t blk_; //成員變量
}
@end
@implementation MyObject
- (id)init
{
self = [super init];
/*
此處使用__block修飾變量,是的block 持有__block變量,而__block變量持有MyObject對(duì)象,而MyObject持有block對(duì)象。出現(xiàn)引用循環(huán):
然而 當(dāng) block執(zhí)行的時(shí)候,__block變量廢棄,從而消除引用循環(huán)
*/
__block id temp = self;
blk_ = ^{
NSLog(@"self = %@,,self);
temp = nil;
}
return self;
}
- (void)execBlock
{
blk_()
}
int main (){
id o = [[MyObject alloc] init];
[o execBlock];//必須執(zhí)行 否則導(dǎo)致引用循環(huán)
return 0;
}
總結(jié)下__block 和 __weak 之間的優(yōu)缺點(diǎn):
使用__block變量的優(yōu)點(diǎn):
- 通過(guò)__block 變量可控制對(duì)象的持有期間
- 在不能使用__weak修飾符的環(huán)境中不使用__unsafe__unretain修飾符即可(不必?fù)?dān)心野指針)
在執(zhí)行Block時(shí)可動(dòng)態(tài)的決定是否將nil或者其他對(duì)象賦值在__block變量中。
使用__block變量的缺點(diǎn)如下:
- 為避免循環(huán)引用必須執(zhí)行Block