【iOS】自動引用計數(一)

  • 自動引用計數
  • 自動引用計數原理
  • 內存管理
  • 自己生成的對象自己持有
  • 非自己生成的對象自己也能持有
  • 不需要自己持有的對象時釋放
  • 無法釋放非自己持有的對象
  • alloc/retain/release/dealloc實現
  • autorelease
  • 認識autorelease
  • 使用autorelease
  • 實現autorelease
  • ARC規則
  • 所有權修飾符
  • __strong修飾符
  • 管理成員變量的對象所有者
  • 作用域中管理
  • 賦值上管理
  • 管理方法參數的對象所有者
  • __weak修飾符
  • __unsafe_unretained修飾符
  • __autoreleasing修飾符
  • __autoreleasing使用機制
  • __autoreleasing隱式使用機制__
  • 規則
  • 屬性
  • 數組
  • 靜態數組
  • 動態數組

自動引用計數

自動引用計數原理

Objective-C中的內存管理就是引用計數,而自動引用計數則是指內存管理中對引用採取自動計數的技術。自動引用計數技術用於管理對象的引用計數,也就是對象被引用的次數。

當一個對象的引用計數大於0時,表示該對象被持有,不可被釋放;當某個指針不再指向該對象時,引用計數減1;當對象的引用計數變為0時,系統將銷燬對象,回收內存。

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_引用計數

正如蘋果的官方説明,在新一代Apple LLVM編譯器中,編譯器將自動進行內存管理

內存管理

Cocoa框架中Foundation框架類庫的NSObject類擔負內存管理的職責,這裏先使用一個圖直觀的感受如何使用引用計數管理對象的內存:

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_修飾符_02

簡而言之,引用計數僅僅只是“生成”、“持有”、“釋放”、“廢棄”這四個詞的操作:

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_強引用_03

自己生成的對象自己持有

編程人員自己生成對象的方法有:

  • alloc類方法:指向生成並持有對象的指針被賦給變量。
  • new類方法:與alloc類方法完全一致。
  • copy方法:基於NSCopying方法約定,由實現 copyWithZone: 方法生成並持有不可變對象的副本。
  • mutableCopying方法:基於NSMutableCopying方法約定,由實現 mutableCopyWithZone: 方法生成並持有可變對象的副本。

非自己生成的對象自己也能持有

上述方法外的方法取得的對象,編程人員自己不是該對象的持有者,變量可以使用retain方法來持有對象。

id obj = [NSMutableArray array];
[obj retain];

不過現在的 Objective-C 已經引入了自動引用計數(ARC)。在啓動ARC的情況下,我們已不再需要手動管理內存,不在使用 retain 和 release 方法了,因此我們輸入時是找不到這個方法的。

不需要自己持有的對象時釋放

自己持有的對象,一旦不再需要,持有者有義務使用 release 方法釋放該對象。

id obj = [NSMutableArray array];
[obj release];

對象一經釋放絕對不可訪問。因為ARC的啓動,release 方法我們同樣找不到。

但是,我們想使得獲取的對象存在,但自己不持有,需要使用autorelease方法。autorelease方法使得對象在超出指定的生存範圍時能夠自動並正確地釋放。

id obj = [[NSObject alloc] init];
[obj autorelease];

這裏我們通過一個圖直觀地對比一下 release 和 autorelease 方法的區別:

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_引用計數_04

無法釋放非自己持有的對象

對於持有者是自己的對象,在不需要時需要將其釋放。而除此之外的對象絕不能釋放,倘若在程序中釋放了非自己所持有的對象就會造成崩潰

例如:釋放之後再次釋放已非自己持有的對象

id obj = [[NSObject alloc] init];
[obj autorelease];
[obj autorelease];

alloc/retain/release/dealloc實現

