1.什麼是內存管理
移動設備的內存極其有限,每個app所能占用的內存是有限制的 當app所占用的內存較多時,系統會發出內存警告,這時得回收一些不需要再使用的內存空間。比如回收一些不需要使用的對象、變量等 管理范圍:任何繼承了NSObject的對象需要去管理內存,但是對於對其他基本數據類型(int、char、float、double、struct、enum等)結構,枚舉等不用去關心內存 復制代碼 - (void)test { int a = 20; int b = 10; Person *person = [[Person alloc] init]; // 方法未退出之前 這三行代碼在內存中如圖 } 復制代碼 一旦test方法執行完畢,意味著局部變量的作用域也失效,那麼棧空間的局部變量系統會自動檢測回收。但是堆空間中動態產生的對象是還沒有被回收。如圖 可以看出即使已經沒有指針指向動態產生的對象了,但還是沒有被回收,因此需要手動管理釋放。釋放的方法是為對象發送一條消息。因此需要調用對象的某個方法來釋放對象。 那麼系統是怎麼知道此時的對象需不需要回收呢?這就涉及到了對象結構中的"引用計數" 2.對象結構 每個OC對象內部都有自己的引用計數器,它是一個整數,表示"對象被引用的次數",即有多少人正在使用這個OC對象 每個OC對象內部會自動設置4個字節的存儲空間來存儲引用計數器 3.引用計數器的作用 當使用alloc、new或者copy創建一個新對象時,新對象的引用計數器默認就是1 當一個對象的引用計數器值為0時,對象占用的內存就會被系統回收。換句話說,如果對象的計數器不為0,那麼在整個程序運行過程,它占用的內存就不可能被回收,除非整個程序已經退出 4.操作對象引用計數器的方法 給對象發送一條retain消息,可以使引用計數器值+1(retain方法返回對象本身)retain方法返回的是id類型,那麼哪個對象調用返回的就是自己 給對象發送一條release消息,可以使引用計數器值-1 可以給對象發送retainCount消息獲得當前的引用計數器值 5.對象的銷毀 當一個對象的引用計數器值為0時,那麼它將被銷毀,其占用的內存被系統回收 當一個對象被銷毀時,系統會自動向對象發送一條dealloc消息 一般會重寫dealloc方法,在這裡釋放相關資源,dealloc就像對象的遺言 一旦重寫了dealloc方法,就必須調用[super dealloc],並且放在最後面調用 不能直接調用dealloc方法 一旦對象被回收了,它占用的內存就不再可用,堅持使用會導致程序崩潰(野指針錯誤) 復制代碼 // alloc方法是給堆中分配內存 init方法和內存無關 此時retainCurrent為1 Person *p = [[Person alloc] init]; // 返回的就是對象本身 retainCurrent為2 [p retain]; // retainCurrent為1 [p release]; // retainCurrent為0 說明Person類對象被回收,那麼對應的在內存中的地址已經不可用了 此時的Person對象稱為“僵屍對象”但是此時p指針還是在指向Person類對象所對應的那塊不可用的地址此時的p指針稱為“野指針” [p release]; 復制代碼 6.開發中要注意的內存管理 默認情況下,Xcode是不會管理僵屍對象的,即使使用了一塊被釋放的內存也不會報錯。為了方便調試,應該開啟僵屍對象監控。如圖設置: 注意三個概念: 僵屍對象:已經被回收的對象,或者說對象所對應的內存地址已經不可用的對象稱為僵屍對象。僵屍對象不可用 野指針:指向一塊不可用內存地址或者指向僵屍對象的指針稱為野指針。給野指針發送消息會報 EXC_BAD_ACCESS錯誤 空指針:沒有指向任何指針變量稱為空指針,也意味著指針變量所存儲的值為0,nil,NULL 這樣可以避免野指針錯誤的發生 復制代碼 /********************************** Person.h **************************************/ #import <Foundation/Foundation.h> @interface Person : NSObject @property int age; @end /********************************** Person.m **************************************/ #import "Person.h" @implementation Person // 重寫父類NSOjbct的遺言方法 對象在被釋放之前一定會調用dealloc方法 - (void)dealloc { NSLog(@"對象在釋放之前會執行遺言方法被執行"); [super dealloc]; // 一定要調用 而且必須放在最後面 } @end /********************************** main.m **************************************/ #import <Foundation/Foundation.h> #import "Person.h" /* main方法是一個死循環方法以保證程序能持續運行,除非用戶關閉程序或者是手機沒電,程序才能終止 那麼在main方法裡面的Person對象不就一直存在麼,因此必須在main方法裡面將對象回收 */ int main(int argc, const char * argv[]) { // alloc方法是給堆中分配內存 init方法和內存無關 此時retainCurrent為1 Person *p = [[Person alloc] init]; // 返回的就是對象本身 retainCurrent為2 [p retain]; // retainCurrent為1 [p release]; /* retainCurrent為0 說明Person類對象被回收,那麼對應的在內存中的地址已經不可用了 此時的Person對象稱為“僵屍對象” 此時p指針還是在指向Person類對象所對應的那塊不可用的地址,此時p指針稱為“野指針” */ [p release]; /* 對象已經被回收,千萬別以為再給對象發送一個retain消息對象就可以“起死回生”應該節哀順變 執行retain方法會報錯,此時的p指針已稱為野指針執行代碼回報:野指針錯誤 */ // [p retain]; /* 此時對象已經被回收稱為“僵屍對象了”不可以再訪問屬性 在執行p.age = 10;報錯: -[Person setAge:]: message sent to deallocated instance 消息發送給了已經被釋放的對象 再次證明“僵屍對象不可以用” */ /* 一旦指針成為野指針再繼續向p指針所指的對象發送消息就會報錯:Exc_BAd_ACCESS 說明訪問了一塊壞內存(已經被回收、不可用的內存) “野指針錯誤” 那麼此時在對象回收之後將指針變量清空 那麼棧中的指針變量就不會再指向堆中類對象的內存地址了 */ p = nil; /* 指針變量內部所存儲的值已被清空,那麼指針已經無指向 再給指針發送任何消息指針會無任何響應,而且也不報錯因為OC中沒有空指針錯誤 */ [p release]; [p release]; [p release]; [p release]; return 0;