本文來自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(垃圾回收);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消息銷毀對象。對象操作與Objective-C方法的對應
(1)、自己生成的對象,自己持有
①、alloc
id obj = [[NSObject alloc]init];
②、new
id obj = [NSObject new];
作用同①。
③、copy/mutableCopycopy方法利用基於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];
GNUstep是Cocoa框架的互換框架。
下面是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來創建使用@autoreleasepool創建NSAutoreleasePool * pool = [[NSAutoreleasePool alloc]init]; //這裡寫代碼 [pool release];
@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類型或各種對象類型。
__strong __weak __unsage_unretained __autoreleasing
所謂對象類型就是指向NSObject這樣的Objective-C類的指針,例如”NSObject “。id類型用於隱藏對象類型的類名部分,相當於C語言中常用的”void “。
ARC有效時,id類型和對象類型同C語言其他類型不同,其類型上必須附加所有權修飾符,所有權修飾符一共有4種:(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表中刪除。