【Objective-C 篇】 ☞ 5. MRC、ARC

MRC 手動管理內存

1.1 內存引用平衡原則程序員

1) 若是使用alloc,new開頭,或者是copy(複製一個對象)來建立一個對象,意味着你擁有這個對象的全部權。這個對象的引用計數器初始值爲1(也有可能>1)objective-c

2) 若是你擁有這個對象的全部權,在不使用此對象時,就有責任向對象發送release消息。(誰建立了對象,誰就有責任release這個對象) 安全

3) 若是並不擁有一個對象的全部權,而想要使用這個對象,爲了防止你在使用此對象期間,對象被別人釋放掉,須要向對象發送retain消息,以保持對象。此時能夠認爲,你也擁有了這個對象全部權。函數

4) 當你使用完retain過的對象後,有責任release一下這個對象。性能

(誰retain了一個對象,誰就有責任release這個對象)優化

       配對出現:(+一、-1 ==>平衡) atom

咱們建立的對象不用了,就release;咱們retain的對象不用了,就releasespa

    內存管理的原則就是有加就有減。也就是說, 一次allocnew對應一次release, 一次retain對應一次release。3d

1.2 自動釋放池(autoreleasepool)指針

經過自動釋放池來管理對象,只須要一個自動釋放池,能夠管理不少對象,當自動釋放池結束的時候,會自動向池中的每一個對象都發送release消息。

1) 若是一個對象建立後,不能立刻釋放它,但又不得不盡到釋放對象的責任,此時能夠將對象放入自動釋放池,延遲對象的釋放時機。好比絕大部分工廠方法都是如此。工廠方法中的對象是方法中建立的,按理來講應該由工廠方法內部釋放,但工廠方法的功能決定了這個對象不能立刻釋放,此時應該將對象放入自動釋放池。

2) 當自動釋放池結束時,會向池中的全部對象發送release消息,若是此時,池中的對象的引用計數器是1,那麼,對象會被釋放掉。

3) 如何開始和結束一個自動釋放池呢?

//自動釋放池,用於回收對象的存儲空間。

@autoreleasepool{ //開始建立一個自動釋放池

  ……  ……

} //結束自動釋放池銷燬了, 給自動釋放池中全部的對象發送一條release消息

      還有一種性能低下,被淘汰的自動釋放池建立方式(瞭解)

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];//開始一個自動釋放池

   ……  ……

[pool drain];//結束一個自動釋放池

4) 不管一個對象是否在自動釋放池,只要這個對象是由別人建立的,你要使用此對象,就得retain,用完此對象,就得release,即便對象在自動釋放池,也依然如此。

5) 實際開發中,合理使用自動釋放池來避免內存使用出現峯值。

若是App出現的內存使用的峯值,此時才考慮是不是因爲大量使用工廠方法形成的,是否須要使用自動釋放池解決問題。要合理使用自動釋放池,大量使用會消耗系統資源。

6) autorelease方法:在自動釋放池中使用,目的是將對象添加到自動釋放池中。自動釋放池銷燬時會對池中對象做release操做。(延遲release)

@autoreleasepool {

Person *p = [[[Person alloc] init] autorelease];

        

}

  • autorelease方法與release方法的區別:這兩個方法都能把對象的引用計數器減1。
    • release是一個精確的減1,對對象的操做只能在release以前進行,若是是在以後,就會出現野指針錯誤;
    • autorelease是一個不精確的引用計數器減1,當給對象發送autorelease消息時,對象就會被放到自動釋放池中,自動釋放池銷燬時會給池中的全部對象發送release消息,使得全部對象的計數器減1,因此本質上autorelease仍是會調用release。
  • 常見錯誤:

// 銷燬自動釋放池的時候 要對person再執行release操做的話 會報野指針錯誤

