iOS 進階 - 記憶體管理(八) -- 迴圈引用
迴圈引用
三種型別迴圈引用
- 自迴圈引用
- 相互迴圈引用
- 多迴圈引用
自迴圈引用
假如有一個物件,內部強持有它的成員變數obj,
若此時我們給obj賦值為原物件時,就是自迴圈引用
相互迴圈引用
物件A內部強持有obj,物件B內部強持有obj,
若此時物件A的obj指向物件B,同時物件B中的obj指向物件A,就是相互引用
多迴圈引用
假如類中有物件1…物件N,每個物件中都強持有一個obj,
若每個物件的obj都指向下個物件,就產生了多迴圈引用
常見迴圈引用
- 代理
- Block
- NSTimer
- 大環引用
如何破除迴圈引用
- 避免產生迴圈引用
在使用代理時,兩個物件,一個強引用,一個弱引用,避免產生相互迴圈引用 - 在合適的時機手動斷環
具體方案
- __weak
- __block
- __unsafe_unretained 用這個的關鍵字修飾的物件也沒有增加引用計數,和__weak在效果上是等效的
__weak破解
物件B會強持有A,物件A弱引用B
__block破解
__block在ARC和MRC中是不同的
MRC下,__block修飾物件不會增加其引用計數,避免了迴圈引用
ARC下,__block修飾物件會被強引用,無法避免迴圈引用,需手動解環
__unsafe_unretained破解
- 修飾物件不會增加其引用計數,避免了迴圈引用
- 如果被修飾的物件在某一時機被釋放,會產生懸垂指標,再通過這個指標去訪問原物件的話,會導致記憶體洩露,所以一般不建議用__unsafe_unretained去解除迴圈引用
迴圈引用示例
NSTimer
假如VC中有個廣告欄,需要1S中滾動一次播放下一個廣告,
我們會把這個廣告欄的UI物件作為VC的成員變數,由VC進行強持有
因為廣告欄每隔1S需要滾動播放,則廣告欄中會新增成員變數NSTimer並強引用,
當分配定時回撥事件之後,NSTimer會對廣告欄的Target進行強引用,就產生了相互迴圈引用
如果把物件對NSTimer的強引用改為弱引用,是無法解決問題的,原因如下圖:
因為當NSTimer被分配之後,會被當前執行緒的Runloop進行強引用,
如果物件以及NSTimer是在主執行緒建立的,就會被主執行緒的Runloop持有這個NSTimer,
所以即使我們在廣告欄中通過弱引用來指向NSTimer,但是由於主執行緒中Runloop常駐記憶體通過對NSTimer的強引用,
再通過NSTimer對物件的強引用,仍然對這個物件產生了強引用,
此時即使VC頁面退出,去掉VC對物件的引用,
當前廣告欄仍然有被Runloop的間接強引用持有,這個物件也不會被釋放,此時就產生了記憶體洩露
解決方法:NSTimer分重複定時器和非重複定時器
非重複定時器:
在定時器的回撥方法中去呼叫[NSTimer invalid]同時將NSTimer置為nil,
可以將Runloop對NSTimer的強引用解除掉,同時NSTimer也解除了對物件的強引用。
重複定時器:
不能在定時器的回撥方法中去呼叫[NSTimer invalid]以及將NSTimer置為nil操作,此時的解決方案是:
左側是Runloop對NSTimer的強引用,右側是VC對物件的強引用,
可以在NSTimer和物件中間新增一箇中間物件,然後由NSTimer對中間物件進行強引用,
同時中間物件分別對NSTimer和廣告欄物件進行弱引用,那麼對於重複物件而已,
噹噹前VC退出之後,VC就釋放了對廣告欄物件的強引用,
當下次定時器的回撥事件回來的時候,可以在中間物件當中,判斷當前中間物件所持有的弱引用廣告欄物件是否被釋放了,
實際上就是判斷中間物件當中所持有的weak變數是否為nil,
如果是nil,然後呼叫[NSTimer invalid]以及將NSTimer置為nil,
就打破了Runloop對NSTimer的強引用以及NSTimer對中間物件的強引用
這個解決方案是利用了:當一個物件被釋放後,它的weak指標會自動置為nil
中間物件TimerWeakObject的實現
//NSTimer的NSTimer類別
#import <Foundation/Foundation.h>
@interface NSTimer (WeakTimer)
+ (NSTimer *)scheduledWeakTimerWithTimeInterval:(NSTimeInterval)interval
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)repeats;
@end
#import "NSTimer+WeakTimer.h"
@interface TimerWeakObject : NSObject
@property (nonatomic, weak) id target;//中間物件的弱引用指標
@property (nonatomic, assign) SEL selector;//定時器到時之後的一個回撥方法
@property (nonatomic, weak) NSTimer *timer;//中間物件的弱引用指標
- (void)fire:(NSTimer *)timer;
@end
@implementation TimerWeakObject
/*對它所持有的target進行判斷,若target存在,判斷它是否響應選擇器,
如果響應則執行對應的回撥方法
否則就把timer置為無效,就可以達到Runloop對Timer強引用的釋放,同時Timer也會對弱引用物件進行釋放
*/
- (void)fire:(NSTimer *)timer
{
if (self.target) {
if ([self.target respondsToSelector:self.selector]) {
[self.target performSelector:self.selector withObject:timer.userInfo];
}
}
else{
[self.timer invalidate];
}
}
@end
//分類中的具體實現
@implementation NSTimer (WeakTimer)
+ (NSTimer *)scheduledWeakTimerWithTimeInterval:(NSTimeInterval)interval
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)repeats
{
/*
建立中間物件,把我們傳進分類中的aTarget和aSelector指派給中間物件,
然後呼叫系統的NSTimer方法去建立NSTimer,
同時指定Timer的回撥事件是中間物件的fire方法,
然後再fire方法中再對實際物件回撥方法進行呼叫
*/
TimerWeakObject *object = [[TimerWeakObject alloc] init];
object.target = aTarget;
object.selector = aSelector;
object.timer = [NSTimer scheduledTimerWithTimeInterval:interval target:object selector:@selector(fire:) userInfo:userInfo repeats:repeats];
return object.timer;
}
@end
相關文章
- iOS迴圈引用iOS
- 記憶體二三事: Xcode 記憶體圖、Instruments 視覺化檢測迴圈引用記憶體XCode視覺化
- iOS 記憶體管理iOS記憶體
- iOS 關於NSTimer的迴圈引用iOS
- 「前端進階」JS中的記憶體管理前端JS記憶體
- iOS 記憶體管理MRCiOS記憶體
- “理解”iOS記憶體管理iOS記憶體
- iOS 記憶體管理研究iOS記憶體
- 解決NSTimer迴圈引用導致記憶體洩漏的六種方法記憶體
- Java進階10 記憶體管理與垃圾回收Java記憶體
- Rust 程式設計影片教程(進階)——015_1 引用迴圈Rust程式設計
- 迴圈引用
- iOS記憶體管理詳解iOS記憶體
- Rust 程式設計視訊教程(進階)——015_1 引用迴圈Rust程式設計
- Python迴圈引用是什麼?如何避免迴圈引用?Python
- iOS | 用於解決迴圈引用的block timeriOSBloC
- iOS-block迴圈引用詳解和應用iOSBloC
- linux記憶體管理(八)- 反向對映RMAPLinux記憶體
- 理解 iOS 和 macOS 的記憶體管理iOSMac記憶體
- 解決迴圈引用
- 高階記憶體管理程式設計指南-實用的記憶管理記憶體程式設計
- 記憶體管理 記憶體管理概述記憶體
- python如何進行記憶體管理Python記憶體
- Python如何進行記憶體管理?Python記憶體
- iOS 記憶體管理相關面試題iOS記憶體面試題
- iOS 問題整理07----記憶體管理iOS記憶體
- iOS開發-屬性的記憶體管理iOS記憶體
- require()迴圈引用問題UI
- 記憶體管理篇——實體記憶體的管理記憶體
- iOS記憶體管理佈局及管理方案-理論篇iOS記憶體
- 筆記-更深層次的瞭解iOS記憶體管理筆記iOS記憶體
- 【記憶體管理】記憶體佈局記憶體
- Spring如何解決迴圈引用Spring
- Java面試題中高階進階(JVM篇Java記憶體)Java面試題JVM記憶體
- 記憶體管理兩部曲之實體記憶體管理記憶體
- Java的記憶體 -JVM 記憶體管理Java記憶體JVM
- Go:記憶體管理與記憶體清理Go記憶體
- iOS使用頻率最高的四種記憶體管理iOS記憶體