iOS開(kāi)發(fā)讀書(shū)筆記:Objective-C高級(jí)編程 iOS與OS X多線程和內(nèi)存管理-上篇(自動(dòng)引用計(jì)數(shù))
iOS開(kāi)發(fā)讀書(shū)筆記:Objective-C高級(jí)編程 iOS與OS X多線程和內(nèi)存管理-中篇(Blocks)
iOS開(kāi)發(fā)讀書(shū)筆記:Objective-C高級(jí)編程 iOS與OS X多線程和內(nèi)存管理-下篇(GCD)
閱讀完Block此篇后,可以與iOS開(kāi)發(fā)經(jīng)驗(yàn)(25)-Block一塊閱讀,主要是可以加深對(duì)__forwarding的理解。
目錄
- 2.1 Blocks概要
- 2.1.1 什么是Blocks
- 2.2 Blocks模式
- 2.2.1 Blocks語(yǔ)法
- 2.2.2 Blocks類(lèi)型變量
- 2.2.3 截獲自動(dòng)變量值
- 2.2.4 __block說(shuō)明符
- 2.2.5 截獲的自動(dòng)變量
- 2.3 Blocks的實(shí)現(xiàn)
- 2.3.1 Block的實(shí)質(zhì)
- 2.3.2 截獲自動(dòng)變量值
- 2.3.3 __block說(shuō)明符
- 2.3.4 Block存儲(chǔ)域
- 2.3.5 __block變量存儲(chǔ)域
- 2.3.6 截獲對(duì)象
- 2.3.7 __block變量和對(duì)象
- 2.3.8 Block循環(huán)引用
- 2.3.9 copy/release
2.1 Blocks概要
2.1.1 什么是Blocks
Blocks是C語(yǔ)言的擴(kuò)充功能。可以用一句話來(lái)表示Blocks的擴(kuò)充功能:帶有自動(dòng)變量(局部變量)的匿名函數(shù)。
顧名思義,所謂匿名函數(shù)就是不帶有名稱(chēng)的函數(shù)。
C語(yǔ)言的標(biāo)準(zhǔn)函數(shù)如下:
int func(int count);//聲明函數(shù)
int result = func(10);//調(diào)用函數(shù)
如果像下面這樣,使用函數(shù)指針來(lái)代替直接調(diào)用函數(shù),必須使用該函數(shù)的名稱(chēng)func。
int result = (*funcptr)(10);
這樣以來(lái),函數(shù)func的地址就能賦值給函數(shù)指針類(lèi)型變量funcptr中了。
但其實(shí)使用函數(shù)指針也仍然需要知道函數(shù)名稱(chēng)。若不使用想賦值的函數(shù)的名稱(chēng),就無(wú)法取得該函數(shù)的地址。
int (*funcptr)(int) = &func;
int result = (*funcptr)(10);
通過(guò)Blocks,源代碼中就能夠使用匿名函數(shù),而不帶名稱(chēng)的函數(shù)。
到這里我們知道了"帶有自動(dòng)變量值的匿名函數(shù)"中"匿名函數(shù)"的概念。那么“帶有自動(dòng)變量值”究竟是什么呢?
首先回顧下函數(shù)中可能使用的變量:
- 自定變量(局部變量)
- 函數(shù)的參數(shù)
- 靜態(tài)變量(靜態(tài)局部變量)
- 靜態(tài)全局變量
- 全局變量
雖然這些變量的作用域不同,但在整個(gè)程序當(dāng)中,一個(gè)變量總保持在一個(gè)內(nèi)存區(qū)域。
另外,“帶有自動(dòng)變量值的匿名函數(shù)”這一概念并不僅指Blocks,它還存在于其他許多程序語(yǔ)言中。在計(jì)算機(jī)科學(xué)中,此概念也稱(chēng)為閉包。
2.2 Blocks模式
2.2.1 Blocks語(yǔ)法
與一般的函數(shù)定義相比,僅有兩點(diǎn)不同
- 沒(méi)有函數(shù)名
- 帶有“^”記號(hào)(插入記號(hào)):因?yàn)镺S X、iOS應(yīng)用程序的源代碼中將大量使用Block,所以插入該記號(hào)便于查找。
以下為Block語(yǔ)法的BN范式
^ 返回值類(lèi)型 參數(shù)列表 表達(dá)式
^ int (int count) {
return count + 1;
}
該源代碼可省略胃如下形式
^ {
return count + 1;
}
2.2.2 Blocks類(lèi)型變量
在Block語(yǔ)法下,可將Block語(yǔ)法賦值給聲明為Block類(lèi)型的變量中。既源代碼中一旦使用Block語(yǔ)法就相當(dāng)于生成了可賦值給Block類(lèi)型變量的“值”。
int (^blk)(int);
與前面的使用函數(shù)指針的源代碼對(duì)比可知,聲明Block類(lèi)型變量?jī)H僅是將聲明函數(shù)指針類(lèi)型變量的“*”變?yōu)椤癪”。該Blcok類(lèi)型變量與一般的C語(yǔ)言變量完全相同,可作為以下用途使用 :
- 自定變量(局部變量)
- 函數(shù)的參數(shù)
- 靜態(tài)變量(靜態(tài)局部變量)
- 靜態(tài)全局變量
- 全局變量
那么,下面我們就試著使用Block語(yǔ)法將Block賦值為Block類(lèi)型變量:
int (^blk) (int) = ^ (int count) {
return count + 1;
}
也可以:
int (^blk1)(int) = blk;
但是此記述方式極為復(fù)雜。這時(shí),我們可以像使用函數(shù)指針類(lèi)型時(shí)那樣,使用typedef來(lái)解決問(wèn)題。
typedef int (^blk_t) (int);
如上所示,通過(guò)使用typedef可聲明“blk_t”類(lèi)型變量。這樣函數(shù)定義就變得更容易理解了。
另外,將賦值給Block類(lèi)型變量中的Block方法像C語(yǔ)言通常的函數(shù)調(diào)用那樣使用,這種方法與使用函數(shù)指針類(lèi)型變量調(diào)用函數(shù)的方法幾乎完全相同。
2.2.3 截獲自動(dòng)變量值
通過(guò)以上說(shuō)明,我們已經(jīng)理解了“帶有自動(dòng)變量值的匿名函數(shù)”中的“匿名函數(shù)”。而“帶有自動(dòng)變量值”究竟是什么呢?“帶有自動(dòng)變量值”在Block中表現(xiàn)為“截獲自動(dòng)變量值”。截獲自動(dòng)變量值的實(shí)例如下:
int main() {
int val = 10;
void (^blk)(void) = ^ {
printf(val);
}
val = 2;
blk();
//打印結(jié)果為10;
}
Block中,Block表達(dá)式截獲所使用的自動(dòng)變量的值,既保持該自動(dòng)變量的瞬間值。這就是自動(dòng)變量值的截獲。
2.2.4 __block說(shuō)明符
實(shí)際上,自動(dòng)變量值截獲指南保持秩序Block語(yǔ)法瞬間的值。保存后就不能改寫(xiě)該值。如果嘗試在Block中改寫(xiě)截獲的自動(dòng)變量值,會(huì)發(fā)生編譯錯(cuò)誤。
若想在Block語(yǔ)法的表達(dá)式中將值賦值在Block語(yǔ)法外聲明的自動(dòng)變量,需要在該自動(dòng)變量上添加__block說(shuō)明符。該變量稱(chēng)為_(kāi)_block變量。
int main() {
__block int val = 10;
void (^blk)(void) = ^ {
val = 1;
}
val = 2;
blk();
//打印結(jié)果為10;
}
2.2.5 截獲的自動(dòng)變量
截獲OC對(duì)象,調(diào)用變更該對(duì)象的方法不會(huì)產(chǎn)生編譯錯(cuò)誤,而向截獲的變量array賦值則會(huì)產(chǎn)生編譯錯(cuò)誤。
//編譯正常
id array = [[NSMutableArray alloc] init];
void (^blk) (void) = ^ {
id obj = [[NSObject alloc] init];
[array addObject:obj];
}
//編譯錯(cuò)誤
id array = [[NSMutableArray alloc] init];
void (^blk) (void) = ^ {
array = [[NSMutableArray alloc] init];
}
以上的第二段代碼需要給截獲的自動(dòng)變量附加__block說(shuō)明符。
2.3 Blocks的實(shí)現(xiàn)
2.3.1 Block的實(shí)質(zhì)
Block上“帶有自動(dòng)變值的匿名函數(shù)”,但Block究竟是什么呢?
它實(shí)際上是作為極普通的C語(yǔ)言源代碼來(lái)處理的,通過(guò)支持Block的編譯器,含有Block的編譯器,含有Block語(yǔ)法的源代碼轉(zhuǎn)換為一般C語(yǔ)言編譯器能夠處理的源代碼,并作為極普通的C語(yǔ)言源代碼被編譯。
clang(LLVM編譯器)具有轉(zhuǎn)換為我們可讀源代碼的功能。通過(guò)“-rewrite-objc”選項(xiàng)就能將含有Block語(yǔ)法的源代碼變換為C++的源代碼。
clang -rewrite-objc xxx.m
其實(shí),所謂Block就是Objective-C對(duì)象。Block指針賦值給Block的結(jié)構(gòu)體成員變量isa。
struct _main_block_impl_0 {
void *isa;
int flags;
int Reserved;
void *FuncPtr;
};
此_main_block_impl_0結(jié)構(gòu)體相當(dāng)于基于objc_object結(jié)構(gòu)體的Objective-C類(lèi)對(duì)象的結(jié)構(gòu)體。另外,對(duì)其中的成員變量isa進(jìn)行初始化,具體如下:
isa = &_NSConcreteStackBlock;
既_NSConcreteStackBlock相當(dāng)于class_t結(jié)構(gòu)體實(shí)例。在將Block作為Objective-C的對(duì)象處理時(shí),關(guān)于該類(lèi)的信息放置于_NSConcreteStackBlock中;
2.3.2 截獲自動(dòng)變量值
本節(jié)主要講解如何截獲自動(dòng)變量值。將截獲自動(dòng)值的源代碼用過(guò)clang進(jìn)行轉(zhuǎn)換(源代碼略)。
我們注意到,Block語(yǔ)法表達(dá)式中使用的自動(dòng)變量被作為成員變量追加到了_main_block_impl_0結(jié)構(gòu)體中。
struct _main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0 *Desc;
int var;
};
_main_block_impl_0結(jié)構(gòu)體內(nèi)聲明的成員變量類(lèi)型與自動(dòng)變量類(lèi)型完全相同。請(qǐng)注意,Block語(yǔ)法表達(dá)式中沒(méi)有使用的自動(dòng)變量不會(huì)被追加。Block的自動(dòng)變量截獲只針對(duì)Block中使用的自動(dòng)變量。
總的來(lái)說(shuō),所謂“截獲自動(dòng)變量值”意味著在執(zhí)行Block語(yǔ)法時(shí),Block語(yǔ)法表達(dá)式所使用的自動(dòng)變量值被保存到Block的結(jié)構(gòu)體實(shí)例(既Block自身)中。
2.3.3 __block說(shuō)明符
以上的截獲自動(dòng)變量的代碼例子,在Block的結(jié)構(gòu)體實(shí)例中重寫(xiě)該自動(dòng)變量也不會(huì)改變?cè)冉孬@的自動(dòng)變量。因?yàn)樵趯?shí)現(xiàn)上不能改寫(xiě)被截獲自動(dòng)變量的值,所以會(huì)發(fā)生編譯錯(cuò)誤。
不過(guò)這樣以來(lái),無(wú)法在Block中保存值了,極為不便。但是有兩個(gè)方法:
- 有如下幾個(gè)變量,允許Block改寫(xiě)值:
- 靜態(tài)變量
- 靜態(tài)全局變量
- 全局變量
- 使用__block修飾變量 :__block 存儲(chǔ)域類(lèi)說(shuō)明符
C語(yǔ)言有如下存儲(chǔ)域類(lèi)說(shuō)明符:
- typedef
- extern
- static:表示作為靜態(tài)變量存在在數(shù)據(jù)區(qū)中
- auto:表示作為自動(dòng)變量存儲(chǔ)在棧中
- register
__block說(shuō)明符類(lèi)似于static、auto和register說(shuō)明符,它們用于指定將變量值設(shè)置到哪個(gè)存儲(chǔ)域中。
個(gè)人筆記
2.3.4 Block存儲(chǔ)域
通過(guò)前面說(shuō)明可知,Block轉(zhuǎn)換為Block的結(jié)構(gòu)體類(lèi)型的自動(dòng)變量,__block變量轉(zhuǎn)換為_(kāi)_block變量的結(jié)構(gòu)體類(lèi)型的自動(dòng)變量。所謂結(jié)構(gòu)體類(lèi)型的自動(dòng)變量,既棧上生成的該結(jié)構(gòu)體的實(shí)例。
另外通過(guò)之前的說(shuō)明可知Block也是Objective-C對(duì)象,該Block的類(lèi)為_(kāi)NSConcreteStackBlock。有很多與之類(lèi)似的類(lèi),如:
- _NSConcreteStackBlock,既該類(lèi)的對(duì)象Block設(shè)置在棧上
- _NSConcreteGlobalBlock,設(shè)置在程序的數(shù)據(jù)區(qū)域(.data)中
- _NSConcreteMallocBlock,設(shè)置在由malloc函數(shù)分配的內(nèi)存塊(既堆)中
在記述全局變量的地方使用Block語(yǔ)法時(shí),生成的Block為_(kāi)NSConcreteGlobalBlock類(lèi)對(duì)象。例如
void (^blk)(void) = ^ {
printf("Global Block");
}
此源代碼通過(guò)聲明全局變量blk來(lái)使用Block語(yǔ)法。如果轉(zhuǎn)換該源代碼,Block用結(jié)構(gòu)體的成員變量isa的初始化如下:
impl.isa = & _NSConcreteGlobalBlock;
該Block的類(lèi)為_(kāi)NSConcreteGlobalBlock類(lèi)。此Block既該Block用結(jié)構(gòu)體實(shí)例設(shè)置在程序的數(shù)據(jù)區(qū)域中。
在以下情況下,Block為_(kāi)NSConcreteGlobalBlock類(lèi)對(duì)象
- 記述全局變量的地方有Block語(yǔ)法時(shí)
- Block語(yǔ)法的表達(dá)式中不使用應(yīng)截獲的自動(dòng)變量時(shí)
除此之外的Block語(yǔ)法生成的Block為_(kāi)NSConcreteStackBlock類(lèi)對(duì)象,且設(shè)置在棧上。
配置在全局變量上的Block,從變量作用域外也可以通過(guò)指針安全的使用。但設(shè)置在棧上的Block,如果其所屬的變量作用域結(jié)束,該Block就被廢棄。由于__block變量也配置在棧上,同樣的,如果其所屬的變量作用域結(jié)束,則該__block變量也會(huì)被廢棄。
Block提供了將Block和__block變量從棧上復(fù)制到堆上的方法來(lái)解決這個(gè)問(wèn)題。將配置在棧上的Block復(fù)制到堆上,這樣即使Block語(yǔ)法記述的變量作用域結(jié)束,堆上的Block還可以繼續(xù)存在。
復(fù)制到堆上的Block將_NSConcreteMallocBlock類(lèi)對(duì)象寫(xiě)入Block用結(jié)構(gòu)體實(shí)例的成員變量isa。
impl.isa = & _NSConcreteMallocBlock;
而__block變量用結(jié)構(gòu)體成員變量_forwarding可以實(shí)現(xiàn)無(wú)論__block變量配置在棧上還是堆上時(shí)都能夠正確的訪問(wèn)__block變量。在此情形下,只要棧上的結(jié)構(gòu)體實(shí)例成員變量__forwarding指向堆上的結(jié)構(gòu)體實(shí)例,那么不管是從棧上的__block變量還是從堆上的__block變量都能夠正確的訪問(wèn)。
那么Block提供的復(fù)制方法是什么呢?當(dāng)ARC時(shí),大多數(shù)情形下編譯器會(huì)恰當(dāng)?shù)呐袛?,自?dòng)生成將Block從棧上復(fù)制到堆上的代碼。
當(dāng)Block作為函數(shù)返回值返回時(shí),執(zhí)行objc_retainBlock方法,實(shí)際上是copy函數(shù)。
那么少數(shù)情形有幾種呢?
- XXXX
另外,對(duì)于已配置在堆上的Block以及配置在程序的數(shù)據(jù)區(qū)域的Block,調(diào)用copy方法又會(huì)如何呢?
- _NSConcreteMallocBlock:引用計(jì)數(shù)增加
- _NSConcreteStackBlock:從棧復(fù)制到堆
- _NSConcreteGlobalBlock:什么也不做
不管是Block配置在何處,用copy方法復(fù)制都不會(huì)引起任何問(wèn)題。在不確定時(shí)調(diào)用copy方法即可
2.3.5 __block變量存儲(chǔ)域
上節(jié)只對(duì)Block的copy進(jìn)行了說(shuō)明,使用__block變量的Block從棧復(fù)制到堆時(shí),使用的所有__block變量也必定配置在棧上。這些__block變量也全部從棧復(fù)制到堆。此時(shí),Block持有__block變量。
如果配置在堆上的Block被廢棄,那么它所使用的__block變量也就被釋放。
此思考方式與OC的引用計(jì)數(shù)內(nèi)存管理完全相同。使用__block變量的Block持有__block變量。日光Block被廢棄,它所持有的__block變量也就被釋放。
那么在理解了__block變量的存儲(chǔ)域之后,在回顧下之前講過(guò)的使用__block變量用結(jié)構(gòu)體成員變量__forwarding的原因?!安还躝_block變量配置在棧上還是在堆上,都能夠正確的訪問(wèn)該變量”。正如這句話所述,通過(guò)Block的復(fù)制,__block變量也從棧復(fù)制到堆。此時(shí)可同時(shí)訪問(wèn)棧上的__block變量和堆上的__block變量。
源代碼可轉(zhuǎn)換為如下形式:
(val.__forwarding->val);
在變換Block語(yǔ)法的函數(shù)中,該變量val為復(fù)制到堆上的__block變量用結(jié)構(gòu)體實(shí)例,而使用的與Block無(wú)關(guān)的變量val,為復(fù)制前棧上的__block變量用結(jié)構(gòu)體實(shí)例。
但是棧上的__block變量用結(jié)構(gòu)體實(shí)例在__block變量從棧復(fù)制帶堆上時(shí),會(huì)將成員變量__forwarding的值替換為復(fù)制目標(biāo)堆上的__block變量用結(jié)構(gòu)體實(shí)例的地址。
通過(guò)該功能,無(wú)論上在Block語(yǔ)法中、block語(yǔ)法外使用__block變量,還是__block變量配置在棧上或堆上,都可以順利的訪問(wèn)同一個(gè)__block變量。
2.3.6 截獲對(duì)象
以下源代碼生成并持有NSMutableArray類(lèi)的對(duì)象,由于附有__strong修飾符的賦值目標(biāo)變量的作用域立即結(jié)束,因此對(duì)象被立即釋放并廢棄。
{
id array = [[NSMutableArray alloc] init];
}
下面我們看一下在Block語(yǔ)法中使用該變量array的代碼:
//運(yùn)行正常
blk_t blk;
{
id array = [[NSMutableArray alloc] init];
blk = [^(id obj) {
[array addObject:obj];
} copy];
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
該代碼運(yùn)行正常,執(zhí)行結(jié)果如下
array count = 1;
array count = 2;
array count = 3;
請(qǐng)注意被賦值NSMutableArray類(lèi)對(duì)象并被截獲的自動(dòng)變量array。我們可以發(fā)現(xiàn)它是Block用的結(jié)構(gòu)體中附有__strong修飾符的成員變量。
struct _main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0 *Desc;
id __strong array;
};
在OC中,C語(yǔ)言結(jié)構(gòu)體不能含有附有__strong修飾的變量。因?yàn)榫幾g器不知道應(yīng)何時(shí)進(jìn)行C語(yǔ)言結(jié)構(gòu)體的初始化和廢棄操作,不能很好的管理內(nèi)存。
但是OC的運(yùn)行時(shí)庫(kù)能夠很準(zhǔn)確把握Block從棧復(fù)制到堆以及堆上的Block被廢棄的時(shí)機(jī),因此Block用結(jié)構(gòu)體中即使含有附有__strong修飾符或__weak修飾符的變量,也可以恰當(dāng)?shù)倪M(jìn)行初始化和廢棄。為此需要使用在__main_block_desc_0結(jié)構(gòu)體中增加的成員變量copy和dispose,以及作為指針賦值給該成員變量的_main_block_copy_0函數(shù)和_main_block_dispose_0函數(shù)。
恰當(dāng)管理賦值給變量array的對(duì)象:__main_block_copy_0函數(shù)使用_Block_object_assign函數(shù)將對(duì)象類(lèi)型對(duì)象復(fù)制給Block用結(jié)構(gòu)體的成員變量array中并持有該對(duì)象。
_Block_object_assign函數(shù)調(diào)用相當(dāng)于retain實(shí)例方法的函數(shù),將對(duì)象賦值在對(duì)象類(lèi)型的結(jié)構(gòu)體成員變量中。
另外,__main_block_dispose_0函數(shù)使用_Block_object_dispose函數(shù),釋放賦值在Block用結(jié)構(gòu)體成員變量array中的對(duì)象。
_Block_object_dispose函數(shù)調(diào)用相當(dāng)于release實(shí)例方法的函數(shù),釋放賦值在對(duì)象類(lèi)型的結(jié)構(gòu)體成員變量中的對(duì)象。
雖然此__main_block_copy_0函數(shù)(以下簡(jiǎn)稱(chēng)copy函數(shù))和__main_block_dispose_0函數(shù)(以下簡(jiǎn)稱(chēng)dispose函數(shù))指針被賦值在__main_block_desc_0結(jié)構(gòu)體成員變量copy和dispose。在Block從棧復(fù)制到堆時(shí)以及堆上的Block被廢棄時(shí)會(huì)調(diào)用這些函數(shù)。
- copy函數(shù):棧上的Block復(fù)制到堆時(shí);
- dispose函數(shù):堆上的Block被廢棄時(shí);
那么什么時(shí)候棧上的Block會(huì)復(fù)制到堆呢?
- 調(diào)用Block的copy實(shí)例方法時(shí);
- Block作為函數(shù)返回值返回時(shí);
- 將Block賦值給附有__strong修飾符id類(lèi)型的類(lèi)或Block類(lèi)型成員變量時(shí);
- 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中傳遞Block時(shí);
在上面這些情況下棧上的Block賦值到堆上,其實(shí)可歸結(jié)為_(kāi)Block_copy函數(shù)被調(diào)用時(shí)Block從棧復(fù)制到堆。相對(duì)的,在釋放復(fù)制到堆上的Block后,誰(shuí)都不持有Block而使其被廢棄時(shí)調(diào)用dispose函數(shù)。這相當(dāng)于對(duì)象的dealloc實(shí)例方法。
有了這種構(gòu)造,通過(guò)使用附有__strong修飾符的自動(dòng)變量,因而B(niǎo)lock中截獲的對(duì)象就能夠超出其變量作用域而存在。
2.3.7 __block變量和對(duì)象
__block說(shuō)明符可指定任何類(lèi)型的自動(dòng)變量。
__block id obj = [[NSObject alloc] init];
其實(shí)該代碼等同于
__block id __strong obj = [[NSObject alloc] init];
ARC有效時(shí),id類(lèi)型以及對(duì)象類(lèi)型變量必定附加所有權(quán)修飾符,缺省為附有__strong修飾符的變量。
在Block中使用附有__strong修飾符的id類(lèi)型或?qū)ο箢?lèi)型自動(dòng)變量的情況下,當(dāng)Block從棧復(fù)制到堆時(shí),使用Block_object_assign函數(shù),持有Block截獲的對(duì)象。當(dāng)堆上的Block被廢棄時(shí),使用_block_object_dispose函數(shù),釋放Block截獲的對(duì)象。
在__block變量為附有_strong修飾符的id類(lèi)型或?qū)ο箢?lèi)型自動(dòng)變量的情形下會(huì)發(fā)生同樣的過(guò)程。當(dāng)__block變量從棧復(fù)制到堆時(shí),使用_Block_object_assign函數(shù),持有賦值給__block變量的對(duì)象。當(dāng)堆上的__block變量被廢棄時(shí),使用_Block_object_dispose函數(shù),釋放賦值給__block變量的對(duì)象。
由此可知,即使對(duì)象賦值復(fù)制到堆上的附有_strong修飾符的對(duì)象類(lèi)型__block變量中,只要__block變量在堆上繼續(xù)存在,那么該對(duì)象就會(huì)繼續(xù)處于被持有的狀態(tài)。這與Block中使用賦值給附有__strong修飾符的對(duì)象類(lèi)型自動(dòng)變量的對(duì)象相同。
另外,我們前面用到的只有附有__strong修飾符的id類(lèi)型或?qū)ο箢?lèi)型自動(dòng)變量。如果使用__weak修飾符會(huì)如何呢?首先是在Block中使用附有__weak修飾符的id類(lèi)型變量的情況。
blk_t blk;
{
id array = [[[NSMutableArray alloc] init];
id __weak array2 = array;
blk = [^(id obj) {
[array2 addObject:obj];
} copy];
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
該代碼可正常執(zhí)行。 執(zhí)行結(jié)果,這與以上代碼的結(jié)果不同:
array2 count = 0;
array2 count = 0;
array2 count = 0;
這是由于附有__strong修飾符的變量array在該變量作用域結(jié)束的同時(shí)被釋放、廢棄,nil被賦值在附有__weak修飾符的變量array2中。
2.3.8 Block循環(huán)引用
如果在Block中使用附有__strong修飾符的對(duì)象類(lèi)型自動(dòng)變量,那么當(dāng)Block從棧復(fù)制到堆時(shí),該對(duì)象為Block所持有。這樣容易引起循環(huán)利用。我們來(lái)看看下面的源代碼:
typedef void (^blk_t)(void);
@interface MOyObject : NSObject
{
blk_t blk_;
}
@end
@implementation MyObject
- (id)init {
self = [super init];
blk_ = ^ {
NSLog(@"self = %@",self);
} ;
return self;
}
- (void)dealloc {
NSLog(@:dealloc:);
}
@end
int main() {
id o = [[MyObject alloc] init];
NSLog(@"%@",o);
return 0;
}
該源代碼中MyObject類(lèi)的dealloc實(shí)例方法一定沒(méi)有被調(diào)用。
MyObject類(lèi)對(duì)象的Block類(lèi)型成員變量blk_持有賦值為Block的強(qiáng)引用。既MyObject類(lèi)對(duì)象持有Nlock。init實(shí)例方法中執(zhí)行的Block語(yǔ)法使用附有_strong修飾符的id類(lèi)型變量self。并且由于Block語(yǔ)法賦值在了成員變量blk中,因此通過(guò)Block語(yǔ)法生成在棧上的Block此時(shí)由棧復(fù)制到堆,并持有所使用的self。self持有Block,Block持有self。這正是循環(huán)引用。
另外,編譯器在編譯該源代碼是能夠查處循環(huán)引用,因此編譯器能正確的進(jìn)行警告。
為避免此循環(huán)引用,可聲明附有__weak修飾符的變量,并將self賦值使用。
- (id)init {
self = [super init];
id __weak tmp = self;
blk_ = ^ {
NSLog(@"self = %@",tmp);
} ;
return self;
}
在該源代碼中,由于Block存在時(shí),持有該Block的MyObject類(lèi)對(duì)象賦值在變量tmp中的self必須存在,因此不需要判斷tmp的值是否為nil。
在面相iOS4(MRC),必須使用__unsafe_unretained修飾符代替__weak修飾符。在此源代碼中也可使用__unsafe_unretained修飾符,且不必?fù)?dān)心懸掛指針。
- (id)init {
self = [super init];
id __unsafe_unretained tmp = self;
blk_ = ^ {
NSLog(@"self = %@",tmp);
} ;
return self;
}
另外在以下源代碼中Block內(nèi)沒(méi)有使用self也同樣截獲了self,引起了循環(huán)引用。
typedef void (^blk_t)(void);
@interface MOyObject : NSObject
{
blk_t blk_;
id obj_;
}
@end
@implementation MyObject
- (id)init {
self = [super init];
blk_ = ^ {
NSLog(@"obj_ = %@",obj_);
} ;
return self;
}
既Block語(yǔ)法內(nèi)使用的obj_實(shí)際上截獲了self。對(duì)編譯器來(lái)說(shuō),obj_只不過(guò)是對(duì)象用結(jié)構(gòu)體的成員變量。
blk_ = ^ {
NSLog(@"obj_ = %@",self->obj_);
};
該源代碼也基本與前面一樣,聲明附有_weak修飾符的變量并賦值obj使用來(lái)避免循環(huán)引用。在此源代碼中也可安全的使用__unsafe_unretained修飾符,原因同上。
- (id)init {
self = [super init];
id __weak obj = obj_;
blk_ = ^ {
NSLog(@"obj = %@",obj);
} ;
return self;
}
在為避免循環(huán)引用而使用__weak修飾符時(shí),雖說(shuō)可以確認(rèn)使用附有__weak修飾符的變量時(shí)是否為nil,但更有必要使之生成以使用賦值給附有__weak修飾符變量的對(duì)象。
另外,還可以使用__block變量來(lái)避免循環(huán)引用。
typedef void (^blk_t)(void);
@interface MOyObject : NSObject
{
blk_t blk_;
}
@end
@implementation MyObject
- (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 o = [[MyObject alloc] init];
[o execBlock];
return 0;
}
該源代碼沒(méi)有循環(huán)引用。原因:通過(guò)執(zhí)行execBlock實(shí)例方法,Block被實(shí)行,nil被賦值在__block變量tmp中。因此,_block變量tmp對(duì)MyObject類(lèi)對(duì)象的強(qiáng)引用失效。但是如果不調(diào)用execBlock實(shí)例方法,既不執(zhí)行賦值給成員變量blk的Block,便會(huì)循環(huán)引用并引起內(nèi)存泄漏。
在生成并持有MyObject類(lèi)對(duì)象的狀態(tài)下會(huì)引起以下循環(huán)引用:
- MyObject類(lèi)對(duì)象持有Block;
- Block持有__block變量;
- __block變量持有MyObject類(lèi)對(duì)象;
下面我們對(duì)使用__block變量避免循環(huán)引用的方法和使用__weak 修飾符及__unsafe_unretained修飾符避免循環(huán)引用的方法做個(gè)比較。
使用__block變量的優(yōu)點(diǎn)如下:
- 通過(guò)__block變量可控制對(duì)象的持有期間
- 在不能使用__weak修飾符的環(huán)境中不使用__unsafe_unretained修飾符即可(不必?fù)?dān)心懸垂指針)
在執(zhí)行Block時(shí)可動(dòng)態(tài)的決定是否將nil或其他對(duì)象賦值在__block變量中。
使用__block變量的缺點(diǎn)如下:
- 為避免循環(huán)引用必須執(zhí)行Block
存在執(zhí)行了Block語(yǔ)法,卻不執(zhí)行Block的路徑時(shí),無(wú)法避免循環(huán)引用。若有雨Block引發(fā)了循環(huán)引用時(shí),根據(jù)Block的用途選擇使用__block變量、__weak修飾符或__unsafe_unretained修飾符來(lái)避免循環(huán)引用。
2.3.9 copy/release
ARC無(wú)效時(shí),一般需要手動(dòng)將Block從棧復(fù)制到堆。另外,由于ARC無(wú)效,所以肯定要釋放賦值的Block。這時(shí)我們用copy實(shí)例方法用來(lái)賦值,用release實(shí)例方法來(lái)釋放。
[blk_ release];
只要Block有一次復(fù)制并配置在堆上,就可通過(guò)retain實(shí)例方法持有。
[blk_ retain];
但是對(duì)于配置在棧上的Block調(diào)用retain實(shí)例方法則不起任何作用。
[blk_ retain];
該源代碼中,雖然堆賦值給blk_的棧上的Block調(diào)用了retain實(shí)例方法,但實(shí)際上對(duì)此源代碼不起任何作用。因此推薦使用copy實(shí)例方法來(lái)持有Block。
另外,ARC無(wú)效時(shí),__block說(shuō)明符被用來(lái)避免Block中的循環(huán)引用。這是由于當(dāng)Block從棧復(fù)制到堆時(shí),若Block使用的變量為附有__block說(shuō)明符的id類(lèi)型或?qū)ο箢?lèi)型的自動(dòng)變量,不會(huì)被retain;若Block使用的變量為沒(méi)有__block說(shuō)明符的id類(lèi)型或?qū)ο箢?lèi)型的自動(dòng)變量,則被retain。
注意:正好在ARC有效時(shí)能夠同__unsafe_unretained修飾符一樣來(lái)使用。由于ARC有效時(shí)和無(wú)效時(shí)__block說(shuō)明符的用途有很大的區(qū)別,因此在編寫(xiě)源代碼時(shí),必須知道該源代碼是在ARC有效情況下編譯還是在ARC無(wú)效情況下編譯。