介於包含NSObject類的Foudation框架並沒有公開,部分源代碼沒有公開。為此,我們首先使用開源軟件GNUstep來説明。

  1. alloc
    我們直接來看一下去掉NSZone後簡化了的源代碼:

    alloc類方法用struct obj_layout中的retained整數來保存引用計數,並將其寫入對象內存頭部,該對象內存塊全部置0後返回。

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_強引用_05

  1. retain
    這裏先認識一下retainCount,通過retainCount實例方法可獲得對象的引用計數。

執行alloc後的對象的retainCount是“1”。因為分配是全部置0,所以retained為0。由NSExtraRefCount(self) + 1得出retainCount為1。

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_修飾符_06


由對象尋址找到對象內存頭部,從而訪問其中的retained變量。

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_修飾符_07


我們再通過源代碼就看出retain方法會使retained變量加1

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_引用計數_08

這裏雖然寫入了retained變量超出最大值時發生異常的代碼,但實際上只運行了使retained加1的retained++的代碼。

  1. release
  • 當retained變量大於0時,release實例方法使retained變量減1
  • 當retained變量等於0時調用dealloc實例方法,廢棄對象

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_修飾符_09


值得注意的是:上述代碼僅廢棄由alloc分配的內存塊

總結一下:

  • 在Objective-C的對象中存有引用計數這一整數值。
  • 調用alloc或retain方法後,引用計數值加1。
  • 調用release後,引用計數值減1。
  • 引用計數值為0時,調用dealloc方法廢棄對象。

蘋果是採用散列表(引用計數表)來管理引用計數的。

autorelease

認識autorelease

首先複習一下C語言的自動變量。當程序執行時,若某自動變量超出其作用域,該自動變量將被自動廢棄。

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_修飾符_10

autorelease會像C語言的自動變量那樣來對待對象實例。當超出變量作用域時,對象實例的release實例方法被調用。初次之外,與C語言的自動變量不同的是,編程人員可以設定變量的作用域。

使用autorelease

autorelease具體使用方法:

  • 生成並持有NSAutoreleasePool對象。
  • 調用已分配對象的autorelease實例對象。
  • 廢棄NSAutoreleasePool對象。

這裏首先認識一下NSAutoreleasePool對象的生存週期。

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_引用計數_11

NSAutoreleasePool對象的生存週期相當於C語言變量的作用域。對於所有調用過autorelease實例方法的對象,在廢棄NSAutoreleasePool對象時,都將調用release實例方法。

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_修飾符_12

在Cocoa框架中,Cocoa程序都有一個主事件循環(RunLoop),程序會一直在循環處理事件,而不是一運行就退出。在每次循環中,系統就會自動管理(創建、使用、銷燬)一個自動釋放池,以釋放autorelease的對象。

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_引用計數_13

但在大量產生autorelease的對象時,只要不廢棄NSAutoreleasePool對象,那麼生成的對象就不能被釋放,因此有時會產生內存不足的現象。

例如讀入大量圖像的同時改變其尺寸,這種情況下會大量產生autorelease的對象,外面只有一個大的autoreleasepool,那麼在循環中產生的所有對象要等到循環結束後才釋放,這樣每張圖片的內存都暫時保留在內存裏,很容易爆內存。

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_修飾符_14

在此情況下,有必要在適當的地方生成、持有或廢棄NSAutoreleasePool對象。在這個例子中,也就是在循環中手動創建小的@autoreleasepool塊。

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_強引用_15

這樣。每次循環結束時,池子就被清空,該釋放的對象馬上釋放,內存使用量會保持穩定。

另外,Cocoa框架中也有很多類方法用於返回autorelease的對象。

id array = [NSMutableArray arrayWithCapacity:1];

這個代碼等同於源代碼:

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_強引用_16

實現autorelease

autorelease實例方法的本質就是調用NSAutoreleasePool對象的addObject類方法,addObject類方法調用正在使用的NSAutoreleasePool對象的addObject實例方法。

如果嵌套生成或持有NSAutoreleasePool對象,調用NSObject類的autorelease實例方法時,該對象將被追加到正在使用的NSAutoreleasePool對象中的數組裏。

