你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> 自動引用計數

自動引用計數

編輯:IOS開發綜合

本文來自Objective-C高級編程(iOS與OS X多線程和內存管理)

一、什麼是自動引用計數

自動引用計數(ARC,Automatic Reference Counting)是指內存管理中對引用采取自動計數的技術;蘋果官方說明:

在Objective-C中采用Automatic Reference Counting(ARC)機制,讓編譯器來進行內存管理。在新一代Apple LLVM編譯器中設置ARC為有效狀態,就無需再次鍵入retain或者release代碼,這就降低程序崩潰、內存洩露等風險的同時,很大程度上減少了開發程序的工作量。編譯器完全清除目標對象,並能立刻釋放那些不再被使用的對象。如此一來,引用程序具有可預防性,且能流暢運行,速度也將大幅提升。

ARC使用條件:

使用Xcode 4.2或以上版本; 使用LLVM編譯器3.0或以上版本; 編譯器選項中設置ARC為有效;

二、內存管理/引用計數

Objective-C提供了三種內存管理方式:

①、manual retain-release(MRR,手動管理); ②、automatic reference counting(ARC,自動引用計數); ③、garbage collection(垃圾回收);

1、概要

ObjC采用引用計數(reference counting)的技術來進行管理:

1)每個對象都有一個關聯的整數,稱為引用計數器; 2)當代碼需要使用該對象時,則將對象的引用計數加1; 3)當代碼結束使用該對象時,則將對象的引用計數減1; 4)當引用計數的值變為0時,表示對象沒有被任何代碼使用,此時對象將被釋放。

與之對應的消息發送方法如下:

1)當對象被創建(通過alloc、new或copy等方法)時,其引用計數初始值為1; 2)給對象發送retain消息,其引用計數加1; 3)給對象發送release消息,其引用計數減1; 4)當對象引用計數歸0時,ObjC給對象發送dealloc消息銷毀對象。

2、內存管理的思考方式

自己生成的對象,自己持有; 非自己生成的對象,自己也能持有; 不再需要自己持有的對象時釋放; 非自己持有的對象無法釋放。

對象操作與Objective-C方法的對應

對象操作 Objective-C方法 生成並持有對象 alloc/new/copy/mutableCopy 持有對象 retain方法 釋放對象 release方法 廢棄對象 dealloc方法

(1)、自己生成的對象,自己持有

①、alloc
id obj = [[NSObject alloc]init];
②、new
id obj = [NSObject new];

作用同①。

③、copy/mutableCopy

copy方法利用基於NSCopying方法約定,由各類實現的copyWithZone:方法生成並持有對象的副本。與copy方法類似,mutableCopy方法利用基於NSMutableCopying方法約定,由各類實現mutableCopyWithZone:方法生成並持有對象的副本。兩者的區別在於,copy方法生成不可變更的對象,而mutableCopy方法生成可變更對象。

(2)、非自己生成的對象,自己也能持有

//去的非自己生成並持有的對象
id obj = [NSMutableArray array];
//取得的對象存在,但自己不持有對象
[obj retain];
//自己持有對象

(3)、不再需要自己持有的對象時釋放

//自己生成並持有對象
id obj = [[NSMutableArray alloc]init];
//自己持有對象
[obj release];
//釋放對象

(4)、非自己持有的對象無法釋放

//取得的對象存在,但自己不持有對象
id obj = [NSMutableArray array];
//釋放對象導致程序崩潰
[obj release];

3、alloc/retain/release/dealloc實現

GNUstep是Cocoa框架的互換框架。

(1)、alloc

下面是GNUstep類中alloc類方法在NSObject.m源代碼中的實現;

/**
 * Allocates a new instance of the receiver from the default
 * zone, by invoking +allocWithZone: with
 * NSDefaultMallocZone() as the zone argument.

 * Returns the created instance.
 */
+ (id) alloc
{
  return [self allocWithZone: NSDefaultMallocZone()];
}

