引用計數式的內存管理 在arc中是沒有改變的。 在arc中,有有效和無效兩種方式,我們可以在一個app中混合使用。
使用clang(LLVM編譯器)或者以上版本,指定編譯器屬性為:"-fobjc-arc"就可以使用arc;
在oc中,引用計數式的內存管理的思考方式就是思考arc所引起的變化:
1、自己生成的對象,自己持有。 2、非自己生成的對象,自己也能持有。 3、自己持有的對象不再需要時釋放。 4、非自己持有的對象無法釋放。
所有權修飾符: oc中,為了處理對象,可以將變量類型定義為id類型或各種對象類型。 對象類型就是指向NSObject這樣的oc類的指針。 id類型用於隱藏對象類型的類名部分,相當於c中的void *; arc中,需要加上修飾符: __strong __weak __unsafe_unretained __autoreleasing
1、__strong 他是id類型和對象類型默認的所有權修飾符。 也就是說 代碼中的id變量實際上被附加了所有權修飾符。 id和對象類型在沒有明確指定所有權修飾符的時候,默認是strong類型的: id obj = [[NSObject alloc]init]; id __strong obj = [[NSObject alloc]init]; 上面兩行代碼時等效的。
非arc時的比較:
{
id __strong obj = [[NSObject alloc] init];
}
{
id obj = [[NSObejct alloc] init];
[obj release];
}
為了釋放生成並持有的對象,增加了調用release方法的代碼。
__strong 修飾符表示對對象的“強引用”, 持有強引用的變量在超出其作用域時被廢棄,隨著強引用的失效,引用的對象會隨之釋放。 來分析一下此段代碼: 取得自己持有的對象。 { //自己生成並持有對象 id __strong obj = [[NSObject alloc] init]; //因為變量obj為強引用 //所以自己持有對象 } //因為變量obj超出其作用域,強引用失效 //所以自動釋放自己持有的對象, 對象的多有者不存在,因此廢棄該對象。
取得非自己持有的對象: { //取得非自己生成並持有的對象 id __strong obj = [NSMutableArray array]; //因為變量obj為強引用, 所以自己持有對象 } //因為變量obj超出其作用域,強引用失效。 //所以自動地釋放自己持有的對象。
在這,對象的所有者 和 對象的生存周期也是明確的。 __strong修飾符的變量之間可以相互賦值。 __strong修飾符的變量不僅在變量作用域中,在賦值上也能夠正確地管理其對象的所有者。
、在oc中,我們可以使用賦有__strong修飾符的變量。
@interface Test: NSObject {
id __strong obj_;
}
- (void)setObject:(id __strong)obj;
@end
通過__strong修飾符,無需再鍵入retain或者release,他們完美地滿足了“引用計數式內存管理的思考方式”: 1、自己生成的對象,自己持有。 2、非自己生成的對象,自己也能持有。 3、不再需要自己持有的對象時釋放。 4、非自己持有的對象無法釋放。
其中1,2 通過帶有__strong修飾符的變量賦值就可以完成。 廢棄帶__strong修飾符的變量或者對變量賦值,都可以做到:”不再需要自己持有的對象釋放“。
第四項的 不是自己持有的對象釋放, 由於我們不用再次鍵入 release 所以原本就不會執行。 他們都滿足於引用計數式的內存管理的思考方式。
__strong屬於默認的修飾符,所以我們不需要鍵入。 arc有效並簡單的編程 遵循了oc的內存管理的思考方式。
2、__weak: 在引用計數的時候會產生 循環引用 的問題。 如圖:
來看循環引用代碼:
@interface Text : NSObject {
id __strong obj_;
}
- (void)setObject:(id __strong) obj;
@end
@implementation Text
- (id) init {
self = [super init];
return self;
}
- (void) setObject:(id __strong)obj {
obj_ = obj;
}
@end
{
id text0 = [[Text alloc] init]; //對象a
id text1 = [[Text alloc] init]; //對象b
[text0 setObject:text1]; //對象a的obj_持有對象b的強引用, 此時持有對象b的強引用變量為 a的obj_和text1.
[text1 setObject:text0]; //對象b的obj_持有對象a的強引用, 此時持有對象b的強引用變量為b的obj_和text0
}
//因為text0 變量超出作用域,強引用失效。自動釋放對象a
//因為text1 變量超出作用域,強引用失效,自動釋放對象b //此時持有對象a的強引用變量為對象b的obj_ 持有對象b的強引用的變量為 對象a的obj_ 發生內存洩露。
還有就是當你只有一個對象,該對象持有其自身的時候也會發生洩露。
__weak與strong相反,提供弱引用,他不能持有對象實例。 我們為了不以自己持有的狀態來保存自己生成並持有的對象,生成的對象會立即被釋放。 { id __strong obj1 = [[NSObject alloc] init]; id __weak obj2 = obj1; }
__weak修飾符的變量不持有對象,所以在超出其變量作用域時,對象即被釋放。 如果像下面這樣將先前可能發生循環引用的類成員變量改成附有__weak修飾的成員變量的話,就會避免循環引用。 @interface Text : NSObject { id __weak obj_; } - (void)setObject:(id __strong) obj; @end
__weak還有一個優點,在持有某對象的弱引用時,若該對象被拋棄,則此弱引用將自動失效,並處於nil被賦值的狀態(空弱應用)。
在ios5 以上版本使用的是__weak, 而在ios4中使用的是__unsafe_unretained修飾符。
3、__unsafe_unretained 他是不安全的所有權修飾符。附有__unsafe_unretained修飾符的變量不屬於編譯器內存管理對象。 他跟__weak一樣,因為自己生成並持有的對象不能繼續為自己所有,所以生成的對象會立即被釋放。 代碼:
id __unsafe_unretianed obj1 = nil;
{
id __strong obj0 = [[NSObject alloc] init];
//obj0為強引用。 自己持有對象
obj1 = obj0;
//obj1變量即不持有對象的強引用也不持有弱引用。
}
//obj0 超出作用域,強引用失效。 自己釋放自己持有的對象, 因為對象沒有持有者,所以廢棄對象。
//obj1 變量表示的對象,已經被廢棄了, 懸垂指針。
4、__autoreleasing修飾符 arc有效的時候,autorelease 和 NSAutoreleasePool 都是不能直接使用的。 我們應該寫成: @autoreleasepool{ id __autoreleasing obj = [[NSObject alloc] init];
} @autoreleasepool來替代NSAutoreleasePool類對象的生成,持有以及廢棄。 在arc有效的時候,要通過對象賦值給附加了__autoreleasing修飾符的變量來替代調用autorelease方法。
@autoreleasepool{ //取得非自己生成並持有的對象 id __strong obj = [NSMutableArray array]; //因為變量obj為強引用,所以自己持有對象, 並且該對象 由編譯器判斷其方法名後,自動注冊到autoreleasepool } //因為變量obj超出作用域,強引用失效,自動釋放持有的對象, 同事隨著@autoreleasepool的結束,注冊到其中的所有對象被釋放。因為對象的所有者不存在,所以廢棄。
還有就是__weak修飾的變量,他在被訪問的時候,必定會訪問注冊到autoreleasepool的對象, 因為__weak修飾符只持有對象的弱引用, 而在訪問引用對象的過程中,該對象有可能被廢棄,如果把要反問的對象注冊到autoreleasepool中,那麼在autoreleasepool塊結束之前都能確保該對象存在;
來說一下__autoreleasing 如下: NSError **error 是等同於: NSError *__autoreleasing*error的。
如下會發生錯誤:
NSError *error = nil;
NSError **pError = &error;
要修改加上__strong修飾符。
NSError *error = nil;
NSError *__strong*pError = &error;
NSError __weak*error = nil;
NSError *__weak*pError = &error;
下面的例子也是正確的:
NSError __strong *error = nil;
NSError **pError = &error;
其實他是被改寫了:
NSError __strong*error = nil;
NSError _autoreleasing *tem = error;
NSError **pError = &tem;
在NSAutoreleasePool中,他可以嵌套使用,在@autoreleasepool中也可以做到:
@autoreleasepool{
@autoreleasepool{
@autoreleasepool{
id __autoreleasing obj = [[NSObject alloc] init];
}
}
}
不論在arc還是非arc中,我們可以使用調試用的非公開函數:_objc_autoreleasePoolPrint() 函數。利用它可以有效的幫我們調試注冊到autoreleasepool上的對象。
__strong和__weak修飾符的變量類似與c++中的職能指針std::shared_ptr和std::weak_ptr。 shared_ptr通過引用計數來持有c++類實例,weak_ptr可避免循環引用。 在不得不使用沒有__strong和__weak修飾符的c++時,強烈推薦這兩種指針。
ARC新規則: 1、不能使用retain/release/retainCount/autorelease 2、不能使用NSAllocateObject/NSDeallocateObject 3、必須遵守內存管理的方法命名規則 4、不要顯式調用dealloc 5、使用@autoreleasepool塊替代NSAutoreleasePool 6、不能使用區域NSZone 7、對象型變量不能作為c語言結構體的成員 8、顯式轉換id和void*
官方文檔說: “設置arc有效時,無需再次鍵入retain和release代碼。”
不知道還記得說過的alloc的實現沒,在gnustep中,alloc是通過調用NSAllocateObject來實現的。 其實他跟retain是一樣的,這樣在arc狀態下就會引起錯誤。NSDeallocateObject也是同樣的道理。
對象型變量不能作為c語言結構體的成員: 這是什麼意思呢? 因為c語言不能管理結構體成員的生存周期。而arc卻把內存管理交給了編譯器, 所以編譯器要知道並管理對象的生存周期。 所以不能作為結構體成員。 如果你很想把對象型變量加入到結構體成員中,你需要強制轉換為void* 或者加上__unsafe_unretained修飾符。
顯式轉換id和void* 單純賦值的情況: 使用__bridge 轉換 id obj = ------ void *p = (__bridge void*)obj; id o = (__bridge id)p; 這樣賦值 有個缺點,就是安全性問題,他會比__unsafe_unretained安全性更低,如果不注意管理對象,很容易造成內存洩露。
__bridge有兩種類型,分別是:__bridge_retianed 和 __bridge_transfer。 他們類似於 retian和release; void *p = 0; { id obj = [[NSObject alloc] init]; p = (__bridge_retained void *)obj; }
//此時作用域結束,obj釋放。 但是由於__bridge_retianed轉換使變量p看上與處於持有對象的狀態。所以對象不會被丟棄。
id obj = (__bridge_transfer id)p; //ob持有對象, p被釋放。
在寫代碼的時候,當arc有效的時候,oc類的屬性也會發生變化。 如:@property (nonatomic, strong) NSString *name; 下面是屬性列表:
上面的copy屬性,它的賦值是通過NSCopying接口和copyWithZone方法復制賦值源所生成的對象。 這裡的nonatomic來解釋一下: 他是禁止多線程,可以進行變量保護, 能提高效率。 還有一個 atomic 這是oc的線程保護機制,防止發生信息未寫完而被讀取。
這裡不得不說一下內存分配的事情: NSObject * __strong *array = nil;
如下分配: array = (id __strong *)calloc(***, sizeof(id)); 這裡是分配的***個所需要的內存塊,由於我們使用了__strong修飾符, 所以必須先初始化為nil; calloc會給你的區域初始化為0;
但是我們使用malloc會怎樣呢 ? array = (id __strong*)malloc(sizeof(id) * ***); 這裡是沒有初始化的,也就是說這裡的內存是隨機分配的。 如果你這樣來初始化: for (NSInteger i = 0; i < ***; ++i) { array[i] = nil; } 這樣很危險,因為我們是強引用對象, 當你nil賦值的時候,array[i]持有的對象會被釋放掉,但是此時的對象時隨機分配的地址,是無意義的,也就是說我們釋放了一個不存在的對象。 此時我們需要使用memset來初始化。 memset(array, 0, *** * sizeof(id));
ARC的實現: 1、__strong 最優化問題。 id __strong obj = [NSMutableArray array]; 他在編譯器中會出現模擬代碼 如下: id obj = objc_msgSend(NSMutableArray, @selector(array)); objc_retainAutoreleasedValue(obj); objc_release(obj); 這裡有個函數 objc_retainAutoreleasedValue。 還存在一個函數 objc_autoreleasepoolReturnValue。
看一下array函數的轉換: +(id)array { return [[NSMutableArray alloc]init]; } 轉換模擬代碼: id obj = objc_msgSend(NSMutableArray, @selector(alloc)); objc_msgSend(obj, @selector(init)); return objc_autoreleaseReturnValue(obj);
objc_autoreleaseReturnValue函數返回注冊對象到autoreleasepool中。 objc_autoreleaseReturnValue會檢查使用該函數的方法或者函數調用方的執行命令列表,如果方法或者函數的調用方在調用了方法或函數後緊接著調用objc_retainAutorelwasedReturnValue函數, 那麼將不會將返回的對象注冊到autoreleasepool中,而是直接傳遞到方法或者函數的調用方。 如圖協作:
2、__weak 1)、若附有__weak修飾符的變量所引用的對象被拋棄,則將nil賦值給該變量。 2)、若用附有__weak修飾符的變量,即是試用注冊到autoreleasepool中的對象。
id __weak obj1 = obj; 他做了什麼呢? 來看:id obj1; objc_initWeak(&obj, obj); objc_destroyWeak(&obj1); 這裡就是初始化和結束的過程。 其實就像這樣: id obj1; obj1 = 0; objc_storeWeak(&obj1, obj); objc_storeWeak(&obj1, 0); 解釋一下: 這裡第一個storeWeak函數把第二個參數的賦值對象的地址作為鍵值,將第一個參數的附有__weak修飾符的變量的地址注冊到weak表中。 可以注意到,第二次調用的時候第二個參數為0, 也就是說第二個參數為-0的時候,會把變量的地址從weak表中刪除。
weak表是什麼呢? 此時聯想到引用計數表, 他們都是利用散列來實現的。 這裡的一個鍵值可以注冊多個變量的地址。 就跟一個對象可以同時付給多個附有__weak修飾符的變量。 也就是說,如果你用一個廢棄對象的地址作為鍵值來檢索,你能夠告訴的獲取對應的附有__weak修飾符的變量的地址。
當釋放對象的時候, 廢棄掉誰都不持有的對象,程序後續還會出現動作: 1、objc_release 2、因為引用計數為0, 所以執行dealloc 3、_objc_rootDealloc 4、object_dispose 5、objc_destructInstance 6、objc_clear_deallocating 最後的第六個步驟的函數會出現如下動作: 1、從weak表中獲取廢棄對象的地址為鍵值的記錄。 2、將包含在記錄中的所有附有__weak修飾符變量的地址,賦值為nil。 3、從weak表中刪除該記錄。 4、從引用計數表中刪除廢棄對象的地址為鍵值的記錄。
當我們使用注冊到autoreleasepool中的對象的時候: id __weak obj1 = obj; 此時的模擬代碼為: id obj1; objc_initWeak(&obj1, obj); id tmp = objc_loadWeakRetained(&obj1); objc_autorelease(tmp); objc_destroyWeak(&obj1);
這裡增加了objc_loadWeakRetained和objc_autorelease的調用。 1、objc_loadWeakRetained:函數取出附有__weak修飾符變量所引用的對象,並retain。 2、objc_autorelease函數將對象注冊到autoreleasepool中。
這樣就可以安全的使用附有__weak修飾符的變量了。 但是如果對象很多呢 ? 最好的方法就是先暫時賦值給 __strong修飾符的變量。 這樣對象就僅登錄到autoreleasepool中一次。
最後提醒的是:id __weak obj = [[NSObject alloc] init]; 和 id __unsafe_unretained obj = [[NSObject alloc] init]; 這樣是不可以的, 前者是因為不能持有對象,後者是obj被賦予的是 懸垂指針。 雖然在arc中不會造成內存洩露,但是還是不要這樣使用的好。
3、引用計數 獲取引用計數值的函數: uintptr_t _objc_rootRetainCount(id obj) 這個函數可以獲取指定對象的引用計數值(ARC中,retainCount已經不能用了); 函數 _objc_autoreleasePoolPrint函數會觀察注冊到autoreleasepool中的引用對象。
-----2014/3/18 Beijing