追加一個問題:如果autorelease了NSAutoreleasePool對象會如何?

答案是發生異常。這是因為無論調用哪一個對象的autorelease實例方法,實際上調用的都是NSObject類的autorelease實例方法,但是對於NSAutoreleasePool類,autorelease實例方法已被該類重載,也就是説,NSAutoreleasePool本身就是管理autorelease對象的池子,而現在它要把自己放進自己管理的池子裏,這樣就會造成嚴重的內存錯誤或死循環,因此會報錯。

ARC規則

所有權修飾符

ARC有效時,id類型和對象類型同C語言其他類型不同,其類型上必須附加所有權修飾符。

所有權修飾符:用於修飾指針類型的關鍵字,用來告訴編譯器對象的內存所有權、引用關係和生命週期管理方式。

所有權修飾符共4種:

  • __strong修飾符
  • __weak修飾符
  • __unsafe_unretained修飾符
  • __autoreleasing修飾符

__strong修飾符

是id類型和對象類型默認的所有權修飾符。

id obj = [[NSObject alloc] init];
id __strong obj1 = [[NSObject alloc] init];

上述兩個代碼是相同的。

管理成員變量的對象所有者
作用域中管理

__strong修飾符表示對對象的強引用。持有強引用的變量在超出其作用域時被廢棄,隨着強引用的實效,引用的對象會隨之釋放。

  • 自己生成並持有的對象
{
//因為變量是強引用,所以自己持有對象
id __strong obj = [[NSObject alloc] init];
}

因為變量obj超出其作用域,強引用失效,所以自動釋放自己持有的對象,對象的所有者不存在,因此廢棄對象。

  • 非自己生成並持有的對象
{
//強引用,自己持有
id __strong obj = [NSMutableArray array];
}
//超出作用域,強引用實效,自動釋放自己持有的對象

與自己生成並持有的對象的生命週期是一樣明確的。

賦值上管理

附有__strong修飾符的變量之間還可以相互賦值,下面具體展示:

obj1持有對象A的強引用。

id __strong obj1 = [[NSObject alloc] init];//對象A

obj2持有對象B的強引用。

id __strong obj2 = [[NSObject alloc] init];//對象B

obj3不持有任何對象。

id __strong obj3 = nil;
obj1 = obj2;
obj3 = obj1;
  • obj1持有obj2賦值的對象的B的強引用,因為obj1被賦值,所以原先持有的對對象A得強引用實效。對象A的所有者不存在,因此廢棄對象A。此時,持有對象B的強引用的變量為obj1和obj2。
  • obj3持有obj1賦值的對象的B的強引用。此時,持有對象B的強引用的變量為obj1、obj2和obj3。
obj2 = nil;
obj1 = nil;
obj3 = nil;
  • 因為nil被賦予了obj2,所以對對象B的強引用實效。此時,持有對象B強引用的變量為obj1和obj3。
  • 因為nil被賦予了obj1,所以對對象B的強引用實效。此時,持有對象B強引用的變量為obj3。
  • 因為nil被賦予了obj3,所以對對象B的強引用實效。此時,對象B的所有者不存在,因此廢棄對象B。

這裏我們使用dealloc函數來查看一下對象被持有和釋放的時機:

-(void)dealloc {
NSLog(@"%@被釋放", self.name);
}

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_引用計數_17

管理方法參數的對象所有者
-(void)setObject:(id __strong)obj;
{
id __strong test = [[Classes alloc] init];
[test setObject:[[NSObject alloc] init]];
}

test持有Classes對象的強引用,同時Classes對象的obj成員持有NSObject對象的強引用。

在上述代碼中,因為test變量超出其作用域,強引用實效,所以自動釋放Classes對象。Classes對象的所有者不存在,因此廢棄該對象。與此同時,Classes對象的obj成員也被廢棄,NSObject對象的強引用實效,自動釋放NSObject對象。同樣,NSObject對象的所有者不存在,因此也廢棄了該對象。

