天天看點

OC基礎15:記憶體管理和自動引用計數

1、什麼是ARC?

   (1)、ARC全名為Automatic Reference Counting,即是自動引用計數,會自動統計記憶體中對象的引用數,并在适當時候自動釋放對象;

   (2)、在工程中使用ARC非常簡單:隻需要像往常那樣編寫代碼,隻不過永遠不用寫retain、 release和autorelease三個關鍵字;

   (3)、在使用ARC之前,需要手動管理記憶體計數,這種機制稱為MRC,即是手動引用計數 (Manual Referecen Counting);

   (4)、ARC是Objective-C編譯器的特性,而不是運作時特性或者垃圾回收機制,ARC所做的隻不過是在代碼編譯時為你自動在合适的位置插入release或autorelease,就如同之前MRC時你所做的那樣;

   (5)、現在隻需要用一個指針指向這個對象,隻要指針沒有被置空,對象就會一直保持在堆上。當将指針指向新值時,原來的對象會被release一次。這對執行個體變量、synthesize的變量或者局部變量都是适用的。比如:

   NSString *firstName = self.textField.text;

   firstName現在指向NSString對象,這時這個對象(textField的内容字元串)将被hold住;

   (6)、ARC的一個基本規則是:隻要某個對象被任一strong指針指向,那麼它将不會被銷毀。如果對象沒有被任何strong指針指向,那麼就将被銷毀。在預設情況下,所有的執行個體變量和局部變量都是strong類型的。可以說strong類型的指針在行為上和MRC時代retain的property是比較相似的,而ARC中預設的指針類型就是strong;

   (7)、使用ARC以後再也不需要關心什麼時候retain,什麼時候release,但是這并不意味你可以不思考記憶體管理,你可能需要經常性地問自己這個問題:誰在持有着這個對象?

   比如下面的代碼,假設array是一個NSMutableArray數組并且裡面至少有一個對象:

   id obj = [array objectAtIndex:0]; 

   [array removeObjectAtIndex:0]; 

   NSLog(@"%@", obj);

   在MRC時代這幾行代碼應該就挂掉了,因為array中0号對象被remove以後就被立即銷毀了,是以obj指向了一個dealloced的對象,是以在NSLog的時候将出現EXC_BAD_ACCESS。而在ARC中由于obj是strong的,是以它持有了array中的首個對象,array不再是該對象的唯一持有者。即使我們從array中将obj移除了,它也不會被銷毀,因為它已經被别的指針持有了;

   (8)、ARC也有一些缺點,對于初學者來說,可能僅隻能将ARC用在objective-c對象上(也即繼承自NSObject的對象),但是如果涉及到較為底層的東西,比如Core Foundation中的malloc()或者free()等,ARC就鞭長莫及了,這時候還是需要自己手動進行記憶體管理;

   (9)、你必須時刻清醒誰持有了哪些對象,而這些持有者在什麼時候應該變為指向nil。

2、關于MRC:

   (1)、使用MRC時,為了保證某個對象存在,每當建立一個引用指向這個對象的時候,需要手工統計引用數,把引用數加1,使用以下語句:

       [xxxClass retain];

   (2)、當在某處不再需要這個對象時(但是可能别處還需要引用),要把引用數減1,使用以下語句:

       [xxxClass release];

   (3)、當對象的引用數為0時,理論上一般就不再需要引用到這個對象了,那麼就可以釋放它的記憶體,通過使用NSObject類的dealloc()方法來操作;

   (4)、在對象已經被銷毀的情況下卻還指向這個對象的引用,稱為懸挂指針。給懸挂指針發送消息可能會引起系統崩潰;

   (5)、當在定義的方法中建立了一個對象,并且最終要把這個對象用作方法的傳回值,那麼當方法的語句運作完的時候這個對象也還必須保留着以作傳回,不能銷毀。那麼就不能使用release()方法粗暴地釋放對象,可以使用以下語句:

       [xxxClass autorelease];

       這個方法會把這個對象添加到自動釋放池autoreleasepool中,在這個方法中做如下處理,假設在方法中建立的對象是xClass:

       XClass *xClass = [[[XClass alloc] init] autorelease];

       或者是寫在傳回語句上:

       return [xClass autorelease];

   (6)、在程式中使用來自Foundation、UIKit、APPKit架構的類時,需要首先建立一個自動釋放池,語句為:

       @autoreleasepool {

         ...

       }

       當執行到autoreleasepool的結尾時,系統會釋放這個池,對于所有發送過autorelease消息來添加到池中的對象,系統會自動對這些對象發送release消息,當某個對象的引用計數已經減至0時(是以如果某個對象的引用計數大于0,它還是不會被釋放),會發出dealloc消息,記憶體會被釋放;

   (7)、自動釋放池裡存放的不是實際的對象,僅僅是對象的引用;

   (8)、任何繼承自NSObject并以alloc、copy、mutableCopy和new為字首的方法建立的對象都不會被自動釋放,需要手動來管理;