@autoreleasepool {

Person *person = [[[Person alloc] init] autorelease];

[person release];

// 對象執行兩次autorelease意味着自動釋放池銷燬的時候 對象會執行兩次release操做 會報野指針錯誤

@autoreleasepool {

Person *person = [[[[Person alloc] init] autorelease] autorelease];

1.3 setter方法的內存管理(應用場景:兩個類是聚合關係)

當一個方法傳入一個對象後,若是須要將這個對象用實例變量等手段保存起來持續使用時,須要作如下事:

1) 先將此對象的引用計數器加1(retain)

2) 再將原來實例變量指向的對象的引用計數器減1(release)

3) 最後將傳入的對象地址保存到實例變量中

4) retain的對象一般須要在dealloc方法中release.

 

只要一個對象想使用房間,就須要對這個房間的引用計數器+1

只要一個對象不想使用房間,就須要對這個房間的引用技術器-1

      

常常會被問到的問題:

1) 前面3個順序能否顛倒?

能不能先release原來的對象,再賦值,最後retain新對象? 通常能夠,但不建議

       

2) dealloc方法中,可不可使用self.屬性 = nil;的方式釋放屬性所指向的對象? ok

能夠的,self.屬性 至關於調用上面的setter方法(該方法中有向對象發release

    Demo:setter方法的內存管理

     

    

1.4 property修飾符

1) nonatomic 非原子性操做 (安全性低,但效率高。iOS實際開發中99%用這個,以確保性能)

atomic 原子性操做 (安全性高,但很耗費資源)

注意,默認是原子性的(atomic),因此在定義屬性時必定要寫上nonatomic

2) assign, retain, copy

    assign  是默認值,僅作賦值。不會解決內存問題(即不會retain,也不會release)。在MRC中可用於對象類型和非對象類型。

    retain 只能用於對象類型的屬性,會解決內存問題(生成的setter方法會自動加入retain,release等內存操做代碼)

    copy 一些特殊對象類型,若是不但願和別人共享一個對象,用copy會自動建立一個新的對象。

      有些屬性在傳入後,須要拷貝一份,以防止傳入的對象內容被修改後,影響到個人對象。

(並非全部的對象都能拷貝,只有遵照<NSCopying>協議,實現了協議中CopyWithZone方法的這種對象才擁有拷貝的功能)

3) readonly/readwrite

readwrite 是默認的,編譯器生成gettersetter方法

readonly 只讀,編譯器只生成getter方法

  屬性訪問器的類型:

  

注:聲明屬性默認狀況下,並無解決內存問題

當使用 @property(retain)引用數據類型,幫咱們解決了setter使用中的內存問題,但dealloc中的release操做,咱們本身來作。

1.5  MRC中的循環引用(兩個對象互相引用(即互相包含)時,要一強一弱!)

  若是A對象要擁有B對象, 而B對象又要擁有A對象, 此時會造成循環retain

  如何解決這個問題:讓其中一方不要作retain操做便可!

Demo:

   

ARC 自動管理內存

1.1 概念

Automatic Reference Counting  自動引用計數

基於MRC, 在MRC下,對象的引用計數是由程序員負責的。在ARC下,對象的引用計數工做由編譯器來完成。

1.2 ARC的工做原理

在ARC下,堆內存空間的管理依然使用引用計數器的方式。只是ARC下的引用計數器的加減工做再也不由程序員來作,而是由編譯器來作。

編譯器在編譯程序期間,會在程序恰當的位置自動加入對對象的retain,release和autorelease的操做。

注意:ARC是 編譯期語法或編譯器特性(便可以理解爲是Xcode的一個功能),而不是運行時特性。

1.3 怎麼用

  程序員不要對對象的引用計數器進行操做,編譯器會幫咱們作:

    1)  在ARC下,不要在程序中調用retain, release, autorelease方法。

    2)  在ARC下,不要重寫retain, release, autorelease方法。

    3)  在ARC下,不要在dealloc方法中調用父類的dealloc方法。實際上,ARC下,dealloc方法基本沒有用了。

      總之,一切與內存操做相關的東西都由ARC自動完成。