另外,_ _ strong 、_ _ weak和_ _autoreleasing一起時可以保證將附有這些修飾符的自動變量初始化為nil。

id __strong obj1;
id __weak obj2;
id __autoreleasing obj3;

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_引用計數_18

這樣,就可以通過__strong修飾符,不必再次輸入retain或者release,且滿足了“引用計數式內存管理的思考方式”。

id類型和對象類型的所有權修飾符默認為__strong修飾符。

__weak修飾符

__strong修飾符不能解決引用計數式內存管理中“循環引用”的問題。

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_修飾符_19

{
//test1持有Classes對象A的強引用
//test2持有Classes對象B的強引用
id test1 = [[Classes alloc] init];//A
id test2 = [[Classes alloc] init];//B
[test1 setObject:test2];//A.obj_ = test2
//Classes對象A的obj_成員變量持有Classes對象B的強引用,此時持有Classes對象B的強引用的變量為對象A的obj_和test2
[test2 setObject:test1];//B.obj_ = test1
//Classes對象B的obj_成員變量持有Classes對象A的強引用,此時持有Classes對象B的強引用的變量為對象B的obj_和test1
}

因為test1變量超出其作用域,強引用實效,所以自動釋放Classes對象A。

因為test2變量超出其作用域,強引用實效,所以自動釋放Classes對象B。

此時,持有Classes對象A的強引用的變量為Classes對象B的obj_。

此時,持有Classes對象B的強引用的變量為Classes對象A的obj_。

也就是,A和B在超出作用域後依然不能自動釋放,因為還互相強引用着,這樣就發生了內存泄漏!

內存泄漏:應當廢棄的對象在超出其生存週期後繼續存在。

對自身強引用時也會發生循環引用。

id test = [[Classes alloc] init];
[test setObject:test];

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_強引用_20

那麼怎樣才能避免循環引用呢?這時候就需要__weak修飾符了。

弱引用不能持有對象實例。

id __weak obj = [[NSObject alloc] init];

上述代碼會引起警告:

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_強引用_21

這是因為代碼將自己生成並持有的對象賦值給附有__weak修飾符的變量obj,也就是變量obj持有對持有對象的弱引用。因此,為了不以自己持有的狀態來保存自己生成並持有的對象,生成的對象會立即釋放,編譯器就會給出警告。

下面代碼(將對象先賦值給附有_ _strong修飾符的變量後再賦值給附有 _ _weak修飾符的變量)可以解決這個問題:

id __strong obj1 = [[NSObject alloc] init];
//因為obj1變量為強引用,所以自己持有對象
id __weak obj2 = obj1;
//obj2變量持有生成對象的弱引用

超出obj1變量作用域,強引用實效,自動釋放自己持有的對象。對象所有者不存在,廢棄該對象。因為弱引用的變量不持有對象,所以超出變量作用域時,對象即被釋放

再看上面“循環引用”的問題,這時我們將成員變量弱引用,就可以避免該問題了。

@interface Classes : NSObject {
id __weak obj_;
}

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_強引用_22

__weak修飾符的另一個優點:在持有某對象的弱引用時,若該對象被廢棄,弱引用將自動失效且處於nil被賦值的狀態(空弱引用)。

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_修飾符_23

id __weak obj1 = nil;
{
id __strong obj2 = [[NSObject alloc] init];
obj1 = obj2;
NSLog(@"%@", obj1);
}
NSLog(@"%@", obj1);

將obj2指向的對象賦給obj1,obj1是弱引用,不增加引用計數,只弱引用,因此作用域中obj1變量持有弱引用的對象。

obj2變量超出作用域後,強引用實效,自動釋放自己持有的對象,廢棄對象。同時,obj1持有該對象的弱引用也實效,nil賦值給obj1,因此輸出nil。

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_引用計數_24

__unsafe_unretained修飾符

不安全的所有權修飾符。附有__unsafe_unretained修飾符的變量不屬於編譯器的內存管理對象。

