之前學習的文章鏈接
iOS內(nèi)存管理篇(一)---alloc/reatain/release/dealloc方法實現(xiàn)
iOS內(nèi)存管理篇(二)---NSAutoreleasePool/@autoreleasepool/autorelease理解與管理
前言:說到內(nèi)存管理,避免不了的就是循環(huán)應用和某個變量釋放的實際,雖然在實際開發(fā)種,ARC會為我們自動的加上引用技術和減少引用技術,但是并不是萬能的,百密一疏,還是會在實際開發(fā)過程中內(nèi)存管理出現(xiàn)問題,今天我們要來看的就是__strong,__weak,這些關鍵詞的作用和使用方法
__strong關鍵字與retain關似,用了它,引用計數(shù)自動+1
有如下 Demo:
id obj = [[NSObject alloc]init];
/*等價于*/
id __strong obj = [[NSObject alloc]init];
那么在內(nèi)存里面是怎么執(zhí)行這段代碼的呢
/*編譯器模擬的代碼*/
id obj = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(obj,@selector(init));
objc_release(obj);
從上面的代碼我們可以看出,系統(tǒng)兩次調(diào)用objc_msgSend來創(chuàng)建這個對象并為之開辟內(nèi)存空間,然后在變量作用于失效的時候使用objc_release來釋放對象,雖然在 ARC模式下我們不能使用 release,但是系統(tǒng)在運行時為我們自動添加了 release。
那么我們來看看實際的例子吧
有兩個變量,是用 strong 修飾的
@property (nonatomic, strong) NSString *str1;
@property (nonatomic, strong) NSString *str2;
做了如下的操作
self.str1 = @"A";
self.str2 = self.str1;
self.str1 = nil;
NSLog(@"self.str2 = %@",self.str2);
大家可以猜猜打印結(jié)果是什么
打印結(jié)果是 self.str2 = A
由于str2是strong定義的屬性,所以引用計數(shù)+1,使得它們所指向的值都是@"A", 雖然 self.str = nil 了,但是并沒有使得 str2的引用計數(shù)減1導致釋放,所以打印的結(jié)果為 A
__weak修飾符提供的功能如同魔法一般
若附有__weak修飾符的變量所引用的對象被廢棄,則將 nil賦值給該變量
使用附有__weak修飾符的變量,即是使用注冊到__autoreleasepool 中的對象
有如下代碼:
id __strong obj = [[NSObject alloc]init];
id __weak obj1 = obj;
我們看看系統(tǒng)是如何為我們處理的
/*編譯器模擬的代碼*/
id obj1;
objc_initWeak(&obj1,obj);
objc_destroyWeak(&obj1);
通過objc_initWeak函數(shù)初始化附有__weak修飾符的變量,在變量作用域結(jié)束的時候用objc_destroyWeak釋放該變量
我們來下一下的具體的例子
@property (nonatomic, strong) NSString *str1;
@property (nonatomic, weak) NSString *str2;
執(zhí)行如下的代碼
self.str1 = @"A";
self.str2 = self.str1;
self.str1 = nil;
NSLog(@"self.str2 = %@",self.str2);
結(jié)果是 self.str2 = null 為什么會是這個結(jié)果呢 分析一下,由于self.string1與self.str2指向同一地址,且str2沒有retain內(nèi)存地址,而 self.str1=nil釋放了內(nèi)存,所以string1為nil。
所以這是符合第一條規(guī)則的,因為str2 指向的地址已經(jīng)被釋放了,那么會將 nil 賦值給 str2
在這個過程中__weak幫我們干了什么呢
(1) 從 weak表中獲取廢棄對象的地址為鍵值的記錄
??(2) 將包含在記錄中的所有附有__weak 修飾變量的地址,賦值為 nil
??(3) 從 weak 表中刪除該記錄
??(4) 從引用技術表中刪除廢棄對象的地址為鍵值的記錄
我們來看看 weak 的另外一個特性
id __weak obj1 = obj/*obj 是 strong 的*/
NSLog(%@"obj1 = %@",obj1);
這段代碼轉(zhuǎn)換為系統(tǒng)編譯器碼為
id obj1;
objc_initWeak(&obj1,obj);
id temp = objc_loadWeakRetained(&obj1);
objc_autorelease(temp);
NSLog(%"%@",temp);
objc_destroyWeak(obj1);
我們可以注意到有個objc_autorelease這個函數(shù),所以正說明了第二點:使用附有__weak修飾符的變量,即是使用注冊到__autoreleasepool 中的對象,因為附有__weak的變量所使用的對象都被注冊到了__autoreleasepool里面,所以在@autoreleasepool塊結(jié)束之前可以放心使用,所以在實際開發(fā)過程中,如果大量使用 weak 來修飾變量的話,是會非常耗費 CPU資源的,所以當只有會可能產(chǎn)生循環(huán)引用的地方使用 weak 是合適的。
__unsafe_unretain顧名思義就是不安全的,其功能和 weak 差不多
有如下的代碼
id __unsafe_unretain obj = [[NSObject alloc]init];
這時候編譯器報出警告,該代碼對應的模擬代碼為
/*編譯器模擬的代碼*/
id obj = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(obj,@selector(init));
objc_release(obj);
objc_release函數(shù)立即釋放了生成并持有的對象,這樣對象的懸垂指針被賦值給了變量obj 當中
我們來看如下的代碼:
@property (nonatomic, strong) NSString *str1;
@property (nonatomic,__unsafe_unretain) NSString *str2;
執(zhí)行如下的代碼
self.str1 = @"A";
self.str2 = self.str1;
self.str1 = nil;
NSLog(@"self.str2 = %@",self.str2);
這次根本就不用輸出,在沒有輸出前,程序已經(jīng)崩潰了,其實就是野指針造成的,為何會造成野指針呢?同于用unsafe_unretained聲明的指針,由于 self.str1=nil已將內(nèi)存釋放掉了,但是str2并不知道已被釋放了,所以是野指針。然后訪問野指針的內(nèi)存就造成crash. 所以盡量少用unsafe_unretained關鍵字。
__autoreleasing:將對象賦值給附有__autoreleasing修飾的變量等同于 ARC無效時調(diào)用對象的 autorelease 方法
有如下代碼:
@autoreleasepool{
id __autoreleasing obj = [[NSObject alloc]init];
}
對應的編譯器碼:
/*編譯器的模擬代碼*/
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(obj,@selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);
- (void) generateErrorInVariable:(__autoreleasing NSError **)paramError{
NSArray *objects = [[NSArray alloc] initWithObjects:@"A simple error", nil];
NSArray *keys = [[NSArray alloc] initWithObjects:NSLocalizedDescriptionKey, nil];
NSDictionary *errorDictionary = [[NSDictionary alloc] initWithObjects:objects forKeys:keys];
*paramError = [[NSError alloc] initWithDomain:@"MyApp" code:1 userInfo:errorDictionary];
}
-(void)test
{
NSError *error = nil;
[self generateErrorInVariable:&error];
NSLog(@"Error = %@", error);
}
__autoreleasing則可以使對像延遲釋放。比如你想傳一個未初始 化地對像引用到一個方法當中,在此方法中實始化此對像,那么這種情況將是__autoreleasing表演的時候。看個示例:
@autoreleasepool{
id __autoreleasing obj = [[NSObject alloc]init];
}
對應的編譯器碼:
- (void) generateErrorInVariable:(__autoreleasing NSError **)paramError{
NSArray *objects = [[NSArray alloc] initWithObjects:@"A simple error", nil];
NSArray *keys = [[NSArray alloc] initWithObjects:NSLocalizedDescriptionKey, nil];
NSDictionary *errorDictionary = [[NSDictionary alloc] initWithObjects:objects forKeys:keys];
*paramError = [[NSError alloc] initWithDomain:@"MyApp" code:1 userInfo:errorDictionary];
}
-(void)test
{
NSError *error = nil;
[self generateErrorInVariable:&error];
NSLog(@"Error = %@", error);
}
符合內(nèi)存管理規(guī)則:誰分配誰釋放。
小結(jié):不管是__strong、__weak、__unsafe_unretain、__autoreleasing 這是為了讓我們更好的理解內(nèi)存管理,而且在必要的時候使用這些修飾符來管理我們的內(nèi)存,而在實際開發(fā)種,一般很少使用到后兩者,前兩者在循環(huán)引用的時候可以用來解循環(huán)引用,我們在今后的學習中還會學習和使用到它們,別忘了喲~
此文借鑒的文章鏈接
iOS中 property中的屬性strong 、weak、copy 、assign 、retain 、unsafe_unretained 與autoreleasing區(qū)別和作用詳解
書籍《Objective-C高級編程 iOS與OS X多線程和內(nèi)存管理》