【iOS】自動引用計數(一)
- 自動引用計數
- 自動引用計數原理
- 內存管理
- 自己生成的對象自己持有
- 非自己生成的對象自己也能持有
- 不需要自己持有的對象時釋放
- 無法釋放非自己持有的對象
- alloc/retain/release/dealloc實現
- autorelease
- 認識autorelease
- 使用autorelease
- 實現autorelease
- ARC規則
- 所有權修飾符
- __strong修飾符
- 管理成員變量的對象所有者
- 作用域中管理
- 賦值上管理
- 管理方法參數的對象所有者
- __weak修飾符
- __unsafe_unretained修飾符
- __autoreleasing修飾符
- __autoreleasing使用機制
- __autoreleasing隱式使用機制__
- 規則
- 屬性
- 數組
- 靜態數組
- 動態數組
自動引用計數
自動引用計數原理
Objective-C中的內存管理就是引用計數,而自動引用計數則是指內存管理中對引用採取自動計數的技術。自動引用計數技術用於管理對象的引用計數,也就是對象被引用的次數。
當一個對象的引用計數大於0時,表示該對象被持有,不可被釋放;當某個指針不再指向該對象時,引用計數減1;當對象的引用計數變為0時,系統將銷燬對象,回收內存。
正如蘋果的官方説明,在新一代Apple LLVM編譯器中,編譯器將自動進行內存管理。
內存管理
Cocoa框架中Foundation框架類庫的NSObject類擔負內存管理的職責,這裏先使用一個圖直觀的感受如何使用引用計數管理對象的內存:
簡而言之,引用計數僅僅只是“生成”、“持有”、“釋放”、“廢棄”這四個詞的操作:
自己生成的對象自己持有
編程人員自己生成對象的方法有:
- 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 方法的區別:
無法釋放非自己持有的對象
對於持有者是自己的對象,在不需要時需要將其釋放。而除此之外的對象絕不能釋放,倘若在程序中釋放了非自己所持有的對象就會造成崩潰。
例如:釋放之後再次釋放已非自己持有的對象
id obj = [[NSObject alloc] init];
[obj autorelease];
[obj autorelease];
alloc/retain/release/dealloc實現
介於包含NSObject類的Foudation框架並沒有公開,部分源代碼沒有公開。為此,我們首先使用開源軟件GNUstep來説明。
- alloc
我們直接來看一下去掉NSZone後簡化了的源代碼:
alloc類方法用struct obj_layout中的retained整數來保存引用計數,並將其寫入對象內存頭部,該對象內存塊全部置0後返回。
- retain
這裏先認識一下retainCount,通過retainCount實例方法可獲得對象的引用計數。
執行alloc後的對象的retainCount是“1”。因為分配是全部置0,所以retained為0。由NSExtraRefCount(self) + 1得出retainCount為1。
由對象尋址找到對象內存頭部,從而訪問其中的retained變量。
我們再通過源代碼就看出retain方法會使retained變量加1。
這裏雖然寫入了retained變量超出最大值時發生異常的代碼,但實際上只運行了使retained加1的retained++的代碼。
- release
- 當retained變量大於0時,release實例方法使retained變量減1。
- 當retained變量等於0時調用dealloc實例方法,廢棄對象。
值得注意的是:上述代碼僅廢棄由alloc分配的內存塊。
總結一下:
- 在Objective-C的對象中存有引用計數這一整數值。
- 調用alloc或retain方法後,引用計數值加1。
- 調用release後,引用計數值減1。
- 引用計數值為0時,調用dealloc方法廢棄對象。
蘋果是採用散列表(引用計數表)來管理引用計數的。
autorelease
認識autorelease
首先複習一下C語言的自動變量。當程序執行時,若某自動變量超出其作用域,該自動變量將被自動廢棄。
autorelease會像C語言的自動變量那樣來對待對象實例。當超出變量作用域時,對象實例的release實例方法被調用。初次之外,與C語言的自動變量不同的是,編程人員可以設定變量的作用域。
使用autorelease
autorelease具體使用方法:
- 生成並持有NSAutoreleasePool對象。
- 調用已分配對象的autorelease實例對象。
- 廢棄NSAutoreleasePool對象。
這裏首先認識一下NSAutoreleasePool對象的生存週期。
NSAutoreleasePool對象的生存週期相當於C語言變量的作用域。對於所有調用過autorelease實例方法的對象,在廢棄NSAutoreleasePool對象時,都將調用release實例方法。
在Cocoa框架中,Cocoa程序都有一個主事件循環(RunLoop),程序會一直在循環處理事件,而不是一運行就退出。在每次循環中,系統就會自動管理(創建、使用、銷燬)一個自動釋放池,以釋放autorelease的對象。
但在大量產生autorelease的對象時,只要不廢棄NSAutoreleasePool對象,那麼生成的對象就不能被釋放,因此有時會產生內存不足的現象。
例如讀入大量圖像的同時改變其尺寸,這種情況下會大量產生autorelease的對象,外面只有一個大的autoreleasepool,那麼在循環中產生的所有對象要等到循環結束後才釋放,這樣每張圖片的內存都暫時保留在內存裏,很容易爆內存。
在此情況下,有必要在適當的地方生成、持有或廢棄NSAutoreleasePool對象。在這個例子中,也就是在循環中手動創建小的@autoreleasepool塊。
這樣。每次循環結束時,池子就被清空,該釋放的對象馬上釋放,內存使用量會保持穩定。
另外,Cocoa框架中也有很多類方法用於返回autorelease的對象。
id array = [NSMutableArray arrayWithCapacity:1];
這個代碼等同於源代碼:
實現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);
}
管理方法參數的對象所有者
-(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;
這樣,就可以通過__strong修飾符,不必再次輸入retain或者release,且滿足了“引用計數式內存管理的思考方式”。
id類型和對象類型的所有權修飾符默認為__strong修飾符。
__weak修飾符
__strong修飾符不能解決引用計數式內存管理中“循環引用”的問題。
{
//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];
那麼怎樣才能避免循環引用呢?這時候就需要__weak修飾符了。
弱引用不能持有對象實例。
id __weak obj = [[NSObject alloc] init];
上述代碼會引起警告:
這是因為代碼將自己生成並持有的對象賦值給附有__weak修飾符的變量obj,也就是變量obj持有對持有對象的弱引用。因此,為了不以自己持有的狀態來保存自己生成並持有的對象,生成的對象會立即釋放,編譯器就會給出警告。
下面代碼(將對象先賦值給附有_ _strong修飾符的變量後再賦值給附有 _ _weak修飾符的變量)可以解決這個問題:
id __strong obj1 = [[NSObject alloc] init];
//因為obj1變量為強引用,所以自己持有對象
id __weak obj2 = obj1;
//obj2變量持有生成對象的弱引用
超出obj1變量作用域,強引用實效,自動釋放自己持有的對象。對象所有者不存在,廢棄該對象。因為弱引用的變量不持有對象,所以超出變量作用域時,對象即被釋放。
再看上面“循環引用”的問題,這時我們將成員變量弱引用,就可以避免該問題了。
@interface Classes : NSObject {
id __weak obj_;
}
__weak修飾符的另一個優點:在持有某對象的弱引用時,若該對象被廢棄,弱引用將自動失效且處於nil被賦值的狀態(空弱引用)。
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。
__unsafe_unretained修飾符
不安全的所有權修飾符。附有__unsafe_unretained修飾符的變量不屬於編譯器的內存管理對象。
與附有_ _weak修飾符的變量一樣,附有 __unsafe_unretained修飾符的變量因為自己生成並持有的對象不能繼續為自己所有,所以生成的對象會被立即釋放。
__unsafe_unretained也不能持有對象實例。
id __unsafe_unretained obj = [[NSObject alloc] init];
那麼,我們對比一下區別:
id __unsafe_unretained obj1 = nil;
{
id __strong obj2 = [[NSObject alloc] init];
obj1 = obj2;
NSLog(@"%@", obj1);
}
NSLog(@"%@", obj1);
我們可以看到輸出結果已經不一樣了,第一次可以輸出,而第二次出現了典型的“訪問已釋放對象”“的報錯。
對比_ _weak,附有__unsafe_unretained的對象是一個不安全的弱引用。它不會增加引用計數,也不會在對象銷燬時自動置為nil。
因此,在作用域中和__weak無異,正常輸出持有的對象,而超出作用域後,對象銷燬且不會自動置為nil,因此程序崩潰,且此時的obj1就是懸垂指針。
懸垂指針:指向一塊已經被釋放或無效的內存的指針。
換句話説,這個指針曾經指向一個合法的對象,但那個對象後來被銷燬了,指針變量本身還保留着那塊舊地址。程序若再訪問,就會訪問到不可用的內存區域,導致程序崩潰。
__autoreleasing修飾符
__autoreleasing使用機制
實際上,不能使用autorelease方法,也不能使用NSAutoreleasePool類。雖然autorelease無法直接使用,但實際上,ARC有效時autorelease功能使起作用的。
- ARC無效時
- ARC有效時
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}
ARC有效和無效時,有一部分是等價的:
- 指定@autoreleasepool塊來代替無效時NSAutoreleasePool類對象的生成、持有及廢棄。
- 對象賦值給附有__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;
那麼這是為什麼呢?
這是因為__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
內存管理是編譯器的工作,因此不必使用內存管理方法。如若使用,將產生報錯:
總之,只能在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方法適用的情況有:
- 對象的所有者不持有該對象,要被廢棄時。
- 刪除已註冊的代理或觀察者對象。
-(void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
ARC會自動對此進行處理,因此在ARC有效時會遵循無法顯式調用dealloc這一規則。若使用,同樣會引起編譯錯誤。
- 使用@autoreleasepool塊代替NSAutoreleasepool
使用NSAutoreleasepool類會引起編譯器報錯。
- 不能使用區域(NSZone)
NSZone是早期Objective-C中的一種內存分配優化機制,是為了讓開發者能夠把不用類型的對象分配在不同的內存區域中,以便優化內存管理或調試。
現代Objective-C宏定義#define __OBJC2__表示當前編譯環境使用現代運行時:
- 所有對象分配都統一由malloc管理。
- 內存區域概念已完全廢棄。
- allocWithZone:仍然存在,但zone參數被忽略。
- alloc方法現在直接調用allocWithZone:nil。
- 對象型變量不能作為C語言結構體的成員
- 顯式轉換"id"和"void"
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類的屬性也會發生變化。
以上,只有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;
這時出現報錯:
這是因為@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重新分配內存塊,這會導致對象被錯誤保留或重複釋放。