與附有_ _weak修飾符的變量一樣,附有 __unsafe_unretained修飾符的變量因為自己生成並持有的對象不能繼續為自己所有,所以生成的對象會被立即釋放。

__unsafe_unretained也不能持有對象實例

id __unsafe_unretained obj = [[NSObject alloc] init];

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_強引用_25

那麼,我們對比一下區別:

id __unsafe_unretained obj1 = nil;
{
id __strong obj2 = [[NSObject alloc] init];
obj1 = obj2;
NSLog(@"%@", obj1);
}
NSLog(@"%@", obj1);

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_強引用_26

我們可以看到輸出結果已經不一樣了,第一次可以輸出,而第二次出現了典型的“訪問已釋放對象”“的報錯。

對比_ _weak,附有__unsafe_unretained的對象是一個不安全的弱引用。它不會增加引用計數,也不會在對象銷燬時自動置為nil

因此,在作用域中和__weak無異,正常輸出持有的對象,而超出作用域後,對象銷燬且不會自動置為nil,因此程序崩潰,且此時的obj1就是懸垂指針。

懸垂指針:指向一塊已經被釋放或無效的內存的指針。

換句話説,這個指針曾經指向一個合法的對象,但那個對象後來被銷燬了,指針變量本身還保留着那塊舊地址。程序若再訪問,就會訪問到不可用的內存區域,導致程序崩潰。

__autoreleasing修飾符

__autoreleasing使用機制

實際上,不能使用autorelease方法,也不能使用NSAutoreleasePool類。雖然autorelease無法直接使用,但實際上,ARC有效時autorelease功能使起作用的。

  • ARC無效時

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_強引用_27

  • ARC有效時
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}

ARC有效和無效時,有一部分是等價的:

  1. 指定@autoreleasepool塊來代替無效時NSAutoreleasePool類對象的生成、持有及廢棄。
  2. 對象賦值給附有__autoreleasing修飾符的變量等價於無效時調用對象的autorelease方法。

如果方法名以alloc、new、copy、mutableCopy開頭,那麼返回的對象不會自動加入autoreleasepool,反之,以array、dictionary、stringWithFormat:等命名的返回的對象會被子哦那個加入。因此同_ _strong修飾符一樣,不用顯式的使用__autoreleasing修飾符也可以。

@autoreleasepool {
id __strong obj = [NSMutableArray array];
}

因為obj強引用,自己持有對象。並且由編譯器判斷方法名後自動註冊到autoreleasepool。然而超出作用域時,強引用實效,自動釋放自己持有的對象,同時,隨着@autoreleasepool塊的結束,註冊到autoreleasepool中的所有對象(包含obj)也自動釋放。

這樣,不使用__autoreleasing修飾符也能使對象註冊到autoreleasepool。

那麼,如果不在@autoreleasepool塊中,不顯式使用__autoreleasing也會自動註冊嗎?

答案是會的。前面我們説到每個線程的 RunLoop在每一輪事件循環中,系統都會自動創建並銷燬一個 autoreleasepool。因此即使沒有顯式寫@autoreleasepool代碼塊,系統也會在每次事件循環自動幫助我們創建一個隱式的@autoreleasepool代碼塊。因此,@autoreleasepool塊中,不顯式使用__autoreleasing也會自動註冊。不過,區別在於顯式autoreleasepool中生成的對象會在離開塊時立即釋放,而不在顯式autoreleasepool中的對象會在當前事件循環結束後才釋放

訪問帶有__weak修飾符的對象,系統實際上會將被訪問的對象註冊到autoreleasepool中。

id __strong obj1 = [[NSObject alloc] init];
id __weak obj2 = obj1;

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_修飾符_28

那麼這是為什麼呢?

這是因為__weak修飾符只持有對象的弱引用,而在訪問引用對象的過程中,該對象有可能被廢棄。此時,如果把要訪問的對象註冊到autoreleasepool中,那麼在@autoreleasepool塊結束之前都能確保該對象存在。

autoreleasing隱式使用機制

