OC中,繼承NSObject的類的對象在建立時,對象內部會自動建立一個內存計數器retainCount,當retainCount爲0時,系統會自動回收該對象。retainCount是判斷的惟一標記。 數據庫
進行retain操做時,返回的是指向本身的self指針。 數組
引用計數: 網絡
當代碼須要訪問一個對象時,該代碼將對象的引用計數器的值+1,表示「我要訪問該對象」。當代碼結束時,對象的引用計數-1,表示「我再也不訪問該對象」。 app
對象的引用計數爲零時,表示沒有代碼會再使用該對象。對象將會被銷燬,其佔用的內存被系統回收。- (void) release; 對象發送該消息使得引用計數-1 函數
在使用@property 和 @synthesize時,若是方法前有retain參數,生成的setter方法中會有retain操做. 工具
@property(retain)Car *car; -(void)setCar:(Car *)car //標準格式 { //1.先判斷是否是新傳進來的對象 If(car!=_car) { //2 對舊對象作一次release [_car release];//若沒有舊對象,則沒有影響 //3.對新對象作一次retain _car=[car retain]; } }
2.若是一個函數建立了一個對象,則該函數就擁有它所建立的對象。 測試
Cocoa內存管理的三條原則:3.保留某個對象時,最終須要釋放或者自動釋放該對象。必須保證retain和release的次數相同。 spa
牢記一條:若是使用new、alloc或者copy方法得到一個對象,則必須釋放或者自動釋放它。 命令行
基本的ARC使用規則 線程
代碼中不能使用retain, release, retain, autorelease
不重載dealloc(若是是釋放對象內存之外的處理,是能夠重載該函數的,可是不能調用[super dealloc])
不能使用NSAllocateObject, NSDeallocateObject
不能在C結構體中使用對象指針
id與void *間的若是cast時須要用特定的方法(__bridge關鍵字)
不能使用NSAutoReleasePool、而須要@autoreleasepool塊
不能使用「new」開始的屬性名稱 (若是使用會有下面的編譯錯誤」Property’s synthesized getter follows Cocoa naming convention for returning ‘owned’ objects」)
iOS平臺的內存使用引用計數的機制,而且引入了半自動釋放機制;這種使用上的多樣性,致使開發者在內存使用上很是容易出現內存泄漏和內存莫名的增 長狀況; 本文會介紹iOS平臺的內存使用原則與使用陷阱; 深度剖析autorelease機制;低內存報警後的處理流程;並結合自身實例介紹內存暴增的問題追查記錄以及相關工具的使用狀況;
iOS平臺內存常見問題
做爲iOS平臺的開發者,是否曾經爲內存問題而苦惱過?內存莫名的持續增加,程序莫名的 crash,難以發現的內存泄漏,這些都是iOS平臺內存相關的常見問題;本文將會詳細介紹iOS平臺的內存管理機制,autorelease機制和內存 的使用陷阱,這些將會解決iOS平臺內存上的大部分問題,提升了程序的穩定性;
1 iOS平臺內存管理介紹
iOS平臺的內存管理採用引用計數的機制;當建立一個對象時使用alloc或者allWithZone方法時,引用計數就會+1;當釋放對象使用release方法時,引用計數就是-1; 這就意味着每個對象都會跟蹤有多少其餘對象引用它,一旦引用計數爲0,該對象的內存就會被釋放掉;另外,iOS也提供了一種延時釋放的機制 AutoRelease,以這種方式申請的內存,開發者無需手動釋放,系統會在某一時機釋放該內存; 因爲iOS平臺的這種內存管理的多樣性,致使開發者在內存使用上很容易出現內存泄漏或者程序莫名崩潰的狀況,本文會詳細介紹iOS平臺內存的使用規範與技 巧以及如何利用工具避免或者發現問題;
2 iOS平臺內存使用原則
2.1 對象的全部權與銷燬
2.1.1 誰建立,誰釋放;
若是是以alloc,new或者copy,mutableCopy建立的對象,則必須調用release或者autorelease方法釋放內存;
若是沒有釋放,則致使內存泄漏!
2.1.2 誰retain,誰釋放;
若是對一個對象發送 retain消息,其引用計數會+1,則使用完必須發送release或者autorelease方法釋放內存或恢復引用計數;
若是沒有釋放,則致使內存泄漏!
2.1.3 沒建立且沒retain,別釋放;
不要釋放那些不是本身alloc或者retain的對象,不然程序會crash;
不要釋放autorelease的對象,不然程序會crash;
2.2 對象的深拷貝與淺拷貝
通常來講,複製一個對象包括建立一個新的實例,並以原始對象中的值初始化這個新的實例。 複製非指針型實例變量的值很簡單,好比布爾,整數和浮點數。複製指 針型實例變量有兩種方法。一種方法稱爲淺拷貝,即將原始對象的指針值複製到副本中。所以,原始對象和副本共享引用數據。另外一種方法稱爲深拷貝,即複製指針 所引用的數據,並將其賦給副本的實例變量。
2.2.1 深拷貝
深拷貝的流程是 先建立一個新的對象且引用計數爲1,並用舊對象的值初始化這個新對象;
ClassA* objA = [[ClassA alloc] init];
ClassA* objB = [objA copy];
objB是一個新對象,引用計數爲1,且objB的數據等同objA的數據;
注意: objB須要釋放,不然會引發內存泄漏!
2.2.2 淺拷貝
淺拷貝的流程是,無需引入新的對象,把原有對象的引用計數+1便可
ClassA* objA = [[ClassA alloc] init];
ClassA* objB = [objA retain];
注意: objB須要釋放,恢復objA的引用計數,不然會引發內存泄漏!
2.3對象的存取方法
2.3.1 屬性聲明和實現
變量聲明的經常使用屬性類型包括readonly,assign,retain和copy;且系統會自動爲聲明瞭屬性的變量生成set和get函數;
readonly屬性: 只能讀,不能寫;
assign屬性: 是默認屬性,直接賦值,沒有任何保留與釋放問題;
retain屬性: 會增長原有對象的引用計數而且在賦值前會釋放原有對象,而後在進行賦值;
copy屬性: 會複製原有對象,並在賦值前釋放原有對象,而後在進行賦值;
2.3.2 使用屬性聲明可能帶來的隱患
當一個非指針變量使用retain(或者copy)這個屬性時,儘可能不要顯性的release這個變量;直接給這個變量置空便可;不然容易產生過分釋放,致使程序crash; 例如:
ClassA類的strName是NSString* 類型的變量且聲明的屬性爲retain;
ClassA.strName = nil; /* 釋放原有對象且對此對象賦值爲空 */
[ClassA.strName release]; /* strName內存可能已經被釋放過了,將致使程序crash */
Assign這個屬性通常是非指針變量(布爾類型,整形等)時用這個類型;屬於直接賦值型,不須要考慮內存的保留與釋放;
若是一個指針類型的變量使用assign類型的屬性,有可能引用已經釋放的變量;致使程序crash; 例如:
ClassB* obj =[[[ClassB alloc] init] autorelease];
……
ClassA.strName = obj; /* strName 指向obj的內存地址*/
後續在使用ClassA.strName的時候, 由於obj是autorelease的,可能obj的內存已經被回收;致使引用無效內存,程序crash;
3iOS平臺AutoRelease機制
3.1 自動釋放池的常見問題
你們在開發iOS程序的時候,是否遇到過在列表滑動的狀況內存莫名的增加,頻繁訪問圖片的時候內存莫名的增加,頻繁的打開和關閉數據庫的時候內存莫名的增加…… 這些都是拜iOS的autorelease機制所賜;具體分析以下:
1: 滑動列表的時候,內存出現莫名的增加,緣由可能有以下可能:
沒有使用UITableView的reuse機制;
致使每顯示一個cell都用autorelease的方式從新alloc一次;
致使cell的內存不斷的增長;
每一個cell會顯示一個單獨的UIView, 在UIView發生內存泄漏,致使cell的內存不斷增加;
2: 頻繁訪問圖片的時候,內存莫名的增加;
頻繁的訪問網絡圖片,致使iOS內部API,會不斷的分配autorelease方式的buffer來處理圖片的解碼與顯示; 利用圖片cache能夠緩解一下此問題;
3: 頻繁打開和關閉SQLite,致使內存不斷的增加;
在進行SQLite頻繁打開和關閉操做,並且讀寫的數據buffer較大,那麼 SQLite在每次打開與關閉的時候,都會利用autorelease的方式分配51K的內存; 若是訪問次數不少,內存立刻就會頂到幾十兆,甚至上百兆! 因此針對頻繁的讀寫數據庫且數據buffer較大的狀況, 能夠設置SQLite的長鏈接方式;避免頻繁的打開和關閉數據庫;
3.2 自動釋放池的概念
NSAutoreleasePool內部包含一個數組(NSMutableArray),用來保存聲名爲autorelease的全部對象。若是一個對象聲明爲autorelease,系統所作的工做就是把這個對象加入到這個數組中去。
ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1,把此對象加入autorelease pool中
NSAutoreleasePool自身在銷燬的時候,會遍歷一遍這個數 組,release數組中的每一個成員。若是此時數組中成員的retain count爲1,那麼release以後,retain count爲0,對象正式被銷燬。若是此時數組中成員的retain count大於1,那麼release以後,retain count大於0,此對象依然沒有被銷燬,內存泄露。
3.3 自動釋放池的做用域與嵌套
AutoreleasePool是能夠嵌套使用的!
池是被嵌套的,嵌套的結果是個棧,同一線程只有當前棧頂pool實例是可用的:
當短生命週期內,好比一個循環中,會產生大量的臨時內存,能夠建立一個臨時的autorelease pool,這樣能夠達 到快速回收內存的目的;
3.4 自動施放池的手動建立與自動建立
3.4.1 須要手動建立自動釋放池
●若是你正在編寫一個不是基於Application Kit的程序,好比命令行工具,則沒有對自動釋放池的內置支持;你必 須本身建立它們。
●若是你生成了一個從屬線程,則一旦該線程開始執行,你必須當即建立你本身的自動釋放池;不然,你將會泄漏對象。
●若是你編寫了一個循環,其中建立了許多臨時對象,你能夠在循環內部建立一個自動釋放池,以便在下次迭代以前銷燬這些對象。這能夠幫助減小應用程序的最大內存佔用量。
3.4.2 系統自動建立自動釋放池
Application Kit會在一個事件週期(或事件循環迭代)的開端—好比鼠標按下事件—自動建立一個自動釋放池,而且在事件週期的結尾釋放它.
4 iOS平臺內存使用陷阱
4.1 重複釋放
在前文已經提到,不要釋放不是本身建立的對象; 釋放本身的autorelease對象,app會crash;
釋放系統的autorelease對象,app會crash;
4.2 循環引用
循環引用,容易產生野引用,內存沒法回收,最終致使內存泄漏!能夠經過弱引用的方式來打破循環引用鏈;所謂的弱引用就是不須要retain,直接賦值的方式,這樣的話,能夠避免循環引用的問題,可是須要注意的是,避免重複釋放的問題;
5 iOS平臺內存報警機制
因爲iOS平臺的內存管理機制,不支持虛擬內存,因此在內存不足的狀況,不會去Ram上 建立虛擬內存;因此一旦出現內存不足的狀況,iOS平臺會通知全部已經運行的app,不管是前臺app仍是後臺掛起的app,都會收到 memory warning的notice;一旦app收到memory warning的notice,就應該回收佔用內存較大的變量;
5.1 內存報警處理流程
1: app收到系統發過來的memory warning的notice;
2: app釋放佔用較大的內存;
3: 系統回收此app所建立的autorelease的對象;
4: app返回到已經打開的頁面時,系統從新調用viewdidload方法,view從新加載頁面數據;從新顯示;
5.2 內存報警測試方法 在Simulate上能夠模擬低內存報警消息; iOS模擬器 -> 硬件 -> 模擬內存警告; 開發者能夠在模擬器上來模擬手機上的低內存報警狀況,能夠避免因爲低內存報警引出的app的莫名crash問題;