https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html#//apple_ref/doc/uid/TP40004447
?
http://en.wikipedia.org/wiki/Reference_counting
【IT168 技術文檔】開發iPhone 應用程序并不難,基本上就是三個詞 - “memory, memory, memory” 。iPhone OS 對內存的要求很嚴格,有memory leak ,殺掉;內存使用超限額,殺掉。一個經過測試的程序,在使用過程中90%以上的崩潰都是內存問題造成的。在這里簡單總結一下Object-C 內存管理。
基本概念
Object-C 的內存管理基于引用計數(Reference Count)這種非常常用的技術。簡單講,如果要使用一個對象,并希望確保在使用期間對象不被釋放,需要通過函數調用來取得“所有權”,使用結束后再調用函數釋放“所有權”。“所有權”的獲得和釋放,對應引用計數的增加和減少,為正數時代表對象還有引用,為零時代表可以釋放。
函數
獲得所有權的函數包括
* alloc - 創建對象是調用alloc,為對象分配內存,對象引用計數加一。
* copy - 拷貝一個對象,返回新對象,引用計數加一。
* retain - 引用計數加一,獲得對象的所有權。
另外,名字中帶有alloc, copy, retain 字串的函數也都認為會為引用計數加一。
釋放所有權的函數包括
* release - 引用計數減一,釋放所有權。如果引用計數減到零,對象會被釋放。
* autorelease - 在未來某個時機釋放。下面具體解釋。
autorelease
在某些情況下,并不想取得所有權,又不希望對象被釋放。例如在一個函數中生成了一個新對象并返回,函數本身并不希望取得所有權,因為取得后再沒有機會釋放(除非創造出新的調用規則,而調用規則是一切混亂的開始),又不可能在函數內釋放,可以借助autorelease 。所謂autorelease , 可以理解為把所有權交給一個外在的系統(這個系統實際上叫autorelease pool),由它來管理該對象的釋放。通常認為交給 autorelease 的對象在當前event loop 中都是有效的。也可以自己創建NSAutoreleasePool 來控制autorelease的過程。
據蘋果的人說,autorelease效率不高,所以能自己release的地方,盡量自己release,不要隨便交給autorelease來處理。
規則
引用計數系統有自己的引用規則,遵守規則就可以少出錯:
* 獲得所有權的函數要和釋放所有權的函數一一對應。
* 保證只有帶alloc, copy, retain 字串的函數才會讓調用者獲得所有權,也就是引用計數加一。
* 在對象的 dealloc函數中釋放對象所擁有的實例變量。
* 永遠不要直接調用dealloc來釋放對象,完全依賴引用計數來完成對象的釋放。
有很多類都提供“便利構造函數(convenience constructors)”,它們創建對象但并不增加引用計數,意味著不需要調用release來釋放所有權。很好辨認,它們的名字中不會有alloc和copy。
只要遵守這些規則,基本上可以消除所有Intrument可以發現的內存泄露問題。
容器
類似NSArray, NSDictionary, NSSet 等類,會在對象加入后引用計數加一獲得所有權,在對象被移除或者整個容器對象被釋放的時候釋放容器內對象的所有權。類似的情況還有UIView對 subview的所有權關系,UINavigationController對其棧上的controller的所有權關系等等。
其他所有權的產生
還有一些用法會讓系統擁有對象的所有權。比如NSObject 的performSelector:withObject:afterDelay 。如果有必要,需要顯示的調用cancelPreviousPerformRequestsWithTarget:selector:object: ,否則有可能產生內存泄露。
因這種原因產生的泄露因為并不違反任何規則,是Intrument所無法發現的。
循環引用
所有的引用計數系統,都存在循環應用的問題。例如下面的引用關系:
* 對象a創建并引用了對象b.
* 對象b創建并引用了對象c.
* 對象c創建并引用了對象b.
這時候b和c的引用計數分別是2和1。當a不再使用b,調用release釋放對b的所有權,因為c還引用了b,所以b的引用計數為1,b不會被釋放。b不釋放,c的引用計數就是1,c也不會被釋放。從此,b和c永遠留在內存中。
這種情況,必須打斷循環引用,通過其他規則來維護引用關系。比如,我們常見的delegate往往是assign方式的屬性而不是retain方式的屬性,賦值不會增加引用計數,就是為了防止delegation兩端產生不必要的循環引用。如果一個UITableViewController 對象a通過retain獲取了UITableView對象b的所有權,這個UITableView對象b的delegate又是a,如果這個 delegate是retain方式的,那基本上就沒有機會釋放這兩個對象了。自己在設計使用delegate模式時,也要注意這點。
因為循環引用而產生的內存泄露也是Instrument無法發現的,所以要特別小心。