1.4 ARC的判斷原則:(即系統怎麼判斷對象是否要釋放)

  • 自動管理內存的判斷原則:只要還有一個強指針變量指向對象,對象就會保持在內存中。
  • 強指針(強引用):
    • 默認狀況下全部的指針變量都是強指針
    • __strong修飾的指針
    • 例如:

TRPerson *p1 = [[TRPerson alloc]init];

__strong TRPerson *p2 = [[TRPerson alloc]init];

 

  • 弱指針(弱引用)
    • __weak修飾的指針
    • 例如:

__weak TRPerson *p = [[TRPerson alloc]init];

 

  • 舉例:

//ARC的判斷準則:只要沒有強指針指向對象,對象就會釋放

TRPerson *p1 = [[TRPerson alloc]init];

__strong TRPerson *p2 = [[TRPerson alloc]init];

__weak TRPerson *p3 = p2;

p2 = nil;//p2改變了指向,此時就沒有強指針指向對象,對象就會釋放

}//出了大括號,局部變量p1就釋放,此時就沒有強指針指向對象,對象就會釋放

        單個對象的內存管理:若是一個對象再也不使用了,就把指向對象的強指針置爲nil,對象就會釋放。

  • 注意:
    • 當使用ARC的時候,暫時忘記「引用計數器」,由於判斷標準變了。
    • 在實際開發中,千萬不要使用一個弱指針來保存一個剛剛建立的對象。

{

//p是弱指針,對象會被當即釋放

__weak TRPerson *p = [[TRPerson alloc]init];//剛建立就被釋放!

}

 

1.5 ARC中多個對象的內存管理:

  • ARCMRC同樣,想擁有某個對象必須用強指針保存對象,可是不須要在dealloc方法中release

@class TRDog;

@interface TRPerson : NSObject

 

//MRC下寫法

@property (nonatomic, retain) TRDog *dog;

//ARC下寫法

@property (nonatomic, strong) TRDog *dog;

 

@end

MRC

A對象想使用B對象, 須要對B對象進行一次retain

A對象不用B對象了, 須要對B對象進行一次release

即,property的時候進行retain, dealloc的時候進行release

 

ARC

A對象想使用B對象, 那麼就須要用一個強指針指向B對象(即用strong,表示強引用)

// ARC中保存一個對象用strong, 至關於MRC中的retain

@property (nonatomic, strong) Dog *dog;

A對象不用B對象了, 什麼都不須要作, 編譯器會自動幫咱們作

 

1.6 ARC下循環引用問題

  循環引用:指兩個對象相互強引用了對方,即retain了對方,從而致使誰也釋放不了誰的內存泄露問題。

  • ARC和MRC同樣,若是A擁有B,B也擁有A,那麼必須一方使用弱指針
  • 兩個強指針互相引用,兩個空間就會永不釋放!因此必需要一強一弱。

@class TRDog;

@interface TRPerson : NSObject

//MRC寫法

//@property (nonatomic, retain) TRDog *dog;

//ARC寫法

@property (nonatomic, strong) TRDog *dog;

@end

 

@interface TRDog : NSObject

//錯誤寫法,循環引用會致使內存泄露

//@property(nonatomic, strong)TRPerson *owner;

 

//正確寫法,當若是保存的是對象類型建議使用weak

//@property(nonatomic, assign)TRPerson *owner;

@property (nonatomic, weak) TRPerson *owner;

@end

  • ARC下循環引用的案例:

    

1.7 若是在ARC下定義屬性的內存特質(attribute)

  • 在MRC下, 與內存相關的屬性特質有:assign, retain, copy
  • 在ARC下, 與內存相關的屬性特質有:

(1)strong  強引用 

  相似於retain,引用時候會引用計數+1。

strong 案例:

@property (nonatomic, strong) NSString *str1;

@property (nonatomic, strong) NSString *str2;

self.str1 = @"Hello World";

self.str2 = self.str1;

self.str1 = nil;

NSLog(@"str2 = %@", self.str2);