id的指針或對象的指針在沒有顯式指定時會被附加上__autoreleasing修飾符。因此一下代碼等價:

- (BOOL) performOperationWithError:(NSError **)error;
- (BOOL) performOperationWithError:(NSError *_autoreleasing *)error;

作為alloc、new、copy、mutableCopy方法返回值取得的對象是自己生成並持有的,其他情況下是取得的是非自己生成並持有的對象,會被註冊到autoreleasepool。因此,使用__autoreleasing修飾符的變量作為對象取得參數,與除alloc、new、copy、mutableCopy外其他方法的返回值取得對象完全一樣,都會註冊到autoreleasepool,並取得非自己生成並持有的對象。

總結一下:

  • alloc、new、copy、mutableCopy:自己持有,不註冊到autoreleasepool
  • 其他工廠方法:非自己持有,註冊到autoreleasepool
  • autoreleasing 參數:非自己持有,註冊到autoreleasepool

規則

具體的ARC規則有:

  • 不能使用retain、release、retainCount、autorelease

內存管理是編譯器的工作,因此不必使用內存管理方法。如若使用,將產生報錯:

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_強引用_29

總之,只能在ARC無效且手動進行內存管理時使用retain、release、retainCount、autorelease方法

  • 不能使用NSAllocateObject、NSDeallocateObject

一般通過NSObject類的alloc類方法來生成並持有Objective-C對象。若使用,同樣會產生報錯。

  • 遵守內存管理的方法命名規則

在ARC無效時,以alloc、new、copy、mutableCopy開始的方法在返回對象時,必須返回給調用方所應當持有的對象。ARC有效時是在此基礎上追加一條init。

以init開始的方法規則更加嚴格,該方法必須是實例方法,並且必須返回對象。返回的對象應為id類型或該方法聲明類的對象類型,或是該類的超類型(父類)或子類型(子類)。該返回對象並不註冊到autoreleasepool上,基本上只是對alloc方法返回值的對象進行初始化處理並返回對象

正確的命名方法:

-(id)initWithObject;

錯誤的命名方法:

-(void)initThisObject;
  • 不要顯示調用dealloc

dealloc方法適用的情況有:

  1. 對象的所有者不持有該對象,要被廢棄時。

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_引用計數_30

  1. 刪除已註冊的代理或觀察者對象。
-(void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

ARC會自動對此進行處理,因此在ARC有效時會遵循無法顯式調用dealloc這一規則。若使用,同樣會引起編譯錯誤。

  • 使用@autoreleasepool塊代替NSAutoreleasepool

使用NSAutoreleasepool類會引起編譯器報錯。

  • 不能使用區域(NSZone)

NSZone是早期Objective-C中的一種內存分配優化機制,是為了讓開發者能夠把不用類型的對象分配在不同的內存區域中,以便優化內存管理或調試。

現代Objective-C宏定義#define __OBJC2__表示當前編譯環境使用現代運行時:

  1. 所有對象分配都統一由malloc管理。
  2. 內存區域概念已完全廢棄。
  3. allocWithZone:仍然存在,但zone參數被忽略。
  4. alloc方法現在直接調用allocWithZone:nil。
  • 對象型變量不能作為C語言結構體的成員
  • 顯式轉換"id"和"void"

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_修飾符_31

id型或對象型變量賦值給void*或者逆向賦值時都需要進行特定的轉換。解決這個問題可以使用“__bridge轉換“。

__bridge轉換有三種橋接修飾符:

  • __bridge轉換
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)(obj);

只做類型轉換,不改變所有權,不改變引用計數,兩個指針指向同一內存。安全性與__unsafe_unretained修飾符相近,甚至更低。如果管理時不注意賦值對象的所有者,就會因懸垂指針而導致程序崩潰。

  • __bridge_retained轉換
id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void*)obj;

與retain類似,從ARC管理被retain到Core Foundation,並增加引用計數。

  • __bridge_transfer轉換