3、關于屬性的特性assign、retain和copy:

   (1)、假設在聲明某個屬性p的時候使用了以上某個特性,那麼當為p指派newValue的時候:

       self.p = newValue;

   各個特性的效果各是這樣的:

   (2)、assign:這是預設特性,使用這個特性之後,指派語句相當于:

       p = newValue;

   (3)、retain:指派語句相當于:

       if (p != newValue) {

         [p release];

         p = [newValue retain];

       }

   (4)、copy:指派語句相當于:

       if (p != newValue) {

         [p release];

         p = [newValue copy];

       }

4、關于這個三個特性的進一步了解:

   (1)、假設你配置設定了一塊記憶體并存儲了一個值在裡面,然後把這塊記憶體的位址指派給了指針a,同時你希望指針b也共享這塊記憶體,于是你又把a直接指派給了b。這就是assign,簡單的指針複制,此時a 和b指向同一塊記憶體。

   assign的問題在于:假設a不再需要這塊記憶體時,它并不能直接釋放記憶體,因為a并不知道b是否還在使用這塊記憶體,如果a釋放了b卻還在使用這塊記憶體,那麼b就成了懸挂指針;

   (2)、而retain和assign差不多,也是指針複制,隻不過retain在複制指針的過程中使用到了引用計數:當記憶體被配置設定并且指派給a時,引用計數是1,當把a指派給b時引用計數增加到 2。

   是以這時如果a不再使用這塊記憶體,它隻需要把引用計數減1,表明自己不再擁有這塊記憶體。b不再使用這塊記憶體時也把引用計數減1即可。當引用計數變為0的時候,系統就知道該記憶體不再被任何指針所引用,可以把它直接釋放掉;

   (3)、copy是在你不希望a和b共享一塊記憶體時會使用到。使用了copy之後a和b各自會有自己的記憶體;

   (4)、為什麼在@implementation定義方法要使用到屬性的時候要加self.?

   因為使用self.會調用到setter方法。如果前面不加self.,相當于assign方式地調用屬性,retainCount不會加1的。

5、一個例子可以進一步了解三個特性:

   NSString *aHouse = [[NSString alloc] initWithString:@”一套房子”]; 

   上面一段代碼會執行以下兩個動作:

   (1)、在堆上配置設定一段記憶體用來存儲@”一套房子” :假設記憶體位址為0X0011,内容為@”一套房子”;

   (2)、在棧上配置設定一段記憶體用來存儲aHouse,假設位址為 0X00AA , 内容為 0X0011(即是@”一套房子”的位址);

   下面分别看三個特性的效果:  

   (1)、assign的情況: NSString  * bHouse  = aHouse;  

   此時bHouse和aHouse完全相同,位址都是 0X00AA,内容為 0X0011,即 bHouse 隻是 aHouse的别名,對任何一個操作就等于對另一個操作。是以 retainCount 不會增加(一把鑰匙,雙方公用);  

   (2)、retain的情況: NSString  * bHouse  = [aHouse retain];   

   此時 bHouse 的位址仍然為 0X00AA ,存儲的内容也仍然是 0X0011,但是aHouse的retainCount會加1。是以aHouse和bHouse都可以管理@”一套房子”所在的記憶體。(兩把鑰匙,各自一把);

   (3)、copy的情況: NSString  * bHouse  = [aHouse copy];

   此時會在堆上重新開辟一段記憶體存放@”一套房子”,假設是0X0022,内容為@”一套房子”,同時會在棧上為bHouse配置設定空間,假設位址為0X00CC,内容便是0X0022。aHouse的retainCount不會變,bHouse有它自己的retainCount了(兩套房子,各自一把鑰匙,各開各的,互不相幹)。

6、什麼時候使用retain什麼時候使用assign?以5的代碼為例來解釋:

   (1)、什麼時候用assign:

   破房子、簡單的房子就可以共享鑰匙,比如:基礎類型(簡單類型,原子類型)NSInteger、CGPoint、CGFloat、C資料類型(int,float,double,char等);

   (2)、什麼時候用copy:

   含有可深拷貝的mutable子類的類,如NSArray、NSSet、NSDictionary、NSData、NSCharacterSet、NSIndexSet、NSString等類。同時需要注意,到了衍生的Mutable類就不能用copy了,不然初始化會有問題。

   (3)、什麼時候用retain:

   其他NSObject及其子類對象 (大多數)。

7、關于atomic和nonatomic特性:

   (1)、atomic特性表示編譯器生成的setter和getter方法是原子操作,nonatomic特性則表示setter和getter方法不會實作原子操作;

   (2)、原子操作即是在多線程的情況下,某個資源會被通路它的線程獨占,在這個線程使用完這個資源前,其他線程不可以介入;

   (3)、是以在調用atomic特性的屬性的setter和getter方法時,其他方法無法通路這個屬性。屬性的nonatomic特性則表示無法獨占它的setter和getter方法;

   (4)、屬性預設是atomic特性;

   (5)、是以在多線程的情況下要使用atomic特性,而這種機制是需要消耗記憶體的,是以如果在不需要多線程的環境下,應把屬性聲明為nonatomic,以節約記憶體;

   (6)、是以會有atomic對應多線程,而nonatomic是禁止多線程這種說法(其實應該說使用nonatomic就要禁止多線程)。

