1. 程式人生 > >OC學習筆記之OC物件的記憶體管理

OC學習筆記之OC物件的記憶體管理


一、為什麼要做記憶體管理
        相對於現在動不動就上T的硬碟外存來說,計算機的記憶體雖然也在提升在還是太小了,而現在的應用軟體也十分吃記憶體,程式執行程序中如果不管理記憶體,如果有洩露,系統記憶體將會越用越小,對移動裝置來說更是如此。蘋果手機的記憶體只有那麼大,如果應用程式不管理記憶體而佔用了過多的記憶體,系統肯定是不允許的,引發的閃退給使用者不好的體驗,那這絕對算不上一個成功的應用開發。
        程式在執行中,應該及時銷燬已經不需要的變數或者物件,及時釋放記憶體,以維持記憶體佔用的動態平衡。

二、OC記憶體管理的目標
        OC中記憶體管理的目標主要是任何繼承根類NSObject後建立的OC物件,對基本資料型別我們不需要關心,系統可以自動回收。因為在記憶體中基本資料型別和OC物件的存放位置不同,前者是用棧存放,後者是用堆存放。
比如:int a = 1; int b = 2; Person *p = [[Person alloc] init];這幾句程式碼在記憶體中的存放位置就分這兩類。小橋這裡回憶一MJ老師畫過的圖:
 

        如圖所示,基本資料型別的變數是用棧來臨時存放的,當過期的時候系統會自動回收,如下面的測試圖片:

        上圖說明,在main函式return之前,基本資料型別的變數佔用的記憶體會自動被釋放,而OC物件始終佔用記憶體,注意:
這裡的Person型別指標p也是基本型別,過期時也會被系統回收,這裡的作用域是main函式,所以return之前,p指標變數還在。但是p所指向的Person物件在整個程式結束之前是一直佔用記憶體的,如果p和a、b一樣是mian中的區域性變數,那麼在return之前p可能就會被釋放,如果p被釋放那麼問題來了,它指向的物件將成為匿名物件,因為物件還在,以後也不能夠釋放它,除非程式退出。

三、OC物件中的引用計數器

        OC物件中都有4個位元組的空間來存引用放計數器

作用:
        引用計數器表示的是這個物件被引用的次數,alloc、new或者copy出一個新物件,它的引用計數器初值就為1。
一旦它的引用計數器為0,那麼系統會毫不留情地回收這個物件,反過來說引用計數器不為0那麼它就始終佔用記憶體。於是如你所思,OC的記憶體管理就是通過平衡引用計數器來完成的,只要保證引用計數器在物件過期時成為0,那麼在程式執行過程中就能動態釋放過期物件,而避免記憶體洩露。但是,就小橋看來,保持物件引用計數器的平衡這幾個字遠沒有表面看上去那麼簡單。
        對單個獨立的物件來說,計數器加1減1是清楚明白的,但是當物件與物件之間聯絡起來的時候,彼此計數器什麼時候加1什麼時候減1就變得複雜了。 
 操作:
         每個物件有三個繼承自根類的方法與記憶體管理相關。
retain: 傳送rratin訊息給物件,將物件的引用計數器加1 ,返回值為物件本身;
release:傳送release訊息給物件,將物件的引用計數器減1,沒有返回值;
retainCount:獲得物件當前引用計數器的值; 
        前兩個就是維持OC物件引用計數器平衡的方法了,管理記憶體就是通過合適地呼叫這兩個方法,使得物件能在過期時被系統回收,而不是等到程式退出才被回收,如果程式從手機開機就一直執行,那過期物件不就一直佔用記憶體麼? 

四、記憶體管理相關的幾個概念
        殭屍物件已經被系統回收的物件叫作殭屍物件,不能再訪問。看一下小橋取消ARC並開啟Xcode的殭屍物件管理後的測試:

 野指標: 指向殭屍物件或者不能訪問的記憶體的指標叫做野指標,比如上圖中的p就是一個野指標,用野指標呼叫方法就會出上圖中的錯。
空指標: p = nil;或者 p =NULL;或者p = 0;這三個都是空指標,和野指標相反,在OC中用空指標呼叫方法是不會報錯的。
五、物件遺言 
        最後小橋再複習一下物件被回收前系統給物件傳送的訊息,這也是物件最後的一個動作了------  dealloc 
OC物件在銷燬前都要呼叫這個方法,用以釋放自己佔用的資源,再呼叫父類的 dealloc方法,讓父類做相同操作,最後完成所有資源的釋放。 管理記憶體時都要重寫這個方法,小橋重寫下這個方法:
//
//  檔案:Person.m
//  專案:部落格筆記
//
//  作者:葬花 橋
//  日期:14-5-8
//  版權:  Copyright (c) 2014年 itcast. All rights reserved.
//
#import "Person.h"

@implementation Person

- (void)dealloc
{
    NSLog(@"呼叫了dealloc方法!");
    [superdealloc];
}

@end