循環(huán)引用,故名思義始锚,即強(qiáng)引用的引用鏈上出現(xiàn)了環(huán)喳逛。A、B兩個(gè)對(duì)象姐呐,A強(qiáng)引用了B典蝌,B又強(qiáng)引用了A,導(dǎo)致在任何時(shí)候A麦轰、B的引用計(jì)數(shù)都不為0,始終不會(huì)被釋放款侵。解決循環(huán)引用的一般方法通過將Strong指針改為weak指針從而打破環(huán)新锈。
- delegate循環(huán)引用
例如有A眶熬、B、C
三個(gè)控制器拳缠,其中B
有個(gè)Strong
屬性C
贸弥,C
有個(gè)Strong
屬性delegate
,該代理指向B
哲鸳】控制器進(jìn)行如下跳轉(zhuǎn),
A--push-->B--push-->C
郁岩,之后再執(zhí)行pop
。
當(dāng)從C--pop-->B
時(shí)萍摊,
C
不會(huì)執(zhí)行dealloc
函數(shù)蝴乔,原因是由于B
還持有C
的強(qiáng)引用,并且B
沒有被釋放片酝,
此時(shí)挖腰,當(dāng)從B--pop-->A
時(shí),
B
不會(huì)執(zhí)行dealloc
函數(shù)审轮,原因是由于C
的代理是強(qiáng)引用,而這個(gè)代理對(duì)象正是B
篡诽,因此出現(xiàn)了強(qiáng)引用環(huán)榴捡,
B<--強(qiáng)引用-->C
,
打破環(huán)的方法為达椰,可以將C
的代理使用weak
修飾詞修飾项乒,
這樣當(dāng)C--pop-->B
時(shí),
C
依然不會(huì)執(zhí)行dealloc
函數(shù)蝇裤,原因仍是由于B
還活著并且持有C
的強(qiáng)引用埃碱,
而當(dāng)從B--pop-->A
時(shí),
由于沒有強(qiáng)引用指針指向B
了啃憎,所以B
先釋放了似炎,之后由于B
的釋放,也就沒有了強(qiáng)引用贩毕,導(dǎo)致C
也釋放了仆嗦。
補(bǔ)充:細(xì)心的你也許會(huì)提出這樣的疑問,為什么
沒有強(qiáng)引用指針指向B了
?這里的原因是從A--push-->B
時(shí)谆甜,navigationController
有個(gè)viewControllers
的屬性集绰,這個(gè)屬性是一個(gè)數(shù)組,當(dāng)執(zhí)行A--push-->B
操作時(shí)栽燕,會(huì)向這個(gè)數(shù)組中添加一個(gè)元素,這個(gè)元素也就是你push
的這個(gè)控制器B
浴讯,我們都知道數(shù)組的addObject
會(huì)使引用計(jì)數(shù)+1
,因此在A--push-->B
操作后侍郭,就持有了B
的強(qiáng)引用掠河,當(dāng)調(diào)用B--pop-->A
操作后執(zhí)行到viewWillDisappear
函數(shù)時(shí)唠摹,已經(jīng)將B
從viewControllers
數(shù)組中移除奉瘤,此時(shí)B
已經(jīng)沒有被任何強(qiáng)引用指針?biāo)钟校?code>B執(zhí)行dealloc
函數(shù)。
除此之外看到這里你也許還會(huì)問盗温,為什么筆者要使用3個(gè)控制器來描述卖局,而非2個(gè)控制器呢?那么接下來請你思考如下的情景是否存在引用循環(huán):
兩個(gè)控制器A砚偶、B
染坯,其中B
有個(gè)Strong
屬性delegate
,該代理指向A
单鹿≈俪控制器進(jìn)行如下跳轉(zhuǎn),
A--push-->B
昼窗,之后再執(zhí)行pop
澄惊。
當(dāng)B--pop-->A
時(shí)富雅,
由于沒有任何強(qiáng)引用持有B
肛搬,雖然B
持有A
的強(qiáng)引用,但是這并不影響B
的釋放蛤奢,因此B
仍然會(huì)執(zhí)行dealloc
函數(shù)陶贼。
- block循環(huán)引用
說起block的循環(huán)引用,就需要談一下block的實(shí)現(xiàn)原理痹屹。
這里推薦一篇介紹block原理非常棒的文章枉氮,iOS底層原理總結(jié) - 探尋block的本質(zhì)(一),剛開始看文章會(huì)覺得有些晦澀難懂楼肪,但是反復(fù)看兩遍就可以理解了惹悄,真的寫的非常好。
有一個(gè)結(jié)論就是block最終都是繼承自NSBlock類型象缀,而NSBlock繼承于NSObjcet爷速,因此block本質(zhì)是一個(gè)OC對(duì)象。
補(bǔ)充:查看源碼方式
首先在main.m文件中寫一個(gè)block
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
void(^block)(int ,int) = ^(int a, int b){
NSLog(@"this is block,a = %d,b = %d",a,b);
NSLog(@"this is block,age = %d",age);
};
block(3,5);
}
return 0;
}
然后我們在終端執(zhí)行如下命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
然后就可以看到c++中block的聲明和定義分別與oc代碼中相對(duì)應(yīng)顯示了莉给。
通過源碼發(fā)現(xiàn)颓遏,在block定義時(shí)調(diào)用了一個(gè)__main_block_impl_0
函數(shù)滞时,并且將該函數(shù)的地址
賦值給了block,那么這個(gè)函數(shù)又是什么呢曼玩?
__main_block_impl_0
是一個(gè)結(jié)構(gòu)體,結(jié)構(gòu)體內(nèi)包括一個(gè)同名構(gòu)造函數(shù)豫尽,還有另外兩個(gè)結(jié)構(gòu)體以及一個(gè)成員變量age顷帖。這個(gè)同名的構(gòu)造函數(shù)接收了幾個(gè)參數(shù)。我們看一下*fp這個(gè)參數(shù)榴嗅,這是一個(gè)函數(shù)指針陶舞,block將我們在block塊中寫的代碼封裝成了一個(gè)函數(shù),然后將這個(gè)函數(shù)的地址傳入了
__main_block_impl_0
的構(gòu)造函數(shù)中保存在結(jié)構(gòu)體內(nèi)。另外age是我們定義的局部變量颁井,在block訪問該變量時(shí)蠢护,block會(huì)對(duì)其進(jìn)行捕獲,那么什么是捕獲呢葵硕?我個(gè)人的理解是對(duì)其進(jìn)行拷貝操作懈凹,這里的拷貝分為深拷貝和淺拷貝兩種,對(duì)于局部變量介评,由于怕使用指針訪問時(shí)該變量被釋放们陆,因此會(huì)在第一次拿到該變量時(shí)進(jìn)行值拷貝;而對(duì)于靜態(tài)變量坪仇,由于該變量不會(huì)自動(dòng)被釋放椅文,因此可以直接通過指針方式進(jìn)行拷貝惜颇;而對(duì)于全局變量恤筛,block在訪問時(shí)并沒有對(duì)其進(jìn)行拷貝操作,而是直接訪問望伦。通過如下示例可以說明:
int a = 10;
static int b = 11;
int main(int argc, const char * argv[]) {
@autoreleasepool {
auto int c = 12;
void(^block)(void) = ^{
NSLog(@"hello, a = %d, b = %d, c = %d", a, b, c);
};
a = 1;
b = 2;
c = 3;
block();
}
return 0;
}
// 控制臺(tái)輸出 a = 1, b = 2, c = 12
總結(jié)如下:
疑問:以下代碼中block是否會(huì)捕獲變量呢屯伞?
#import "Person.h"
@implementation Person
- (void)test {
void(^block)(void) = ^{
NSLog(@"%@", self.name);
NSLog(@"%@", _name);
};
block();
}
不論對(duì)象方法還是類方法都會(huì)默認(rèn)將self作為參數(shù)傳遞給方法內(nèi)部豪直,既然是作為參數(shù)傳入,那么self肯定是局部變量末融。上面講到局部變量肯定會(huì)被block捕獲暇韧。
對(duì)于block中使用的是實(shí)例對(duì)象的屬性時(shí),block捕獲的是實(shí)例對(duì)象巧婶,并通過實(shí)例對(duì)象的方法選擇器去獲取使用到的屬性涂乌;而對(duì)于block中使用的是實(shí)例對(duì)象的成員變量來說,block捕獲的仍然是實(shí)例對(duì)象湿右,然后通過成員變量的地址訪問罚勾。
因此,導(dǎo)致block會(huì)發(fā)生循環(huán)引用的問題來了堰塌,
也就是說分衫,如果某個(gè)類強(qiáng)引用了某個(gè)block,又在這個(gè)block中訪問了這個(gè)類牵现,就會(huì)造成引用循環(huán),因?yàn)閎lock會(huì)對(duì)內(nèi)部的實(shí)例對(duì)象進(jìn)行捕獲科乎,這種捕獲是一個(gè)強(qiáng)引用贼急。
而解決這個(gè)引用循環(huán)的方式也很簡單,就是使用一個(gè)weak指針修飾一下將要在block中使用的對(duì)象就可以了空闲。
-
Timer循環(huán)引用
NSTimer 的 target 對(duì)傳入的參數(shù)都是強(qiáng)引用(即使是 weak 對(duì)象)
解決方法:在不需要timer的時(shí)候調(diào)用一下invalidate方法即可碴倾。
4.1 對(duì)于block掉丽,是否都需要使用weakSelf來解決循環(huán)引用問題?
當(dāng)block本身不被self持有,而被別的對(duì)象持有捶障,同時(shí)不產(chǎn)生循環(huán)引用的時(shí)候残邀,就不需要使用weakSelf了柑蛇。
例如UIView的某個(gè)負(fù)責(zé)動(dòng)畫的對(duì)象持有了 block
block 持有了 self
因?yàn)閟elf并不持有block,所以就沒有循環(huán)引用產(chǎn)生空免,就不需要使用weakSelf了盆耽。