iOS記憶體管理(objective-c)

來源:互聯網
上載者:User

標籤:

 移動app開發中,由於行動裝置記憶體的限制,記憶體管理是一個非常重要的話題。objective-c的記憶體管理,不僅是面試當中老生常談的一個必問話題,也是日常項目開發中,特別需要重視的環節。對於筆者這種以java語言入門編程世界的開發人員來說,習慣了垃圾收集器的自動化管理,對於oc的引用計數器管理方式,還是需要花功夫來學習和運用。

1. ARC 和 非ARC

  oc的記憶體管理方式,分為ARC(automatic reference counting自動引用計數)和非ARC模式。Apple 在 Xcode 4.2 中發布了 Automatic Reference Counting (ARC),該功能為開發人員消除了手動執行引用計數的負擔。

  目前xcode建立項目,都會推薦預設的ARC方式(ARC的確有很大的優勢)。當然,如果必須要使用非ARC,可以在build setting中修改automatic reference counting選項為NO。如果在ARC項目中引入非ARC的代碼或者靜態庫,需要在build phases設定相應資源為-fno-objc-ar;相反,非arc項目設定arc使用-fobjc-arc。

  ARC與非ARC,從字面上來看,在於是否auto,即是由編譯器來自動實現reference counting,還是由開發人員手動完成引用計數的加減。作為現在經常使用arc模式來開發的我們來說,ARC大大減少了我們對記憶體管理的工作,甚至,很多入門開發人員完全像開發java應用一樣,沒有管object的釋放。然而對oc來說,從mac os x10.8開始,garbage collector也已廢棄,iOS上壓根就沒有出現過這個概念。iOS上oc的記憶體管理,本質上是引用計數的管理,理解引用計數,是iOS記憶體管理的核心。

2. 引用計數

  如果一個對象沒有被其他對象所引用,則表明該對象已不需要,即可以釋放。這就好比人們在一張飯桌上吃飯,如果飯桌上還有人,就表明該飯桌還不可以收拾;只有當所有人都吃好飯離開,使用人數小於1,才表明這張飯桌已使用完,可以清理收拾。 所以對象釋放的時機,即是該記憶體的引用計數小於1. oc中的記憶體管理方式,就是基於對引用計數的管理。

  ARC模式下,編譯器完成了引用計數的管理,開發人員不需要手動添加引用計數管理的代碼(實際上也不允許),所以以下主要通過非ARC模式的代碼方式,解釋引用計數管理方式。

  1》對象的初始化和釋放

  oc中的object需要繼承自統一的父類NSObject。對於一個NSObject的生命週期來說,關注以下幾個方法:

  •  alloc
  •    new
  •    copy
  •    mutableCopy
  •    dealloc

  當調用上述前四種方法時,都會產生新的對象,object的引用計數都會+1,歸調用者所有(即需要調用者釋放)。系統對象以及庫中所有的對象都會遵循這個規則,即所有以上述四種方法名開頭的方法,都會使引用計數+1;反之,所有不以該命名開頭的返回對象方法,都不會使引用計數+1.對於ARC來說,這種規則被確立為硬性規定。當我們自己自訂對象時,也應遵循這種規範,雖然通過命名規則來體現記憶體管理方式有些讓人奇怪。

  dealloc是對象內部的實現方法,當object的引用計數為0,即沒有被其他對象引用時,該方法會調用,釋放object。需要注意的是,在每個對象的生命週期內,dealloc只會調用一次。然而當引用計數為0時,並不能保證系統何時執行該方法。運行期系統會在何時時機調用dealloc,所以決不應該手動調用dealloc,一旦調用後對象就不可用。

  在非ARC對象的dealloc方法中,主要就是釋放對象所擁有的引用,除此之外,通常還需要把原來配置的觀測行為清理掉,如remove掉kvo和notification的觀察者。對於ARC模式來說,並不允許我們直接複寫dealloc這些記憶體管理相關的操作。編譯器會利用Objective-c++的特性,為C++對象的.cxx_destruct方法產生代碼,釋放對象。但是,對於那些通過malloc()產生的記憶體或者比如CoreFoundation中的對象,並非屬於oc對象,開發人員需要按照需要自己釋放。

  在非ARC模式中,我們可以手動控制引用計數的增減。通過以下兩個方法:

  •    retain
  •    release

  當調用[object retain],引用計數+1;調用[object release]時,引用計數-1.當object不在使用,需要釋放時,調用release使引用計數清0,而不要直接調用dealloc。

  NSObject協議中,存在retainCount這個方法,用於查詢對象的當前保留計數:

