自動引用計數
- 前言
- ARC規則
- 所有權修飾符
- **__strong修飾符**
- __weak修飾符
- __unsafe_unretained修飾符
- __autoreleasing修飾符
- 規則
- 屬性
- 數組
前言
上一篇我們主要學習了一些引用計數方法的內部實現,現在我們學習ARC規則。
ARC規則
所有權修飾符
OC中,為了處理對象,可以將變類型定義為id類型或各種對象類型。
對象類型: 即OC類的指針,例如“NSObject* ”
id類型: 用于隱藏對象類型的類名部分,相當于C語言中的(void *)
ARC有效時,id類型和對象類型同C語言其他類型不同,必須附加上所有權修飾符。
- __strong修飾符
- __weak修飾符
- __unsafe_unretained修飾符
- __autoreleasing修飾符
__strong修飾符
__strong修飾符是id類型和對象類型默認的所有權修飾符。也就是說
id obj = [[NSObject alloc] init];id __strong obj = [[NSObject alloc] init];
這兩種代碼是一樣的。
但是,當ARC無效時,該如何實現__strong修飾符呢。
{id obj = [[NSObject alloc] init];[obj release]
}
如上述代碼所示,附有__strong修飾符的變量obj在超出其變量作用域時,即在該變量被廢棄時,會釋放其被賦予的對象。
因此,我們可以通過在最后調用release代碼,實現這一功能。
如“strong”所示,__strong修飾符表示對對象的“強引用”。持有強引用的變量在超出其作用域時廢棄。隨著強引用的失效,引用的對象會隨之釋放。
對于自己生成并持有對象的源代碼來說,對象的所有者和對象的生存周期都是明確的,那么如果是取得非自己生成并持有的對象呢。
{id__strong obj = [NSMutableArray array];
}
這里我們通過NSMutableArray類的array類方法學習。
{//取得非自己生成并持有的對象id __strong obj = [NSMutableArray array];//變量obj為強引用,所以自己持有對象。
}
//變量obj超出其作用于,強引用失效,自動釋放自己持有的對象。
可見取得非自己生成但是持有的對象的生存周期也是明確的
即使是OC類成員變量,也可以在方法參數上,使用附有__strong修飾符的變量。
@interface Test : NSObject
{id __strong obj_;
}
- (void)setObject:(id __strong)obj;
@end@implementation Test
- (id)init
{self = [super init];return self;
}
- (void)setObject:(id __strong)obj
{obj_ = obj;
}
@end
下面我們進行使用:
{id __strong test = [[Test alloc] init];//test持有Test對象的強引用[test setObject:[[NSObject alloc] init];//Test對象的obj_成員,持用NSObjcet對象的強引用。
}
/*因為test變量超出其作用域,強引用失效所以自動釋放Test對象。Test對象的所有者不存在,因此廢棄該對象。廢棄Test對象的同時,Test對象的obj_成員也被廢棄,NSObjcet對象的強引用失效自動釋放NSObjcet對象所有者不存在,廢棄該對象。*/
通過這種方法,無需額外工作便可以使用于類成員變量以及方法參數中。
修飾符可以保證將附有這些修飾符的自動變量初始化為nil。
id __strong ojb0;
//這兩種初始化方式相同
id __strong obj0 == nil;
通過__strong
修飾符,不必再次鍵入retain或者release即可實現OC內存管理的思考方式。
并且,id類型和對象類型的所有權修飾符默認為__strong修飾符,所以不需要寫上"__strong"。這一設定使得ARC有效以及簡單的編程遵循了OC內存管理的思考方式。
__weak修飾符
如果僅使用__strong修飾符,容易發生循環引用的問題,這對項目是毀滅性的。
如以下這種情況:
{id test0 = [[Test alloc] init];//test0持有Test對象A的強引用id test1 = [[Test alloc] init];//test1持有Test對象B的強引用[test0 setObject:test1];/*Test對象A的obj_成員變量持用Test對象B的引用此時,持有Test對象B的強引用的變量為Test對象A的obj_和test1。*/[test1 setObject:test0];/*Test對象B的obj_成員變量持用Test對象A的引用此時,持有Test對象A的強引用的變量為Test對象B的obj_和test0。*/
}/*
因為 test0 變量超出其作用域,強引用失效,
所以自動釋放 Test 對象 A。
因為 test1 變量超出其作用域,強引用失效,
所以自動釋放 Test 對象 B。
此時,持有 Test 對象 A 的強引用的變量為
Test 對象 B 的 obj_。
此時,持有 Test 對象 B 的強引用的變量為
Test 對象 A 的 obj_。
發生內存泄漏!
*/
如下圖所示:
循環引用容易發生內存泄漏:即應當廢棄的對象在超出其生存周期后繼續存在。
上述代碼分別將對象A賦給test0,對象B賦給test1后,在超出作用域后無法正確被釋放。
為了避免以上這種情況,我們可以采用__weak修飾符。
__weak
修飾符:提供弱引用,不能持有對象實例。
id __weak obj = [[NSObject alloc] init];
會出現以下警告。
變量 obj 持有對持有對象的弱引用。因此,為了不以自己持有的狀態來保存自己生成并持有的對象,生成的對象會立即被釋放。
如果使用以下代碼,將對象賦值給附有__strong修飾符的變量之后,在賦值附有__weak修飾符的變量,就不會發生警告。
{//自己生成并且持有對象id __strong obj0 = [[NSObject alloc] init];//obj0變量為強引用,所以自己持有對象id __weak obj1 = obj2;//obj1變量持有生成對象的弱引用
}
//因為obj0變量超出其作用域,強引用失效,所以自動釋放自己持有的對象
//因為對象的所有者不在,所以會自動廢棄obj1
因此上述代碼只需要將可能發生循環引用的類成員變量改成附有__weak修飾符的成員變量,即可避免循環引用的問題。如下修:
@interface Test : NSObject
{id __weak obj_;
}
- (void)setObject:(id __strong)obj;
@end
此時對象引用情況如圖所示:
__weak修飾符還有另一優點:在持有某對象的弱引用時,若該對象被廢棄,則此弱引用將自動失效切處于nil被賦值的狀態(空弱引用)。
id __weak obj1 = nil;{id __strong obj0 = [[NSObject alloc] init];obj1 = obj0;NSLog(@"A: %@", obj1);}/*obj0變量超出其作用域,強引用失效所以自動釋放自己持有的對象因為對象無持用者,所以廢棄該對象廢棄對象的同時持有該對象弱引用的obj1變量的弱引用失效,nil賦值給obj1*/
NSLog(@"B: %@", obj1);
源代碼的結果如下:
像這樣,使用__weak修飾符即可避免循環引用。通過檢查附有__weak修飾符的變量是否為nil,可以判斷被賦值的對象是否已廢棄。
__unsafe_unretained修飾符
__unsafe_unretained修飾符正如其名,是不安全的所有權修飾符。
附有該修飾符的變量不屬于編譯器的內存管理對象。
與附有__weak修飾符的變量一樣,因此自己生成并持有的對象不能繼續為自己所有,所以生成的對象會立即釋放。但是當廢棄時并不會自動置nil。
id __unsafe_unretained obj1 = nil;{id __strong obj0 = [[NSObject alloc] init];obj1 = obj0;NSLog(@"A: %@", obj1);}
NSLog(@"B: %@", obj1);
以上代碼偶爾會運行成功,但更多情況下訪問一個空對象會報錯。
__autoreleasing修飾符
在 ARC 有效時,用 @autoreleasepool
塊替代 NSAutoreleasePool 類,用附有 __autoreleasing 修飾符的變量替代 autorelease 方法
/* ARC無效 */
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];/* 有效 */
@autoreleasepool {id __autoreleasing obj = [[NSObject alloc] init];
}
但是,通常情況下我們不會顯示的附加__autoreleasing修飾符和__strong修飾符。
當使用alloc/new/copy/mutableCopy
以外的方法來取得丟下時,該對象會自動被注冊到autorelease
方法中。
訪問附有__weak修飾符的變量時,必須訪問注冊到autoreleasepool的對象。這是因為__weak修飾符紙持有對象的弱引用,而對象有可能被廢棄,但是如果把要訪問的對象注冊到autoreleasepool中,在@autoreleasepool塊結束之前都能確保該對象存在。因此:
使用附有__weak修飾符的變量時必定要使用注冊到autoreleasepool中的對象
當我們顯示的制定__autoreleasing修飾符時,必須注意對象變量要為自動變量(包括局部變量,函數以及方法參數)
無論 ARC 是否有效,調試用的非公開函數 _objc_autoreleasePoolPrint()
都可使用。
該函數可以用于打印當前自動釋放池中的所有對象信息。
規則
當ARC有效時,需要遵守的規則:
- 不能使用
retain
/release
/retainCount
/autorelease
- 不能使用
NSAllocateObject
/NSDeallocateObject
- 須遵守內存管理的方法命名規則
- 不要顯式調用
dealloc
- 使用
@autoreleasepool
塊替代NSAutoreleasePool
- 不能使用區域(
NSZone
) - 對象型變量不能作為 C 語言結構體(
struct
/union
)的成員 - 顯式轉換 “
id
” 和 “void *
”
不能使用 retain
/release
/retainCount
/autorelease
內存管理是編譯器的工作,因此沒必要使用內存管理的方法。
設置ARC有效時,無需(禁止)再次鍵入retain或release代碼。
實際上,再次鍵入retain和release代碼時會報錯,所以應該是禁止鍵入。
同樣的,retainCount和release也會引起編譯錯誤。
不能使用 NSAllocateObject
/NSDeallocateObject
在ARC有效時,禁止使用NSAllocateObject函數。同retain方法一樣,會引起編譯報錯。同一釋放對象的NSDeallocateObject
函數也不可使用。
須遵守內存管理的方法命名規則
當ARC無效時,用于對象生成/持有的方法必須遵守以下的命名規則。
使用alloc/new/copy/mutableCopy
時,必須返回給調用方所應當持有的對象。
但是當ARC有效時,init
開始的方法必須是實例方法,并且要返回對象。返回的對象應為id類型或該方法聲明類的對象類型,或者是該類型的父類或者子類。該返回對象并不注冊到autoreleasepool
上。基本知識對alloc方法返回值的對象進行初始化處理并返回該對象。如下所示:
-(void) initWithObject:(id) obj;
對象型變量不能作為 C 語言結構體(struct
/union
)的成員
struct Data {NSMutableArray *array;
};
以上代碼會報錯
顯式轉換 “id
” 和 “void *
”
//id和void*互轉時需要通過__bridge轉換id obj = [[NSObject alloc] init];void *p = (__bridge void *)obj;id o = (__bridge id)p;
__bridge_retained 轉換可使要轉換賦值的變量也持有所賦值的對象。
/* ARC無效 */
id obj = [[NSObject alloc] init];
void *p = obj;
[(id)p retain];
__bridge_retained 轉換變為了 retain。變量 obj 和變量 p 同時持有對象。
void *p = 0;
{id obj = [[NSObject alloc] init];p = (__bridge_retained void *)obj;
}
NSLog(@"class=%@", [(__bridge id)p class]);
變量作用域結束時,雖然隨著持有強引用的變量 obj 失效,對象隨之釋放,但由于 __bridge_retained 轉換使變量 p 看上去處于持有該對象的狀態,因此該對象不會被廢棄。
__bridge_transfer 轉換提供與此相反的動作,被轉換的變量所持有的對象在該變量被賦值給轉換目標變量后隨之釋放。
id obj = (__bridge_transfer id)p;
//上述代碼在ARC無效時如下表達:
/* ARC無效 */
id obj = (id)p;
[obj retain];
[(id)p release];
同 __bridge_retained 轉換與 retain 類似,__bridge_transfer 轉換與 release 相似。在給 id obj 賦值時 retain 即相當于 __strong 修飾符的變量。
屬性
當ARC有效時,以下可作為這種屬性聲明中使用的屬性來用
以上各種屬性賦值給指定的屬性中就相當于賦值給附加各屬性對應的所有權修飾符的變量中。
只有 copy 屬性不是簡單的賦值,它賦值的是通過 NSCopying 接口的 copyWithZone: 方法復制賦值源所生成的對象。
另外,在聲明類成員變量時,如果同屬性聲明中的屬性不一致則會引起編譯錯誤。比如下面這種情況。
id obj;//默認為__strong@property (nonatomic, weak) id obj;
//會出現報錯//需要改成以下形式
id __weak obj;
數組
使用修飾符賦值數組的使用與變量相同。
id objs[10];id __weak objs[10];
__unsafe_unretained
修飾符以外的 __strong
/__weak
/__autoreleasing
修飾符保證其指定的變量初始化為 nil
。同樣地,附有 __strong
/__weak
/__autoreleasing
修飾符變量的數組也保證其初始化為 nil
。
下面我們就來看看數組中使用附有 __strong
修飾符變量的例子。
{id objs[2];objs[0] = [[NSObject alloc] init];objs[1] = [NSMutableArray array];
}
數組超出其變量作用域時,數組中各個附有 __strong
修飾符的變量也隨之失效,其強引用消失,所賦值的對象也隨之釋放。這與不使用數組的情形完全一樣。
將附有 __strong
修飾符的變量作為動態數組來使用時又如何呢?在這種情況下,根據不同的目的選擇使用 NSMutableArray
、NSMutableDictionary
、NSMutableSet
等 Foundation 框架的容器。這些容器會恰當地持有追加的對象并為我們管理這些對象。
像這樣使用容器雖然更為合適,但在 C 語言的動態數組中也可以使用附有 __strong
修飾符的變量,只是必須要遵守一些事項。以下按順序說明。
聲明動態數組用指針。
id __strong *array = nil;
聲明動態數組時,我們需要顯式的指定為__strong修飾符。
id __strong *array = nil;
由于 “id *
類型” 默認 為 “id __autoreleasing *
類型”,所以有必要顯式指定為 strong
修飾符。另外,雖然保證了附有 __strong
修飾符的 id
型變量被初始化為 nil
,但并不保證附有 __strong
修飾符的 id
指針型變量被初始化為 nil
。
使用類名如下述描述:
NSObject * __strong *array = nil;
其次,使用 calloc 函數確保想分配的附有 __strong
修飾符變量的容量占有的內存塊。
array = (id __strong *)calloc(entries, sizeof(id));
該源代碼分配了 entries
個所需的內存塊。由于使用附有 __strong
修飾符的變量前必須先將其初始化為 nil
,所以這里使用使分配區域初始化為 0 的 calloc
函數來分配內存。不使用 calloc
函數,在用 malloc
函數分配內存后可用 memset
等函數將內存填充為 0。
但是,像下面的源代碼這樣,將 nil
代入到 malloc
函數所分配的數組各元素中來初始化是非常危險的。
array = (id __strong *)malloc(sizeof(id) * entries);
for (NSUInteger i = 0; i < entries; ++i)array[i] = nil;
這是因為由 malloc
函數分配的內存區域沒有被初始化為 0,因此 nil
會被賦值給附有 __strong
修飾符的并被賦值了隨機地址的變量中,從而釋放一個不存在的對象。在分配內存時推薦使用 calloc
函數。
像這樣,通過 calloc
函數分配的動態數組就能完全像靜態數組一樣使用。
array[0] = [[NSObject alloc] init];
但是,在動態數組中操作附有 __strong
修飾符的變量與靜態數組有很大差異,需要自己釋放所有的元素。
當我們要廢棄數組時,不能如下直接free。會使數組各元素的值的對象無法釋放,引起內存泄漏。如下述代碼所示。
free(array);
這是因為:在靜態數組中,編譯器能夠根據變量的作用域自動插入釋放賦值對象的代碼,而在動態數組中,編譯器不能確定數組的生存周期,所以無從處理。
如以下源代碼所示,一定要將 nil
賦值給所有元素中,使得元素所賦值對象的強引用失效,從而釋放那些對象。在此之后,使用 free
函數廢棄內存塊。
for (NSUInteger i = 0; i < entries; ++i)array[i] = nil;
free(array);
同初始化時的注意事項相反,即使用 memset
等函數將內存填充為 0 也不會釋放所賦值的對象。這非常危險,只會引起內存泄漏。對于編譯器,必須明確地使用賦值給附有 __strong
修飾符變量的源代碼。所以請注意,必須將 nil
賦值給所有數組元素。
并且,memcpy和realloc函數也會有危險,因為數組元素所賦值的對象有可能被保留在內存中或是重復被廢棄,所以也禁止使用。