/**
 * This is the basic method to create a new instance.  It
 * allocates a new instance of the receiver from the specified
 * memory zone.
 * 

* Memory for an instance of the receiver is allocated; a * pointer to this newly created instance is returned. All * instance variables are set to 0. No initialization of the * instance is performed apart from setup to be an instance of * the correct class: it is your responsibility to initialize the * instance by calling an appropriate init * method. If you are not using the garbage collector, it is * also your responsibility to make sure the returned * instance is destroyed when you finish using it, by calling * the release method to destroy the instance * directly, or by using autorelease and * autorelease pools. *

*

* You do not normally need to override this method in * subclasses, unless you are implementing a class which for * some reasons silently allocates instances of another class * (this is typically needed to implement class clusters and * similar design schemes). *

*

* If you have turned on debugging of object allocation (by * calling the GSDebugAllocationActive * function), this method will also update the various * debugging counts and monitors of allocated objects, which * you can access using the GSDebugAllocation... * functions. *

*/ + (id) allocWithZone: (NSZone*)z { return NSAllocateObject (self, 0, z); }

NSAllocateObject函數:

NSAllocateObject (Class aClass, NSUInteger extraBytes, NSZone *zone)
#endif
{
  id    new;
  int   size;

  NSCAssert((!class_isMetaClass(aClass)), @"Bad class for new object");
  size = class_getInstanceSize(aClass) + extraBytes + sizeof(struct obj_layout);
  if (zone == 0)
    {
      zone = NSDefaultMallocZone();
    }
  new = NSZoneMalloc(zone, size);
  if (new != nil)
    {
      memset (new, 0, size);
      new = (id)&((obj)new)[1];
      object_setClass(new, aClass);
      AADD(aClass, new);
    }

  /* Don't bother doing this in a thread-safe way, because the cost of locking
   * will be a lot more than the cost of doing the same call in two threads.
   * The returned selector will persist and the runtime will ensure that both
   * calls return the same selector, so we don't need to bother doing it
   * ourselves.
   */
  if (0 == cxx_construct)
    {
      cxx_construct = sel_registerName(".cxx_construct");
      cxx_destruct = sel_registerName(".cxx_destruct");
    }
  callCXXConstructors(aClass, new);

  return new;
}

NSAllocateObject函數通過調用NSZoneMalloc函數來分配存放對象所需的內存空間,之後將內存空間置0,最後返回作為對象而使用的指針。

注:NSZone:它是防止內存碎片而引入的結構。對內存分配的區域本身進行多重化管理,根據使用對象的目的、對象的大小分配內存,從而提高了內存管理的效率;
這裡寫圖片描述
alloc方法調用struct obj_layout中的retained整數來保存引用計數,並將其寫入對象內存頭部,該對象內存塊全部置0後返回。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjwvYmxvY2txdW90ZT4NCjxwcmUgY2xhc3M9"brush:java;"> struct obj_layout { char padding[__BIGGEST_ALIGNMENT__ - ((UNP % __BIGGEST_ALIGNMENT__) ? (UNP % __BIGGEST_ALIGNMENT__) : __BIGGEST_ALIGNMENT__)]; NSUInteger retained; }; typedef struct obj_layout *obj;

4、自動釋放池(autorelease)

autorelease就是自動釋放,當給一個對象發送autorelease消息時,方法會在未來某個時間給這個對象發送release消息將其釋放,在這個時間段內,對象還是可以使用的。
(1)、原理
對象接收到autorelease消息時,它會被添加到了當前的自動釋放池中,當自動釋放池被銷毀時,會給池裡所有的對象發送release消息。
(2)、使用方法

①、生成並持有NSAutoreleasePool對象;

使用NSAutoreleasePool來創建
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc]init];
//這裡寫代碼
[pool release];
使用@autoreleasepool創建
@autoreleasepool {
     //這裡寫代碼
}

②、調用已分配對象的aurorelease實例方法;
③、廢棄NSAutoreleasePool對象;
這裡寫圖片描述

NSAutoreleasePool對象的生命周期相當於C語言變量的作用域。對於所有調用autorelease實例方法的對象,在廢棄NSAutoreleasePool對象時,都將調用release實例方法。

在cocoa框架中,程序主循環的NSRunLoop或者在其他程序可運行的地方,對NSAutoreleasePool對象進行生成、持有和廢棄處理。
當我們大量產生autorelease對象時,只要不廢棄NSAutoreleasePool對象,那麼生成的對象就不能被釋放。有時候會產生內存不足的情況。
我們可以在必要的地方持有,廢棄:

for (int i = 0; i < count; ++i) {  
     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];  
     ***  
     [pool drain];  
}  

三、ARC規則

