級別: ★★☆☆☆
標籤:「iOS」「內存管理」「Objective-C」
做者: MrLiuQ
審校: QiShare團隊php
前言: 這幾篇文章是小編在鑽研《Effective Objective-C 2.0》的知識產出,其中包含做者和小編的觀點,以及小編整理的一些demo。但願能幫助你們以簡潔的文字快速領悟原做者的精華。 在這裏,QiShare團隊向原做者Matt Galloway表達誠摯的敬意。html
文章目錄以下:
iOS 編寫高質量Objective-C代碼(一)
iOS 編寫高質量Objective-C代碼(二)
iOS 編寫高質量Objective-C代碼(三)
iOS 編寫高質量Objective-C代碼(四)
iOS 編寫高質量Objective-C代碼(五)
iOS 編寫高質量Objective-C代碼(六)
iOS 編寫高質量Objective-C代碼(七)
iOS 編寫高質量Objective-C代碼(八)git
本篇的主題是iOS中的 「內存管理機制」。github
說到iOS內存管理,逃不過iOS的兩種內存管理機制:MRC & ARC。
先簡單介紹一下:
MRC(manual reference counting): 「手動引用計數」 ,由開發者管理內存。 ARC(automatic reference counting):「自動引用計數」,從iOS 5
開始支持, 由編譯器幫忙管理內存。web
iOS 4
以前,全部iOS開發者必需要手動管理內存,即手動管理對象的內存分配和釋放。首先,不斷插入retain
、release
等內存管理語句,大大加大了工做量和代碼量。其次,在面對一些多線程併發操做時,開發者手動管理內存並不簡單,還可能會帶來不少沒法預知的問題。
因此,蘋果從iOS 5
開始引入ARC機制,由編譯器幫忙管理內存。在編譯期,編譯器會自動加上內存管理語句。這樣,開發者能夠更加關注業務邏輯。編程
下面進入正題:編寫高質量Objective-C代碼(五)——內存管理篇。數組
這裏引入《Objective-C 高級編程 iOS與OSX多線程和內存管理》這本書的例子: 很經典的圖解:安全
解釋:
1.開燈:引伸爲:「 建立對象 」。
2.關燈:引伸爲:「 銷燬對象 」。
bash
解釋:
1.有人來上班打卡了:開燈。——(建立對象,計數爲1)
2.又有人來了:保持開燈。——(保持對象,計數爲2)
3.又有人來了:保持開燈。——(保持對象,計數爲3)
4.有人下班打卡了:保持開燈。——(保持對象,計數爲2)
5.又有人下班了:保持開燈。——(保持對象,計數爲1)
6.全部員工全下班了:關燈。——(銷燬對象,計數爲0)微信
場景 | 對應OC的動做 | 對應OC的方法 |
---|---|---|
上班開燈 | 生成對象 | alloc/new/copy/mutableCopy等 |
須要照明 | 持有對象 | retain |
不須要照明 | 解除持有 | release |
下班關燈 | 銷燬對象 | dealloc |
若是以爲本書中的例子說的有點抽象難懂,不要緊,請看下面圖解示例:
提示:實箭頭爲強引用,虛箭頭爲弱引用。
這裏有個set方法的例子:
- (void)setObject:(id)object {
[object retain];// Added by ARC
[_object release];// Added by ARC
_object = object;
}
複製代碼
解釋:set方法將保留新值,釋放舊值,而後更新實例變量。這三個語句的順序很重要。 若是先release
再retain
。那麼該對象可能已經被回收,此時retain
操做無效,由於對象已釋放。這時實例變量就變成了懸掛指針。(懸掛指針:指針指nil的指針。)
main
函數裏就有一個autoreleasepool
(自動釋放池)。int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
複製代碼
autorelease
能延長對象的生命週期,在對象跨越「方法調用邊界」後(就是}
後)依然能夠存活一段時間。
循環引用(retain cycle
)又稱爲「保留環」。 造成循環引用的緣由:是對象之間互相經過強指針指向對方(或者說互相強持有對方)。 在開發中,咱們不但願出現循環引用,由於會形成內存泄漏。 解決方案:有一方使用弱引用(weak reference
),解開循環引用,讓多個對象均可以釋放。 PS:關於如何檢驗項目中有無內存泄漏:參考這篇博客。
,在ARC環境下,禁止🚫調用:retain
、release
、autorelease
、dealloc
方法。
使用ARC時必須遵循的方法命名規則: 若方法名以alloc
、new
、copy
、mutableCopy
開頭,則規定返回的對象歸調用者。
變量的內存管理語義:
對比一下MRC和ARC在代碼上的區別
MRC環境下:
- (void)setObject:(id)object {
[_object release];
_object = [object retain];
}
複製代碼
這樣會出現一種邊界狀況,若是新值和舊值是同一個對象,那麼會先釋放掉,object就變成懸掛指針。
ARC環境下:
- (void)setObject:(id)object {
_object = object;
}
複製代碼
ARC會用一種更安全的方式解決邊界問題:先保留新值,再釋放舊值,最後更新實例變量。
同時,ARC能夠經過修飾符來改變局部變量和實例變量的語義:
修飾符 | 語義 |
---|---|
__strong | 默認,強持有,保留此值。 |
__weak | 不保留此值,安全。對象釋放後,指針置nil。 |
__unsafe_unretained | 不保留此值,不安全。對象釋放後,指針依然指向原地址(即不置nil)。 |
__autoreleasing | 此值在方法返回時自動釋放。 |
MRC中,開發者須要在dealloc
中動插入必要的清理代碼(cleanup code)。 而ARC會借用Objective-C++
的一項特性來完成清理任務,回收OC++對象時,會調用C++的析構函數:底層走.cxx_destruct
方法。而當釋放OC對象時,ARC在.cxx_destruct
底層方法中添加所須要的清理代碼(這個方法底層的某個時機會調用dealloc
方法)。 不過若是有非OC的對象,仍是要重寫dealloc
方法。好比CoreFoundation
中的對象或是malloc()
分配在堆中的內存依然須要清理。這時要適時調用CFRetain
/CFRelease
。
- (void)dealloc {
CFRelease(_coreFoundationObject);
free(_heapAllocatedMemoryBlob);
}
複製代碼
調用dealloc
方法時,對象已經處於回收狀態了。這時不能調用其餘方法,尤爲是異步執行某些任務又要回調的方法。若是異步執行完回調的時候對象已經摧毀,會直接crash。
dealloc
方法裏要作些釋放相關的事情,好比:
舉個例子:
- (void)viewDidLoad {
//....
[webView addObserver:self forKeyPath:@"canGoBack" options:NSKeyValueObservingOptionNew context:nil];
[webView addObserver:self forKeyPath:@"canGoForward" options:NSKeyValueObservingOptionNew context:nil];
[webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:nil];
[webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
}
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
self.backItem.enabled = self.webView.canGoBack;
self.forwardItem.enabled = self.webView.canGoForward;
self.title = self.webView.title;
self.progressView.progress = self.webView.estimatedProgress;
self.progressView.hidden = self.webView.estimatedProgress>=1;
}
- (void)dealloc {
[self.webView removeObserver:self forKeyPath:@"canGoBack"];//< 移除KVO
[self.webView removeObserver:self forKeyPath:@"canGoForward"];
[self.webView removeObserver:self forKeyPath:@"title"];
[self.webView removeObserver:self forKeyPath:@"estimatedProgress"];
}
複製代碼
- (void)viewDidLoad {
//......
// 添加響應通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tabBarBtnRepeatClick) name:BQTabBarButtonDidRepeatClickNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(titleBtnRepeatClick) name:BQTitleButtonDidRepeatClickNotification object:nil];
}
// 移除通知
- (void)dealloc {
// [[NSNotificationCenter defaultCenter] removeObserver:self name:BQTabBarButtonDidRepeatClickNotification object:nil];
// [[NSNotificationCenter defaultCenter] removeObserver:self name:BQTitleButtonDidRepeatClickNotification object:nil];
// 或者使用一個語句所有移除
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
複製代碼
異常只應在發生嚴重錯誤後拋出。
用的很差會形成內存泄漏:在try
塊中,若是先保留了某個對象,而後在釋放它以前又拋出了異常,那麼除非catch塊能解決問題,不然對象所佔內存就會泄漏。
緣由:C++
的析構函數由Objective-C
的異常處理例程來運行。因爲拋出異常會縮短生命期,因此發生異常時必須析構,否則就內存泄漏,而這時若是文件句柄(file handle)等系統資源沒有正確清理,就會發生內存泄漏。
try
塊內所創立的對象清理乾淨。-fobjc-arc-exceptions
標誌。但很影響性能。因此建議最好仍是不要用。但有種狀況是可使用的:Objective-C++
模式。PS:在運行期系統,C++
與Objective-C
的異常互相兼容。也就是說其中任一語言拋出的異常,能用另外一語言所編的**「異常處理程序」**捕獲。而在編寫Objective-C++
代碼時,C++處理異常所用的代碼與ARC實現的附加代碼相似,編譯器自動打開-fobjc-arc-exceptions
標誌,其性能損失不大。
最後,仍是建議:
nil
或者0
(例如:初始化的參數不合法,方法返回nil或0)NSError
這條比較簡單,內容主旨就是標題:以弱引用避免循環引用(Retain Cycle)
weak
)。weak
,ARC下,對象釋放時,指針會置nil
。@autoreleasepool
):每次for循環都會直接釋放內存,從而下降了內存的峯值。尤爲,在遍歷處理一些大數組或者大字典的時候,可使用自動釋放池來下降內存峯值,例如:
NSArray *qiShare = /*一個很大的數組*/
NSMutableArray *qiShareMembersArray = [NSMutableArray new];
for (NSStirng *name in qiShare) {
@autoreleasepool {
QiShareMember *member = [QiShareMember alloc] initWithName:name];
[qiShareMembersArray addObject:member];
}
}
複製代碼
PS:自動釋放池的原理:排布在「棧」中,對象執行autorelease消息後,系統將其放入最頂端的池裏(進棧),而清空自動釋放池就是把對象銷燬(出棧)。而調用出棧的時機:就是當前線程執行下一次事件循環時。
如上圖,勾選這裏能夠開啓殭屍對象設置。開啓以後,系統在回收對象時,不將其真正的回收,而是把它的isa指針
指向特殊的殭屍類(zombie class),變成殭屍對象。殭屍類可以響應全部的選擇子,響應方式爲:打印一條包含消息內容以及其接收者的消息,而後終止應用程序。
殭屍對象簡單原理:在Objective-C的運行期程序庫、Foundation框架以及CoreFoundation框架的底層加入了實現代碼。在系統即將回收對象時,經過一個環境變量
NSZombieEnabled
識別是殭屍對象——不完全回收,isa
指針指向殭屍類而且響應全部選擇子。
在蘋果引入ARC以後retainCount已經正式廢棄,任什麼時候候都無法調用這個retainCount方法來查看引用計數了,由於這個值實際上已經沒有準確性了(並且在ARC環境下也調用不了)。可是在MRC下仍是能夠正常使用的。
最後,特別緻謝:《Effective Objective-C 2.0》第五章。
關注咱們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公衆號)
推薦文章:
iOS與JS交互之WKWebView-WKUIDelegate協議
若是360推出辣椒水,各位女士會買嗎?
從撒狗糧帶你瞭解WoT鏈接場景