1. 程式人生 > >Objective-C高階程式設計讀書筆記之記憶體管理

Objective-C高階程式設計讀書筆記之記憶體管理

Objective-C高階程式設計
iOS與OS X多執行緒和記憶體管理

自動引用計數(ARC, Automatic Reference Counting)

目錄

  1. 什麼是自動引用計數
  2. 記憶體管理的思考方式
  3. autorelease
  4. 所有權修飾符介紹
  5. ARC規則
  6. ARC實現(所有權修飾符作用詳解)
  7. 如何獲取引用計數值
  8. 總結

1. 什麼是自動引用計數

編譯器自動幫你在合適的地方插入retain/release程式碼, 不用你手動輸入.

2. 記憶體管理的思考方式

  • 自己生成的物件, 自己持有.
  • 非自己生成的物件, 自己也能持有.
  • 不再需要自己持有物件時釋放.
  • 非自己持有的物件無法釋放.
物件操作 Objective-C方法
生成並持有物件 alloc/new/copy/mutableCopy等方法
持有物件 retain方法
釋放物件 release方法
廢棄物件 dealloc方法

非自己生成的物件, 自己也能持有

如 :

Objective-C
12 id
obj=[NSMutableArrayarray];// 取得物件存在, 但自己並不持有物件[obj retain];// 自己持有物件

不再需要自己持有物件時釋放

如 :

Objective-C
12 idobj=[[NSObjectalloc] init
];// 自己持有物件[obj autorelease];// 取得的物件存在, 但自己不持有物件

無法釋放非自己持有的物件

如 :

Objective-C
12 idobj=[NSMutableArrayarray];// 取得物件存在, 但自己並不持有物件[obj release];// 釋放了非自己持有的物件!會導致應用程式崩潰

永遠不要去釋放非自己持有的物件!

Objective-C
1234 -Objective-C的物件中存有引用計數這一整數值-呼叫alloc/retain方法後,引用計數+1-呼叫release,引用計數-1-引用計數為0,呼叫dealloc廢棄物件

GNUstop將引用計數儲存在物件佔用記憶體塊頭部的結構體(struct obj_layout)變數(retained)中, 而蘋果的實現則是採用散列表(引用計數表)來管理引用計數.

GNUstop的好處 :

  • 少量程式碼即可完成.
  • 能夠統一管理引用計數用記憶體塊與物件用記憶體塊.

蘋果的好處 :

  • 物件用記憶體塊的分配無需考慮記憶體塊頭部.
  • 引用計數表各記錄中存有記憶體塊地址, 可從各個記錄追溯到各物件的記憶體塊.

3. autorelease

每一個RunLoop對應一個執行緒, 每個執行緒維護一個autoreleasepool.

RunLoop開始 -> 建立autoreleasepool -> 執行緒處理事件迴圈 -> 廢棄autoreleasepool -> RunLoop結束 -> 等待下一個Loop開始

可以用以下函式來列印AutoreleasePoolPage的情況

Objective-C
1234 // 函式宣告externvoid_objc_autoreleasePoolPrint();// autoreleasepool 除錯用輸出開始_objc_autoreleasePoolPrint();

NSAutoreleasePool類的autorelease方法已被過載, 因此呼叫NSAutoreleasePool物件的autorelease會報錯.

4. 所有權修飾符

  • __strong
  • __weak
  • __unsafe_unretained
  • __autoreleasing

__strong

ARC中, id及其他物件預設就是__strong修飾符修飾
MRC中, 使用__strong修飾符, 不必再次鍵入retain/release. 持有強引用的變數超出其作用域時被廢棄, 隨著強引用的失效, 引用的物件會隨之釋放.

__weak

解決迴圈引用問題. 並且弱引用的物件被廢棄時, 則次弱引用將自動失效並等於nil

通過__weak變數訪問物件實際上必定是訪問註冊到autoreleasepool的物件, 因為該修飾符只持有物件的弱引用, 在訪問物件的過程中, 該物件可能被廢棄, 如果把要訪問的物件註冊到autoreleasepool中, 那麼在block結束之前都能確保該物件存在.