1、概要

(1)、設置ARC有效的編譯方法如下:
指定編譯器屬性為:”-fobjc-arc”。
(2)、設置ARC無效的編譯方法如下:
指定編譯器屬性為:”-fno-objc-arc”。

2、內存管理的思考方式

自己生成的對象,自己所持有 非自己生成的對象,自己也能持有 自己持有的對象不再需要時釋放 非自己持有的對象無法釋放

3、所有權修飾符

Objective-C編程中為了處理對象,可將變量類型定義為id類型或各種對象類型。
所謂對象類型就是指向NSObject這樣的Objective-C類的指針,例如”NSObject “。id類型用於隱藏對象類型的類名部分,相當於C語言中常用的”void “。
ARC有效時,id類型和對象類型同C語言其他類型不同,其類型上必須附加所有權修飾符,所有權修飾符一共有4種:

__strong __weak __unsage_unretained __autoreleasing

(1)、__strong修飾符

___strong修飾符是id類型和對象類型默認的所有權修飾符。 也就是說 代碼中的id變量實際上被附加了所有權修飾符。
id和對象類型在沒有明確指定所有權修飾符的時候,默認是strong類型的:

//等價
id obj = [[NSObject alloc]init];
id __strong obj = [[NSObject alloc]init];

__strong修飾符的變量之間可以相互賦值。
__strong修飾符的變量不僅在變量作用域中,在賦值上也能夠正確地管理其對象的所有者。

(2)、__weak修飾詞

在引用計數的時候會產生 循環引用 的問題。
這裡寫圖片描述
這裡寫圖片描述
上述兩種情況都會產生循環引用;

__weak與strong相反,提供弱引用,他不能持有對象實例。
我們為了不以自己持有的狀態來保存自己生成並持有的對象,生成的對象會立即被釋放。

{
     id __strong obj1 = [[NSObject alloc] init];
     id __weak obj2 = obj1;
}

__weak還有一個優點,在持有某對象的弱引用時,若該對象被拋棄,則此弱引用將自動失效,並處於nil被賦值的狀態(空弱應用)。

(3)、__unsafe_unretained修飾符

他是不安全的所有權修飾符。附有__unsafe_unretained修飾符的變量不屬於編譯器內存管理對象。
他跟__weak一樣,因為自己生成並持有的對象不能繼續為自己所有,所以生成的對象會立即被釋放。

(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塊結束之前都能確保該對象存在;

id的指針或對象的指針在沒有顯示指定時會被附加上__autoreleasing修飾詞,例如:

NSObject* *obj;-->NSObject* __autoreleasing*obj;
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;

4、規則

在ARC有效的情況下編譯源代碼,必須遵守一定的規則。

不能使用retain/release/retainCount/autorelease 不能使用NSAllocateObject/NSDeallocateObject 須遵守內存管理的方法命名規則 不要顯式調用dealloc 使用@autoreleasepool塊代替NSAutoreleasePool 不能使用區域(NSZone) 對象型變量不能作為C語言結構體(struct/union)的成員 顯式轉換”id”和”void *”

id型或對象型變量賦值給void *或者逆向賦值都需要進行特定的轉換。如果單純地賦值,則可以使用”__bridge轉換”

id obj = ------
void *p = (__bridge void*)obj;
id o = (__bridge id)p;

但是轉換為void *的__bridge轉換,其安全性與賦值給__unsafe_unretained修飾符相近,甚至會更低。如果管理時不注意賦值對象的所有者,就會因懸掛指針而導致程序崩潰。

__bridge有兩種類型,分別是:__bridge_retianed 和 __bridge_transfer。 他們類似於 retian和release

5、屬性

當ARC有效時,以下可作為這種屬性聲明中使用的屬性來用:

屬性聲明的屬性 所有權修飾符 assign __unsafe_unretained修飾符 copy __strong修飾符(但是賦值的是被復制的對象) retain __strong修飾符 strong __strong修飾符 unsafe_unretained __unsafe_unretained修飾符 weak __weak修飾符

四、ARC的實現

1、__strong修飾符

最優化問題。

id __strong obj = [NSMutableArray array];

他在編譯器中會出現模擬代碼 如下:

id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(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_retainAutoreleasedReturnValue函數, 那麼將不會將返回的對象注冊到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表中刪除。

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved