以前發了一篇關於圖片加載優化的文章,仍是引發不少人關注的,不過也有好多人反饋看不太懂,此次談談iOS中ARC的一些使用注意事項,相信作iOS開發的不會對ARC陌生啦。
這裏不是談ARC的使用,只是介紹下ARC下仍然可能發生的內存泄露問題,可能不全,歡迎你們補充。html
Ps:關於ARC的使用以及內存管理問題,強烈建議看看官方文檔,裏面對內存管理的原理有很詳細的介紹,相信用過MRC的必定看過這個。ios
另也有簡單實用的ARC使用教程:ARC Best Practicesapp
在2011年的WWDC中,蘋果提到90%的crash是因爲內存管理引發的,ARC(
Automatic Reference Counting
)就是蘋果給出的解決方案。啓用ARC後,開發者不須要擔憂內存管理,編譯器會爲你處理這一切(注意ARC是編譯器特性,而不是iOS運行時特性,更不是其餘語言中的垃圾收集器)。
簡單來講,編譯器在編譯代碼時,會自動生成實例的引用計數代碼,幫助咱們完成以前MRC須要完成的工做,不過聽說除此以外,編譯器也會執行某些優化。函數
ARC雖然可以解決大部分的內存泄露問題,可是仍然有些地方是咱們須要注意的。oop
循環引用簡單來講就是兩個對象相互強引用了對方
,即retain了對方,從而致使誰也釋放不了誰的內存泄露問題。好比聲明一個delegate時通常用weak而不能用retain或strong,由於你一旦那麼作了,很大可能引發循環引用。測試
這種簡單的循環引用只要在coding的過程當中多加註意,通常均可以發現。
解決的辦法也很簡單,通常是將循環鏈中的一個強引用改成弱引用就可解決。
另一種block引發的循環引用問題,一般是一些對block原理不太熟悉的開發者不太容易發現的問題。優化
咱們先看看官方文檔關於block調用時的解釋:Object and Block Variablesui
When a block is copied, it creates strong references to object variables used within the block. If you use a block within the implementation of a method:this
- If you access an instance variable by reference, a strong reference is made to self;
- If you access an instance variable by value, a strong reference is made to the variable.
主要有兩條規則:第一條規則,若是在block中訪問了屬性,那麼block就會retain住self。
第二條規則,若是在block中訪問了一個局部變量,那麼block就會對該變量有一個強引用,即retain該局部變量。
url
根據這兩條規則,咱們能夠知道發生循環引用的狀況:
//規則1 self.myblock = ^{ [self doSomething]; // 訪問成員方法 NSLog(@"%@", weakSelf.str); // 訪問屬性 }; //規則2 ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url]; [request setCompletionBlock:^{ NSString* string = [request responseString]; }];
對象對block擁有一個強引用,而block內部又對外部對象有一個強引用,造成了閉環,發生內存泄露。
怎麼解決這種內存泄露呢?
能夠用block變量來解決,首先仍是看看官方文檔怎麼說的:
Use Lifetime Qualifiers to Avoid Strong Reference Cycles
In manual reference counting mode,
__block id x
; has the effect of not retaining x. In ARC mode,__block id x
; defaults to retaining x (just like all other values). To get the manual reference counting mode behavior under ARC, you could use__unsafe_unretained __block id x
;. As the name__unsafe_unretained
implies, however, having a non-retained variable is dangerous (because it can dangle) and is therefore discouraged. Two better options are to either use__weak
(if you don’t need to support iOS 4 or OS X v10.6), or set the__block
value to nil to break the retain cycle.
官網提供了幾種方案,咱們看看第一種,用__block變量:
在MRC中,__block id x不會retain住x;可是在ARC中,默認是retain住x的,咱們須要
使用__unsafe_unretained __block id x來達到弱引用的效果。
那麼解決方案就以下所示:
__block id weakSelf = self; //MRC //__unsafe_unretained __block id weakSelf = self; ARC下面用這個 self.myblock = ^{ [weakSelf doSomething]; NSLog(@"%@", weakSelf.str); };
<!-- ## performSelector的問題 [self performSelector:@selector(foo:) withObject:self.property afterDelay:3]; performSelector延時調用的原理是這樣的,執行上面這段函數的時候系統會自動將self.property的`retainCount`加1,直到selector執行完畢以後纔會將self.property的`retainCount`減1。這樣子若是selector一直未執行的話,self就一直不可以被釋放掉,就有可能照成內存泄露。比較好的解決方案是將未執行的perform給取消掉: [NSObject cancelPreviousPerformRequestsWithTarget:self]; 因這種緣由產生的泄露由於並不違反任何規則,是Intrument所沒法發現的。 -->
咱們都知道timer用來在將來的某個時刻執行一次或者屢次咱們指定的方法,那麼問題來了(固然不是挖掘機)。究竟系統是怎麼保證timer觸發action的時候,咱們指定的方法是有效的呢?萬一receiver無效了呢?
答案很簡單,系統會自動retain住其接收者,直到其執行咱們指定的方法。
看看官方的文檔吧,也建議你本身寫個demo測試一下。
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
target
The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to target until it (the timer) is invalidated. (系統會維護一個強引用直到timer調用invalidated) userInfo The user info for the timer. The timer maintains a strong reference to this object until it (the timer) is invalidated. This parameter may be nil. repeats If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.
能夠注意到repeats
參數,一次性(repeats爲NO)的timer會再觸發後自動調用invalidated,而重複性的timer則不會。
如今問題又來了,看看下面這段代碼:
- (void)dealloc { [timer invalidate]; [super dealloc]; }
這個是很容易犯的錯誤,若是這個timer是個重複性的timer,那麼self
對象就會被timerretain
住,這個時候不調用invalidate
的話,self
對象的引用計數會大於1,dealloc
永遠不會調用到,這樣內存泄露就會發生。
timer都會對它的target進行retain,咱們須要當心對待這個target的生命週期問題,尤爲是重複性的timer,同時須要注意在dealloc以前調用invalidate。
關於timer其實有挺多能夠研究的,好比其必須在runloop中才有效,好比其時間必定是準的嗎?這些因爲和本章主題不相關,暫時就不說了。
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay
咱們仍是看看官方文檔怎麼說的,一樣也但願你們能寫個demo驗證下。
This method sets up a timer to perform the aSelector message on the current thread’s run loop. The timer is configured to run in the default mode (NSDefaultRunLoopMode). When the timer fires, the thread attempts to dequeue the message from the run loop and perform the selector. It succeeds if the run loop is running and in the default mode; otherwise, the timer waits until the run loop is in the default mode.
大概意思是系統依靠一個timer來保證延時觸發,可是隻有在runloop
在default mode
的時候纔會執行成功,不然selector
會一直等待run loop
切換到default mode
。
根據咱們以前關於timer
的說法,在這裏其實調用performSelector:afterDelay:一樣會形成系統對target
強引用,也即retain
住。這樣子,若是selector
一直沒法執行的話(好比runloop
不是運行在default model
下),這樣子一樣會形成target
一直沒法被釋放掉,發生內存泄露。
怎麼解決這個問題呢?
其實很簡單,咱們在適當的時候取消掉該調用就好了,系統提供了接口:
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget
這個函數能夠在dealloc
中調用嗎,你們能夠本身思考下?
關於NSNotification的addObserver與removeObserver問題
咱們應該會注意到咱們經常會再dealloc
裏面調用removeObserver
,會不會上面的問題呢?
答案是否認的,這是由於addObserver
只會創建一個弱引用到接收者,因此不會發生內存泄露的問題。可是咱們須要在dealloc
裏面調用removeObserver
,避免通知的時候,對象已經被銷燬,這時候會發生crash
.
C 語言不可以調用OC中的retain與release,通常的C 語言接口都提供了release函數(好比CGContextRelease(context c))來管理內存。ARC不會自動調用這些C接口的函數,因此這仍是須要咱們本身來進行管理的.
下面是一段常見的繪製代碼,其中就須要本身調用release接口。
CGContextRef context = CGBitmapContextCreate(NULL, target_w, target_h, 8, 0, rgb, bmi); CGColorSpaceRelease(rgb); UIImage *pdfImage = nil; if (context != NULL) { CGContextDrawPDFPage(context, page); CGImageRef imageRef = CGBitmapContextCreateImage(context); CGContextRelease(context); pdfImage = [UIImage imageWithCGImage:imageRef scale:screenScale orientation:UIImageOrientationUp]; CGImageRelease(imageRef); } else { CGContextRelease(context); }
總的來講,ARC仍是很好用的,可以幫助你解決大部分的內存泄露問題。因此仍是推薦你們直接使用ARC,儘可能不要使用mrc。