__unsafe_unretained

不安全的修飾符, 附有該修飾符的變數不屬於編譯器的記憶體管理物件. 該修飾符與__weak一樣, 是弱引用, 並不能持有物件.並且訪問該修飾符的變數時如果不能確保其確實存在, 則應用程式會崩潰!

__autoreleasing

物件賦值給__autoreleasing修飾的變數相當於MRC下手動呼叫autorelease方法.可理解為, ARC下用@autoreleasepool block代替NSAutoreleasePool類, 用__autoreleasing修飾符的變數代替autorelease方法.
但是, 顯式使用__autoreleasing修飾符跟__strong一樣罕見,

ps : id的指標或者物件的指標會被隱式附上__autoreleasing修飾符, 如 :
id *obj == id __autoreleasing *obj;
NSObject **obj == NSObject * __autoreleasing *obj;

編譯器特性

編譯器會檢查方法名是否以alloc/new/copy/mutableCopy開始, 如果不是則自動將返回值物件註冊到autoreleasepool(init方法返回值物件不註冊到autoreleasepool), 詳情見下面ARC規則之<須遵循記憶體管理的方法命名規則>

5. ARC規則

  • 不能使用retain/release/retainCount/autorelease
  • 不能使用NSAllocateObject/NSDeallocateObject
  • 須遵循記憶體管理的方法命名規則
  • 不要顯式呼叫dealloc(即不能手動呼叫的dealloc方法, 可以過載)
  • 使用@autoreleasepool block代替NSAutoreleasePool
  • 不能使用區域(NSZone)
  • 物件型變數不能作為C語言結構體(struct/union)的成員
  • 顯式轉換”id”和”void *”

不能使用NSAllocateObject/NSDeallocateObject

alloc實現實際上是通過直接呼叫NSAllocateObject函式來生成並持有物件, ARC下禁止使用NSAllocateObject函式與NSDeallocateObject函式.

須遵循記憶體管理的方法命名規則

只有作為alloc/new/copy/mutableCopy方法的返回值取得物件,才能自己生成並持有物件, 其餘情況均為”取得非自己生成並持有的物件”..以下為ARC下編譯器偷偷幫我們實現的事.