void *p = (__bridge_retained void*) [[NSObject alloc] init];
(void)(__bridge_transfer id)p;

與release類似,把Core Foundation轉換到ARC,把所有權交給ARC管理。

Objective-C對象 & Core Foundation對象:
Core Foundation對象主要用於C語言編寫的Core Foundation框架中,並使用引用計數的對象。

Core Foundation對象與Objective-C對象區別很小,不同之處只在於由哪一個框架生成。Foundation框架的API生成並持有的對象可以用Core Foundation框架的API釋放,反之也可以。

屬性

當ARC有效時,Objective-C類的屬性也會發生變化。

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_修飾符_32

以上,只有copy屬性不是簡單的賦值,它賦值的是通過NSCopying接口的copyWithZone:方法複製賦值源所生成的對象。

不過,在聲明類成員變量時,如果同屬性聲明中的屬性不一致就會引起編譯錯誤。

@interface Auto : NSObject {
id obj;
}
@property(nonatomic, weak) id obj;

我們嘗試跑這個代碼,發現可以正常運行。這是為什麼呢?

這是因為我們現在使用了新版 Xcode(Clang ≥ 8),該編譯器不會強制使用我們聲明的ivar,而是為屬性自動生成一個實例變量_obj。這樣的情況下,@property使用_obj,而我們定義的成員變量obj只是一個普通指針,沒有被使用,因此編譯器不會報衝突。那麼哪種情況下會報錯呢?

當我們顯式使用@synthesize,並讓屬性和我們定義的ivar綁定時。

@synthesize obj = obj;

這時出現報錯:

iOS開發讀書筆記:Objective-C高級編程 iOS與OS X多線程和內存管理-上篇(自動引用計數)_強引用_33

這是因為@synthesize強制讓weak屬性使用了強引用的ivar。
那麼解決這個問題的辦法有兩種:

@interface Auto : NSObject {
id __weak obj;
}
@property(nonatomic, weak) id obj;
@interface Auto : NSObject {
id obj;
}
@property(nonatomic, strong) id obj;

數組

靜態數組

靜態數組除 __unsafe_unretained 外,__strong__weak__autoreleasing 修飾的數組元素會被自動初始化為nil。

{
id obj[2];
obj[0] = [[NSObject alloc] init];
obj[1] = [NSMutableArray array];
}

數組超出其變量作用域時,數組中各個附有__strong修飾符的變量也隨之實效,其強引用消失,所賦值的對象也隨之釋放。

動態數組

將附有__strong修飾符的變量作為動態數組來使用時,需要手動管理內存。必須遵守以下事項:

  • 聲明方式
  • 需用指針顯式指定修飾符:由於“id *類型“默認為“id__autoreleasing *類型“,所以顯式指定為_ _strong修飾符。
id __strong *array = nil;
  • 指定類名的形式
NSObject * __strong *array = nil;
  • 內存分配

必須使用calloc函數分配內存。因calloc會將內存初始化為0,滿足__strong變量使用前需初始化為nil的要求。那麼為什麼不使用malloc呢?

這是因為使用malloc函數分配的內存區域沒有被初始化為0,因此nil會被賦值給__strong修飾符的並被賦值了隨機地址的變量中,從而釋放一個不存在的對象。

NSInteger entries = 10;
id __strong *array = (id __strong*)calloc(entries, sizeof(id));

若用malloc分配後,需要使用memset等函數將內存填充為0,禁止直接遍歷數組給元素賦值為nil。

  • 內存釋放

不能直接使用free釋放數組內存,需將所有元素賦值為nil,即讓元素對對象的強引用實效,釋放對象,再調用free釋放內存塊。否則會內存泄漏。

for (int i = 0; i < entries; i++) {
array[i] = nil;
}
free(array);

使用memset等函數填充0也無法釋放對象,會導致內存泄漏。

  • 禁止操作

禁止使用memcpy拷貝數組元素,realloc重新分配內存塊,這會導致對象被錯誤保留或重複釋放。