- (NSUInteger)retainCount

 然而它並沒有卵用。然而它並沒有卵用(重要的事要說兩次)。在ARC模式中,記憶體管理相關的方法都無法通過編譯,而在非ARC中,retainCount很多時候並不能反映出對象正常的保留計數。甚至當對象已被釋放的情況下,再次調用到retainCount會直接導致crash。

    NSString *str = @"12456";    NSLog(@"str retainCount: %lu",(unsigned long)[str retainCount]);    NSNumber *num = @1;    NSLog(@"num retainCount: %lu",(unsigned long)[num retainCount]);    NSNumber *numF = @3.1415f;    NSLog(@"numF retainCount: %lu",(unsigned long)[numF retainCount]);

 

  以上代碼的結果可能會讓你大跌眼鏡:

2015-07-24 14:37:19.238 TestMemory[10987:3418823] str retainCount: 42949672952015-07-24 14:37:19.241 TestMemory[10987:3418823] num retainCount: 182015-07-24 14:37:19.241 TestMemory[10987:3418823] numF retainCount: 1

  str和num對象的retainCount出現了讓人不解的數字。實際上編譯器對這些對象都做了最佳化,這種最佳化只在某些場合才會發生,所以numF對象的retainCount是正常的。所以再次強調,不要試圖利用retainCount來做任何操作。

  iOS的記憶體管理,說白了也就是對於引用計數,調用retain和release來告知系統對記憶體的釋放。我們舉個栗子,以下代碼在非ARC模式中運行:

/*! *  飯桌 */ @interface Table : NSObject@end@implementation Table- (void)dealloc{    NSLog(@"%s",__func__);    [super dealloc];}@end /*! *  客人 */@interface Guest : NSObject@property (nonatomic, copy) NSString *name;@property (nonatomic, retain) Table *table;- (instancetype)initWithName:(NSString *)name;@end@implementation Guest- (instancetype)initWithName:(NSString *)name{    self = [self init];    if (self) {        self.name = name;    }    return self;}- (void)dealloc{    NSLog(@"%@ dealloc",self.name);        [self.name release];    [self.table release];    [super dealloc];}- (void)setTable:(Table *)table{    [table retain];    [_table release];    _table = table;}@end

 