Objective-C
1234567891011121314151617 +(Person<em>)newPerson{Person</em>person=[[Personalloc] init];returnperson;/* 該方法以new開始, 所以直接返回物件本身, 無需呼叫autorelease */}+(Person<em>)onePerson{Person</em>person=[[Personalloc] init];returnperson;/* 該方法不以alloc/new/copy/mutableCopy開始, 所以返回的是[person autorelease] */}-(void)doSomething{Person<em>personOne=[PersonnewPerson];// ...Person</em>personTwo=[PersononePerson];// .../*  當方法結束時, ARC會自動插入[personOne release]. &lt;想想是為什麼?&gt; */}

ARC下還有一條命名規則

  • init

以init名稱開始的方法必須是例項方法(物件方法), 並且要返回物件, 返回的物件不註冊到autoreleasepool上. 實際上只是對alloc方法返回值的物件做初始化處理並返回該物件.

不能使用區域(NSZone)

在現在的執行時系統中, NSZone已被忽視

顯式轉換 id 和 void *

  • __bridge轉換
  • __bridge_retained轉換
  • __bridge_transfer轉換

__bridge轉換

單純地轉換, 不安全.

Objective-C
123 idobj=[[NSObjectalloc] init];void*p=(__bridge void*)obj;ido=(__bridge id)p;

__bridge_retained轉換

可使要轉換賦值的變數也持有所賦值的物件, 即

Objective-C
123 idobj=[[NSObjectalloc] init];void*p=(__bridge_retained void*)obj;// 相當於加上 [(id)p retain];

則obj與p同時持有該物件

__bridge_transfer轉換

與__bridge_retained相反, 被轉換的變數所持有的物件在該變數被賦值給轉換目標變數後隨之釋放.

Objective-C
123 idobj=[[NSObjectalloc] init];void*p=(__bridge_transfer void*)obj;// 相當於加上 [(id)p retain]; [obj release];

小結

__ bridge_retained轉換與retain類似, __ bridge_transfer轉換與release類似. 該兩種轉換多用於Foundation物件與Core Foundation物件之間的轉換

屬性

Objective-C
1 @property(nonatomic,strong)NSString*name;

在ARC下, 以下可作為這種屬性宣告中使用的屬性來用.

屬性宣告的屬性 所有權修飾符
assign __unsafe_unretained修飾符
copy __strong修飾符(但是賦值的是被複制的物件)
retain __strong修飾符
strong __strong修飾符
unsafe_unretained __unsafe_unretained修飾符
weak __weak修飾符

以上各種屬性賦值給指定的屬性中就相當於賦值給附加各屬性對應的所有權修飾符的變數中.

6. ARC實現

ARC是由編譯器+執行時庫共同完成的.

__strong修飾符

Objective-C
1234567 {id__strong obj=[[NSObjectalloc] init];}可轉換為以下程式碼idobj=objc_msgSend(NSObject,@selector(alloc));objc_msgSend(obj,@selector(init));objc_release(obj);
Objective-C
1234567 {id__strong obj=[NSMutableArrayarray];}可轉換為以下程式碼idobj=objc_msgSend(NSMutableArray,@selector(array));objc_retainAutoreleasedReturnValue(obj);objc_release(obj);

objc_retainAutoreleasedReturnValue函式主要用於最優化程式執行. 它是用來持有返回註冊在autoreleasepool中物件的方法.這個函式是成對的, 另外一個objc_autoreleaseReturnValue函式則用於alloc/new/copy/mutableCopy方法以外的類方法返回物件的實現上, 如下 :

Objective-C
1234567891011 +(id)array{return[[NSMutableArrayalloc] init];}可轉換為以下程式碼+(id)array{idobj=objc_msgSend(NSMutableArray,@selector(alloc));objc_msgSend(obj,@selector(init));returnobjc_autoreleaseReturnValue(obj);}

那麼objc_autoreleaseReturnValue函式和objc_retainAutoreleasedReturnValue函式有什麼用?

省略autoreleasepool註冊

可以這樣來總結 :
如果呼叫autorelease之後又緊接著呼叫retain的話, 這兩部就顯得多餘, 所以以上兩個函式就發揮其作用了.

  • 用objc_autoreleaseReturnValue函式替代autorelease, 該函式檢測如果物件緊接著會呼叫retain, 他就不呼叫autorelease了(並設定一個標誌)
  • 用objc_retainAutoreleasedReturnValue函式來替代retain, 該函式會檢測物件的標誌是否被設定, 如被設定, 則不呼叫retain; 反之則呼叫retain方法.

通過這兩個函式能優化程式, 減少不必要的多餘的操作.

__weak修飾符

  • 若附有__weak修飾符的變數所引用的物件被廢棄, 則將nil賦值給該變數.
  • 使用附有__weak修飾符的變數, 即是使用註冊到autoreleasepool中的物件.
Objective-C
123456789101112 {id__weakobj1=obj;}可轉換為以下程式碼idobj1;objc_initWeak(&amp;obj1,obj);objc_destroyWeak(&amp;obj1);也可轉換為以下程式碼idobj1;obj1=0;objc_storeWeak(&amp;obj1,obj);objc_storeWeak(&amp;obj1,0);

訪問__weak變數時, 相當於訪問註冊到autoreleasepool的物件

Objective-C
123456789101112 {id__weakobj1=obj;NSLog(@"%@",obj1);}可轉換為以下程式碼idobj1;objc_initWeak(&amp;obj1,obj);idtmp=objc_loadWeakRetained(&amp;obj1);objc_autorelease(tmp);NSLog(@"%@",tmp);objc_destroyWeak(&amp;obj1)