結果是:str2 = Hello World

(2)weak   弱引用

  • 相似於assign,不會改變引用計數,只作簡單的賦值。
  • weakassign的區別:
    • assign 僅作賦值(默認值),不會解決內存問題。在MRC中可用於對象類型和非對象類型。
    • weak只用於ARC下的對象類型(即帶*號的指針類型)。weak比assign更徹底,會自動把野指針置空。
    • assign通常在ARC下用於基本數據類型(即不帶*號的類型,包括id類型)
    • weak 的特色:
      • 聲明爲weak的指針,指針指向的地址一旦被釋放,這些指針都將被賦值爲nil。這樣的好處能有效的防止野指針。
      • 特定狀況下,若是內存的釋放會出現問題,常用weak來解決,好比,最多見的問題是"內存循環引用"

weak 案例:

@property (nonatomic, strong) NSString *str1;

@property (nonatomic, weak) NSString *str2;

self.str1 = @"Hello World";

self.str2 = self.str1;

self.str1 = nil;

NSLog(@"str2 = %@", self.str2);

結果是:str2 = null

(3)unsafe_unretained  (從名字上來看:不安全,不retain)

  • 默認的, 能夠用於非對象類型/對象類型。
  • 用於非對象類型時和assign同樣,只作簡單的賦值。
  • 用於對象類型時相似於weak,但沒有weak安全(由於當指向的對象被銷燬時,指針不會自動置空,會產生野指針錯誤)

unsafe_unretained 案例:

@property (nonatomic, strong) NSString *str1;

@property (nonatomic, unsafe_unretained) NSString *str2;

self.str1 = @"Hello World";

self.str2 = self.str1;

self.str1 = nil;

NSLog(@"str2 = %@", self.str2);

沒有輸出結果!程序崩掉,會報野指針錯誤(EXC_BAD_ACCESS 壞訪問)

(4)copy   拷貝 (和之前的copy同樣)

    使用copy: 對NSString

    效果其實和retain沒什麼兩樣,惟一的區別就是copy只用於NSString而不能用於NSMutableString, 若是當一個類繼承NSObject,那麼這個類裏面的屬性須要使用copy。

    retain是指針拷貝,copy是內容拷貝。

  • strong, weak, unsafe_unretained每每都是用來聲明屬性的。

    若是想聲明臨時變量就得用__strong, __weak, __unsafe_unretained, __autoreleasing,其用法與上面介紹的相似。

案例:

__strong NSString *str1 = @"Hello World";

__weak  NSString *str2 = str1;

__unsafe_unretained NSString *str3 = str2;

str1 = nil;

//如今str1str2的指針都爲nil,str3不爲nil,可是是野指針。

提示:沒有兩個下劃線的放屬性裏,有兩個下劃線的放變量前。

  • objective-c內存管理中有一條是:誰分配誰釋放。__autoreleasing則可使對像延遲釋放。

      autoreleasing的應用:在函數內部申請的空間,在函數外部也可使用

 

//MRC

-(NSString *)stringTest

{

    NSString *retStr = [NSString stringWithString:@"test"];

    return [[retStr retain] autorelease];

}

//ARC

-(NSString *)stringTest  

{

    __autoreleasing NSString *retStr = [NSString alloc] initWithString:@"test"];  

    return retStr;  

}

  • 總結:

    ARC下@property參數:

      strong:用於OC對象,至關於MRC中的retain (強引用)

      weak:用於OC對象,至關於MRC中的assign  (弱引用)

      assign:用於基本數據類型,跟MRC中的assign同樣。

 

1.8 ARC下的自動釋放池

在ARC下,對象的工廠方法依然會將對象放入自動釋放池。當池結束時,向池中的對象發送release消息。

池中的對象何時銷燬,沒法肯定,由於編譯器會作不少優化。

在iOS開發中,一個事件循環結束,自動釋放池會釋放一次,池中的對象會收到release消息。

相關文章
相關標籤/搜索