在上文我們知道了內存的五大區域組成,接下來了解對于內存的管理。
內存管理其實就是管理堆
因為分配堆的空間大小不確定,而且它的生命周期不確定,需要人為管理。
棧的數據雖然也是運行時臨時分配的,但是其會在方法調用結束后自動回收,所以無需關注。
全局區/靜態區和常量區、代碼區的生命周期都是APP運行的整個過程,所以系統會在APP運行結束后自動回收。
因此我們常說的內存管理很大程度上就是指管理堆
引用計數的認識
iOS提供引用計數方式來管理內存,當計數器為0時,就表示這個對象就成為了垃圾對象,系統會在合適的時候進行回收。
引用計數的時機:
- 新創建一個引用類型對象(alloc/new/copy/mutableCopy),計數為1
- retain的+1操作(只要被別人用到了,就要+1)
- 將對象賦值給其他變量
- 將對象賦值給其他實例變量或屬性
- 將對象加入到集合中
- 將對象作為參數傳遞
- 將對象作為返回值
- release的-1操作
- 將變量賦值為nil或其他值
- 將屬性或實例變量設置為nil或其他值
- 實例變量或屬性所在的對象被釋放
- 參數或局部變量離開函數時
- 將對象從集合中刪除
文藝的解釋
記得在《尋夢環游記》里對于一個人的死亡是這樣定義的:當這個這個世界上最后一個人都忘記你時,就迎來了終極死亡。類比于引用計數,就是每有一個人記得你時你的引用計數加1,每有一個人忘記你時,你的引用計數減1,當所有人都忘記你時,你就消失了,也就是從內存中釋放了。
如果再深一層,包含我們后面要介紹的ARC中的強引用和弱引用的話,那這個記住的含義就不一樣了。強引用就是你摯愛的親人,朋友等對你比較重要的人記得你,你的引用計數才加1。
而弱引用就是那種路人,一面之緣的人,他們只是對你有一個印象,他們記得你是沒有用的,你的引用計數不會加1。當你摯愛的人都忘記你時,你的引用計數歸零,你就從這個世界上消失了,而這些路人只是感覺到自己記憶中忽然少了些什么而已。
MRC手動引用計數
早期需要手動管理,也就是手動對引用計數進行加減,現在都是在系統自動進行引用計數的管理。
注意:
- 原則:計數加一和減一的調用次數一定要匹配,
- 計數為0時,系統自動調用dealloc方法來回收
- 內存回收后,只是代表著這個空間可以分配給別人,而不是真的空間被刪掉,或者內存數據被刪掉
- 在MRC模式下,必須遵守:誰創建,誰釋放,誰引用,誰管理
ARC自動引用計數
ARC簡單來說就是系統幫我們自動加上release(autoRelease)和retain方法,不需要手動添加。
注意:當對象沒有強指針引用時,會被回收,注意這里一定要是強指針,因為有弱指針,也會被立即回收
所有權修飾符
使用ARC時,處理對象類型或id類型時,一定要加上所有權修飾符,沒有加的話就默認是__strong,即強引用
- __strong:默認所有權修飾符,表示強引用,會增加引用計數
- __weak:表示弱引用,不會增加引用計數,只會在對象的弱引用計數表中進行計數。
- 可用于解決循環引用問題。
- 當對象被釋放前會通過判斷是否存在弱引用表,所有指向它的弱引用都會被釋放掉,這樣可以避免野指針問題。
- 不可以修飾基本數據類型,只能修飾引用類型
- iOS5以上版本可使用
- _unsafe_unretained:與__weak類似,區別在于,不能在對象被釋放后自動將引用設置為nil,iOS5以下版本使用,所以無需關注
- __autoreleasing:
- 它可以將對象注冊到autorelease,也就等同于在MRC下調用對象的autorelease方法
- 在ARC中,可以在@autorelease{}中來使用
- 一般來說會自動添加,不用顯式使用__autorelease
屬性修飾符
分別查看不同的屬性修飾符它們所代表的所有權修飾符是什么
- assign:__unsafe_unretained
- 因此他不能在對象被釋放后自動將引用設置為nil,所以以后只能用在基本數據類型,而不能用在引用類型
- retain:__strong
- retain也會增加引用計數
- strong:__strong
- 可以用在ARC上對屬性進行修飾,作為強引用
- copy:__strong
- copy的所有權也是__strong,所以也會進行強引用
- 區別在于會重新開辟一個空間,指向該引用,新對象引用+1,原來的對象并不會引用+1
- weak:__weak
- 在ARC中使用,作為弱引用。
- __unsafe_unretained:__unsafe_unretained
- 不要用,僅了解,因為只在iOS5以下版本才會使用
默認的關鍵字為:
引用類型:@property (atomic,readWrite,strong) UIView *view;
基本數據類型:@property (atomic,readWrite,assign) int *aa;
野指針和僵尸對象
野指針
指向一個已經被刪除的對象或者訪問受限內存區域的指針就是野指針
注意:野指針不是nil指針,而是指向了垃圾內存的指針
野指針的場景:
- 對象釋放后,指針沒有置空
- 使用unsafe_unretained修飾符,對象釋放后,沒有手動置為nil
- KVO沒有移除觀察者
- 對象提前釋放
- 異步函數中block使用的self沒有強引用,導致外部已經釋放掉,但是里面還在使用
- 對象多次釋放
- 多個線程同時對某個對象賦值但沒有加鎖,就可能多次release。
僵尸對象
一個已經被釋放掉的對象就是僵尸對象
一個OC對象的引用計數為0,調動dealloc后釋放之后,就是僵尸對象。
一個對象雖然被釋放掉了,但是數據依然在內存中,所以如果通過野指針去訪問僵尸對象,一旦這個僵尸對象的內存已經被分配給其他人了,就會出錯。
為什么不開啟僵尸對象檢測?
這樣每次通過指針訪問對象的時候都會檢查是否為僵尸對象,這樣很影響效率