8、如果定義了一個retain特性的數組屬性:

   @property (nonatomic, retain) NSMuatableArray *data;

   然後在程式中其他位置要使用到data,如果用以下的語句來初始化:

   data = [NSMutableArray array];

   array方法建立了一個自動釋放的數組,然後把它指派給了data,那麼data數組在目前事件結束後就會被銷毀,因為自動釋放池會在事件結束後進行清理。如果要讓這個數組在事件循環後還能夠存活下來,可以使用以下3種方法:

   (1)、data = [[NSMutableArray array] retain];  //直接retain增加retainCount

   (2)、data = [[NSMutableArray alloc] init];  //alloc出來的對象不會被自動釋放池銷毀

   (3)、self.data = [NSMutableArray array];  //加上self.會使用到setter 方法,那麼@property

                                      //裡的retain就會起作用了

   這3中方法都會導緻對象不會被自動釋放池自動銷毀,那麼就需要覆寫dealloc方法:

   -(void) dealloc {

     [data release];

     [super dealloc];

   }

9、關于MRC的一些總結:

   (1)、對一個對象發送retain消息可以保持這個對象不會被銷毀,但是在使用完這個對象之後要發送release消息來釋放;

   (2)、對一個對象發送release消息并不意味着這個對象就會被銷毀了,銷毀與否取決于對象的引用計數,release消息隻會讓對象的引用計數減1,當引用計數減至0時對象才會銷毀;

   (3)、要對使用了alloc、new、copy、mutableCopy或retain方法的任何對象,或是具有retain和copy特性的屬性進行釋放,需要覆寫dealloc方法;

   (4)、自動釋放池清空的時候,系統會發送release消息給池中的每個對象,然後對于那些引用計數減至0的對象,系統會發送dealloc消息銷毀這些對象;

   (5)、如果某個對象需要作為方法的傳回值使用,即是方法調用完之後方法中的某個對象仍必須繼續存活下去,那麼可以對這個對象使用autorelease方法。autorelease方法不會影響到引用計數,隻會标記這個對象延遲釋放;

   (6)、當應用程式終止時,所有對象全部會被釋放;

   (7)、在程式運作的過程中,一個事件處理完後,系統會清空自動釋放池,并等待下個事件發生。如果要讓某個成員對象或者執行個體變量在自動釋放池清空後還能夠存在,可以對它發送retain消息,隻要對象或變量的引用計數大于發送autorelease消息的數量,就能夠在自動釋放池的清理中生存下來。

10、關于強變量和弱變量:

    (1)、強變量使用關鍵字strong,弱變量使用關鍵字weak;

    (2)、通常所有對象的指針變量預設都是強變量,強變量預設會被初始化為零。但是屬性的預設特性不是strong,預設特性是unsafe_unretaind(相當于assign);

    (3)、為對象變量或者屬性指定weak特性的方法如下:

        __weak UIView *parentView;

        @property (weak, nonatomic) UIView *parentView;

    (4)、strong關鍵字與retain相似,用了它引用計數會自動加1,即是它所指向的内容會為它而保留,不會無故被銷毀。用以下代碼來說明:

        ...  //首先有以下定義

        @property (nonatomic, strong) NSString *string1;

        @property (nonatomic, strong) NSString *string2;

        ...

        @synthesize string1;

        @synthesize string2;  

        ...   //情況1:

        self.string1 = @"xxxx";   //retainCount = 1

        self.string2 = self.string1;  //retainCount = 2

        self.string1 = nil;         //retainCount = 1

        NSLog(@"String 2 = %@", self.string2);  

        //輸出的結果是:String 2 = xxxx

        ...   //情況2,string2被聲明為weak特性:

        @property (nonatomic, weak) NSString *string2;  

        ...

        self.string1 = @"String 1";  //retainCount = 1

        self.string2 = self.string1;    //retainCount = 1

        self.string1 = nil;          //retainCount = 0

        NSLog(@"String 2 = %@", self.string2);

        //輸出的結果是:String 2 = null

    (5)、是以強變量會将指向對象的retainCount加1,能保證持有;而弱變量則無法影響到指向對象的retainCount;弱變量有一個好處:如上例所示,如果弱變量指向的對象被釋放了,若變量會自動被設定為nil;

    (6)、當兩個對象互相持有對方的引用,并且雙方都是強變量的時候,就會産生循環引用,會導緻這兩個對象一直存在,無法被銷毀。這時候要把其中一個對象聲明為弱變量來解決這個問題;