//兩位客人到一張飯桌上吃飯的過程再現     Table *table = [[Table alloc] init];    NSLog(@"table retain count: %lu",(unsigned long)[table retainCount]);        Guest *guest_1 = [[Guest alloc] initWithName:@"guest_1"];    NSLog(@"%@ retain count: %lu",guest_1.name,(unsigned long)[guest_1 retainCount]);    guest_1.table = table;    NSLog(@"table retain count: %lu",(unsigned long)[table retainCount]);        Guest *guest_2 = [[Guest alloc] initWithName:@"guest_2"];    NSLog(@"%@ retain count: %lu",guest_2.name,(unsigned long)[guest_2 retainCount]);    guest_2.table = table;    NSLog(@"table retain count: %lu",(unsigned long)[table retainCount]);        [guest_1 release];    NSLog(@"%@ retain count: %lu",guest_1.name,(unsigned long)[guest_1 retainCount]);    NSLog(@"table retain count: %lu",(unsigned long)[table retainCount]);        [guest_2 release];    NSLog(@"%@ retain count: %lu",guest_2.name,(unsigned long)[guest_2 retainCount]);    NSLog(@"table retain count: %lu",(unsigned long)[table retainCount]);        [table release];    NSLog(@"table retain count: %lu",(unsigned long)[table retainCount]);

    以上實現了一個簡單的客人到飯桌吃飯的情境:首先有準備出一張飯桌,來了一位客人1,做到飯桌吃飯,又來了一位客人2,到飯桌吃飯,客人1吃好飯走人,客人2走人,飯桌使用完畢,收拾飯桌。

      首先看兩個類,Table很簡單,類中沒有保留的其他對象,dealloc方法調用[super dealloc]保證父類對象的釋放。Guest類中,添加了兩個屬性(@property),屬性的修飾符是copy和retain。iOS會對property自動建立get和set方法,而copy/retain/assign等這一類修飾符,表明了set方法中對屬性值不同記憶體管理的方式,這一點後文詳述。這裡只要知道,copy和retain都會導致set方法中將屬性值保留,即retainCount+1。Guest類中加入了自訂的init方法,注意init開頭的方法,必須按照oc的代碼規範要求才能實現init時調用的目的,如例子中initWithName:這樣的camel-case。dealloc方法中實現了對保留對象的釋放,最後調用[super dealloc]。setTable:方法複寫了property的set方法,實際上只是將retain修飾符屬性值的預設set方法用代碼再現了一下。可以看到,對於retain的屬性值,會先保留新值,後釋放舊值,然後將新值賦給屬性,實現對象的保留。而set方法的調用者,需要管理所賦值的釋放。

     以上代碼的log如下:

2015-07-24 10:14:07.623 TestMemory[10956:3397055] table retain count: 12015-07-24 10:14:07.628 TestMemory[10956:3397055] guest_1 retain count: 12015-07-24 10:14:07.629 TestMemory[10956:3397055] table retain count: 2              -> table被guest對象保留,引用計數+12015-07-24 10:14:07.630 TestMemory[10956:3397055] guest_2 retain count: 12015-07-24 10:14:07.631 TestMemory[10956:3397055] table retain count: 3              -> table被guest對象保留,引用計數+12015-07-24 10:14:07.632 TestMemory[10956:3397055] guest_1 dealloc                    -> guest_1 引用計數為為0,觸發dealloc2015-07-24 10:14:07.632 TestMemory[10956:3397055] guest_1 retain count: 12015-07-24 10:14:07.633 TestMemory[10956:3397055] table retain count: 22015-07-24 10:14:07.634 TestMemory[10956:3397055] guest_2 dealloc                    -> guest_2 引用計數為為0,觸發dealloc2015-07-24 10:14:07.634 TestMemory[10956:3397055] guest_2 retain count: 12015-07-24 10:14:07.635 TestMemory[10956:3397055] table retain count: 12015-07-24 10:14:07.636 TestMemory[10956:3397055] -[Table dealloc]                   -> table 引用計數為0,觸發dealloc2015-07-24 10:14:07.636 TestMemory[10956:3397055] table retain count: 1

  可以看到,對象通過alloc方法create後,retainCount都會+1。table對象在alloc,又分別被guest_1和guest_2保留,導致retainCount為3。guest調用release方法後,引用計數為0,都會觸發guest對象的dealloc方法,導致table對象引用計數-1.最後table調用release,引用計數為0,觸發table對象dealloc。

     然而,正如上所說,retainCount在這裡並沒有很好地起到說明引用計數的作用。雖然引用計數都以為0,最後的retainCount都沒能顯示為0.這可能是運行期內部對retainCount方法做出了處理,或者是對象記憶體並沒有被立即釋放,否則對象dealloc後再次調用已釋放對象的方法都會導致EXC_BAD_ACCESS的crash發生。

  2》屬性的所有權語義(ownership semantic)

 

    待續...  

iOS記憶體管理(objective-c)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.