什么是引用計數(shù)?
引用計數(shù)是一個簡單而有效的管理對象生命周期的方式。
- 當(dāng)我們創(chuàng)建一個新對象時,它的引用計數(shù)為1
- 當(dāng)有一個新的指針指向這個對象時,我們將引用計數(shù)加1
- 當(dāng)某個指針不再指向這個對象時,我們將引用計數(shù)減1
- 當(dāng)對象的引用計數(shù)為0時,說明這個對象不再被任何指針指向了,就可以將對象銷毀,回收內(nèi)存

引用計數(shù)的運(yùn)用場景
引用計數(shù)真正派上用場的場景是在面向?qū)ο蟮某绦蛟O(shè)計架構(gòu)中,用于對象之間傳遞和共享數(shù)據(jù)。
以對象M為例,哪些對象需要長時間使用這個對象,就把它的引用計數(shù)加1,使用完之后再把引用計數(shù)減1.所有對象都遵守這個規(guī)則的話,對象的生命周期管理就可以完全交給引用計數(shù)了。
ARC下的常見內(nèi)存管理問題
循環(huán)引用問題 (Reference Cycle)
引用計數(shù)這種管理內(nèi)存的方式雖然簡單,但是有一個比較大的瑕疵,它不能很好的解決循環(huán)引用問題
- 什么是循環(huán)引用問題?
對象A和對象B,相互引用了對方作為自己的成員變量,只有當(dāng)自己銷毀時,才會將成員變量的引用計數(shù)減1,這就導(dǎo)致了A的銷毀依賴于B的銷毀,同樣B的銷毀依賴于A的銷毀,這樣就造成了循環(huán)引用問題。

不僅僅只在兩個對象中存在循環(huán)引用問題,多個對象依次持有對方,形成一個環(huán)狀,也會造成循環(huán)引用問題。

- 如何解決循環(huán)引用問題?
主動斷開循環(huán)引用
在合理的位置主動斷開環(huán)中的一個引用,使得對象得以回收。

主動斷開循環(huán)引用這種方式常見于各種與block相關(guān)的代碼邏輯中。
例如:網(wǎng)絡(luò)請求的回調(diào)block是被持有的,如果這個block中又存在對于View Controller的引用,就容易產(chǎn)生循環(huán)引用。
- Controller 持有網(wǎng)絡(luò)請求對象
- 網(wǎng)絡(luò)請求對象持有了回調(diào)的block
- 回調(diào)的block里面使用了self,所以持有了Controller
// View Controller
- (void)sendRequest {
// Controller 持有網(wǎng)絡(luò)請求對象
Request *request = [Request alloc] initWithPath:@"https://www.baidu.com"];
[request startWithCompletionBlockWithSuccess:^(Block block) {
// 回調(diào)的block里面使用了self,所以持有了Controller
[self reloadView];
} failure:^(NSError *error) {
[self toast];
}
}
// Request
// 網(wǎng)絡(luò)請求對象持有了回調(diào)block
- (void)startWithCompletionBlockWithSuccess:(void (^)(Block block))success failure:(void (^)(NSError *error) {
self.successCompletionBlock = success;
self.failureCompletionBlock = failure;
}
解決這個問題的辦法就是在網(wǎng)絡(luò)請求結(jié)束后,主動釋放對block的持有,打破循環(huán)引用
- (void)clearCompletionBlock {
self.successCompletionBlock = nil;
self.failureCompletionBlock = nil;
}
使用弱引用
主動斷開循環(huán)引用需要程序員能夠準(zhǔn)確發(fā)現(xiàn)循環(huán)引用,并知道什么時機(jī)斷開循環(huán)引用,所以這種解決方法并不常見,更常見的辦法是使用弱引用
弱引用雖然持有對象,但是不增加引用計數(shù),這樣就避免了循環(huán)引用的產(chǎn)生。例如delegate模式中的weak聲明。View Controller的delegate成員變量通常是一個弱引用,以避免兩個View Controller互相引用對方造成循環(huán)引用。

弱引用的實(shí)現(xiàn)原理
系統(tǒng)對于每一個有弱引用的對象,都維護(hù)一個表來記錄它所有的弱引用的指針地址。當(dāng)一個對象的引用計數(shù)為0時,系統(tǒng)就通過這張表,找到所有的弱引用指針,將他們置為nil。弱引用的使用是有額外的開銷的,雖然這個開銷很小,但是如果肯定不需要使用弱引用特性時,就不應(yīng)該盲目使用弱引用
Block如何避免循環(huán)引用
- 當(dāng)block本身不被self持有,而被別的對象持有,同時不產(chǎn)生循環(huán)引用的時候,就不需要weakself,最常見的代碼就是UIView的動畫代碼
[UIView animateWithDuration:0.2 animations:^{
self.alpha = 1;
}];
- 通過weakSelf和strongSelf解決循環(huán)引用
__weak __typeof (self)weakSelf = self;
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"Change" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (strongSelf) {
[strongSelf reload];
}
}
- 在block之前定義對self的弱引用weakSelf,因?yàn)槭侨跻?,所以self被釋放時weakSelf會變成nil
- 在block中引用該弱引用,考慮到多線程情況,通過強(qiáng)引用strongSelf來引用該弱引用,如果self不為nil,就會retain self,以防在block內(nèi)部使用過程中self被釋放
- 在block塊中使用該強(qiáng)引用strongSelf,注意對strongSelf進(jìn)行nil檢測,因?yàn)槎嗑€程在弱引用weakSelf對強(qiáng)引用strongSelf賦值時,弱引用weakSelf可能已經(jīng)為nil了
- 強(qiáng)引用strongSelf在block作用域結(jié)束之后,自動釋放
- weakSelf為什么需要strongSelf配合使用
在block塊中先寫一個strongSelf,是為了避免在block的執(zhí)行過程中,突然出現(xiàn)self被釋放的情況。
__weak與__block的區(qū)別
__weak可以避免循環(huán)引用,__block不能避免循環(huán)引用,__block的作用是提升變量的作用域
總結(jié)
- 引用計數(shù)是一種簡單高效的管理對象生命周期的方法
- 引用計數(shù)無法避免循環(huán)引用
- 循環(huán)引用的解決分為弱引用(事先規(guī)避)和主動斷開循環(huán)引用(事后補(bǔ)救)兩個方案
-
__weak可以避免循環(huán)引用,但是其存在外部對象釋放后,block內(nèi)部也訪問不到這個對象的問題,所以我們通過在block內(nèi)部聲明一個__strong的變量來指向weakObj,使得外部對象既能在block內(nèi)部保持,又能避免循環(huán)引用的問題 -
__block無法避免循環(huán)引用的問題,它的作用是提升了變量的作用域,在block內(nèi)外訪問的都是同一個對象,如果想要在block中改變變量的值,就需要在變量聲明的時候加上__block修飾符