arc automatic reference counting 記憶體管理中對引用採取自動計數。
apple官方文檔: 在oc中採用arc機制,讓編譯器來進行記憶體管理,在新一代apple llvm編譯器中設定arc為有效狀態,就無需再次鍵入retain或release代碼,降低程式崩潰,記憶體泄露等風險的同時,很大程度上減少了開發程式的工作量。編譯器完全清楚目標對象,並能立刻釋放那些不再被使用的對象。如此一來,應用程式將具有可預測性,並且能流程運行,運行速度也將大幅提升。
來說一下引用計數: 比如上班, 最早進入辦公室的人需要開燈,之後進入辦公室的人需要照明, 下班離開辦公室的人不需要照明,最後離開辦公室的人需要關燈。 這樣對應的引用計數就是:第一個人進入辦公室開燈,引用計數是1. 之後進入辦公室需要照明 引用計數是2 。 下班一個人離開辦公室 引用計數變成了1 最後一個離開了辦公室,引用計數變成了0 。
在記憶體管理中: 自己產生的對象,自己持有。 不是自己產生的對象,自己也能持有。 不再需要自己持有的對象就是放。 不是自己持有的對象無法釋放。 產生並持有對象:alloc/new/copy/mutableCopy 持有對象:retain 釋放對象: release 廢棄對象: dealloc 這些記憶體管理是在 cocoa架構 中的foundation架構類庫的NSObject類擔負的。
autorelease 是使對象在超出指定的生存範圍時能夠自動並正確地釋放。
我們在釋放對象的時候,不能釋放不是自己持有的對象。 ex:
//自己產生並持有對象id obj = [[NSObject alloc] init];//自己持有對象[obj release];//對象已釋放[obj release];//釋放之後再次釋放已非自己持有的對象,應用程式崩潰。 //崩潰情況: 再度廢棄已經廢棄了的對象時崩潰, 訪問已經廢棄的對象時崩潰我們取得對象,但是自己不持有對象://取得對象,但是自己並不持有對象id obj1 = [obj0 object];//釋放不是自己持有的對象,應用程式崩潰[obj1 release];
來說一個架構:GNUstep 他是和 cocoa架構的互換架構。他們的行為和實現方式是一樣的,相似的。 在gnusetp中,alloc的實現如下:
/*** 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()];} *
* 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); }
通過allocWithZone: 類方法調用NSAllocateObject函數指派至。
/** Now do the REAL version - using the other version to determine* what padding (if any) is required to get the alignment of the* structure correct.*/struct obj_layout { char padding[__BIGGEST_ALIGNMENT__ - ((UNP % __BIGGEST_ALIGNMENT__) ? (UNP % __BIGGEST_ALIGNMENT__) : __BIGGEST_ALIGNMENT__)]; NSUInteger retained;};inline idNSAllocateObject (Class aClass, NSUInteger extraBytes, NSZone *zone) { int size = 計算容納對象所需記憶體大小. id new = NSZoneMalloc(zone, size); memset(new, 0, size); new = (id) & ((struct obj_layout *)new)[1];}
這裡是通過NSZoneMalloc來分配存放對象所需的記憶體空間,之後將該記憶體空間置0 最後返回作為對象而是用的指標。
這裡的NSZone解釋一下: 是為了防止記憶體片段化而引入的結構,對記憶體配置的地區本身進行多重化管理,根據使用對象的目的、對象的大小分配記憶體,從而提高了記憶體管理的效率。
去掉NSZone的原始碼:
struct obj_layout { NSUInteger retained;};+ (id) alloc { int size = sizeof (struct obj_layout) + 對象大小; struct obj_layout *p = (struct obj_alyout*)calloc(1, size); return (id)(p + 1);}
這裡是用struct obj_layout中的retained整數來儲存引用計數,並將其寫入對象記憶體頭部。 對象記憶體塊 全部置0後返回。
通過retainCount來返回:
/*** Returns the reference count for the receiver. Each instance has an* implicit reference count of 1, and has an 'extra reference count'* returned by the NSExtraRefCount() function, so the value returned by* this method is always greater than zero.* By convention, objects which should (or can) never be deallocated* return the maximum unsigned integer value.*/- (NSUInteger) retainCount{#if GS_WITH_GC return UINT_MAX;#else return NSExtraRefCount(self) + 1;#endif}/*** Return the extra reference count of anObject (a value in the range* from 0 to the maximum unsigned integer value minus one).* The retain count for an object is this value plus one.*/inline NSUIntegerNSExtraRefCount(id anObject){#ifdef __OBJC_GC__ if (objc_collecting_enabled()) { return UINT_MAX-1; }#endif#if GS_WITH_GC return UINT_MAX - 1;#else /* GS_WITH_GC */ return ((obj)anObject)[-1].retained;#endif /* GS_WITH_GC */}
由對象定址找到對象記憶體頭部,從而訪問其中的retained變數。
retain方法:/*** Increments the reference count and returns the receiver.* The default implementation does this by calling NSIncrementExtraRefCount()*/- (id) retain{#if (GS_WITH_GC == 0) NSIncrementExtraRefCount(self);#endif return self;}/*** Increments the extra reference count for anObject.* The GNUstep version raises an exception if the reference count* would be incremented to too large a value.* This is used by the [NSObject-retain] method.*/inline voidNSIncrementExtraRefCount(id anObject)#endif{#if GS_WITH_GC || __OBJC_GC__ return;#else /* GS_WITH_GC */ if (allocationLock != 0) {#if defined(GSATOMICREAD) /* I've seen comments saying that some platforms only support up to * 24 bits in atomic locking, so raise an exception if we try to * go beyond 0xfffffe. */ if (GSAtomicIncrement((gsatomic_t)&(((obj)anObject)[-1].retained)) > 0xfffffe) { [NSException raise: NSInternalInconsistencyException format: @"NSIncrementExtraRefCount() asked to increment too far"]; }#else /* GSATOMICREAD */ NSLock *theLock = GSAllocationLockForObject(anObject); [theLock lock]; if (((obj)anObject)[-1].retained == UINT_MAX - 1) { [theLock unlock]; [NSException raise: NSInternalInconsistencyException format: @"NSIncrementExtraRefCount() asked to increment too far"]; } ((obj)anObject)[-1].retained++; [theLock unlock];#endif /* GSATOMICREAD */ } else { if (((obj)anObject)[-1].retained == UINT_MAX - 1) { [NSException raise: NSInternalInconsistencyException format: @"NSIncrementExtraRefCount() asked to increment too far"]; } ((obj)anObject)[-1].retained++; }#endif /* GS_WITH_GC */}
release的實現:
/*** Decrements the retain count for the receiver if greater than zero,* otherwise calls the dealloc method instead.* The default implementation calls the NSDecrementExtraRefCountWasZero()* function to test the extra reference count for the receiver (and* decrement it if non-zero) - if the extra reference count is zero then* the retain count is one, and the dealloc method is called.* In GNUstep, the [NSObject+enableDoubleReleaseCheck:] method may be used* to turn on checking for ratain/release errors in this method.*/- (oneway void) release{#if (GS_WITH_GC == 0) if (NSDecrementExtraRefCountWasZero(self)) {# ifdef OBJC_CAP_ARC objc_delete_weak_refs(self);# endif [self dealloc]; }#endif}BOOL NSDecrementExtraRefCountWasZero(id anObject) { if (((struct obj_layout *)anObject)[-1].retained == 0) { return YES; } else { ((struct obj_layout *)anObject)[-1].retained--; return NO; }}
dealloc實現:
*
* If you have allocated the memory using a non-standard mechanism, you * will not call the superclass (NSObject) implementation of the method * as you will need to handle the deallocation specially.
* In some circumstances, an object may wish to prevent itself from * being deallocated, it can do this simply be refraining from calling * the superclass implementation. *
*/ - (void) dealloc { NSDeallocateObject (self); } inline void NSDeallocateObject(id anObject) { struct obj_layout *o = &((struct obj_layout*)anObject)[-1]; free(o); }
在oc的對象中存有引用計數這一整數值。
調用alloc或者retain方法後,引用計數+1;
調用release後,引用計數-1;
引用計數值為0時,調用dealloc方法廢棄對象。
分析其cocoa的實現: 在NSObject的alloc上下斷點,可以看到調用函數: +alloc +allocWithZone: class_createInstance calloc 這裡alloc類方法首先調用allocWithZone:類方法, 跟GNUstep相同。然後調用class_createInstance函數。最後用calloc來分配記憶體。 retainCount: -retainCount __CFDoExternRefOperation CFBasicHashGetCountOfKey
retain: -retain __CFDoExternRefOperation CFBasicHashAddValue
release: -release __CFDoExternRefOperation CFBasicHashRemoveValue (CFBasicHashRemoveValue返回0時, -release調用dealloc)
int __CFDoExternRefOperation(uintptr_t op, id obj) { CFBasicHashRef table = init; int count; switch (op) { case OPERATION_retainCount; count = CFBasicHashGetCountOfKey(table, obj); return count; case OPERATION_retain: CFBasicHashAddValue(table, obj); return obj; case OPERATION_release: count = CFBasicHashRemoveValue(table, obj); return 0 == count; }}- (NSUInteger)retainCount { return (NSUInteger) __CFDoExternRefOperation(OPERATION_retainCount, self);}- (id)retain { return (id)__CFDoExternRefOperation(OPERATION_retain, self);}- (void)release { return __CFDoExternRefOperation(OPERATION_release, self);}
從函數中看出,apple用的是hash來管理引用計數。
來說一下兩種記憶體管理:
1、通過記憶體塊頭部管理引用計數好處:
少量代碼就能完成。
能夠統一管理引用計數用記憶體塊與對象用記憶體塊。
2、通過引用計數表管理引用計數好處:
對象用記憶體塊的分配無需考慮記憶體塊頭部。
引用計數表各項記錄中存有記憶體塊地址,可從各個記錄追溯到各個對象的記憶體塊。
在第二條中,追溯到記憶體塊 在調試中是很重要的。只要引用計數表沒有被破壞就能找到記憶體塊的位置。
autorelease的實現: 類比c的範圍概念。
使用方法:
1、產生並持有NSAutoreleasePool對象。
2、調用已指派對象的autorelease執行個體方法。
3、廢棄NSAutoreleasePool對象。
在cocoa架構中,程式主迴圈的NSRunLoop或者在其他程式可啟動並執行地方,對NSAutoreleasePool對象進行產生、持有和廢棄處理。 當我們大量產生autorelease對象時,只要不廢棄NSAutoreleasePool對象,那麼產生的對象就不能被釋放。有時候會產生記憶體不足的情況。 我們可以在必要的地方持有,廢棄:
for (int i = 0; i < count; ++i) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; *** [pool drain];}
在NSObject中,aurelease是這樣運作的:
- (id) autorelease { [NSAutoreleasePool addObject:self];}
addObject 是將對象串連上去,即GNUstep使用的是串連列表。 如NSMutableArray也是一樣的。
apple對autorelea的實現:
class AutoreleasePoolPage { static inline void *push() { 產生或持有NSAutoreleasePool類對象。 } static inline void *pop(void *token) { 廢棄NSAutoreleasePool類對象; releaseAll(); } static inline id autorelease(id obj) { 相當於NSAutoreleasePool類的addObject類方法。 } id *add(id obj) { 追加; } void releaseAll() { 調用內部數組中的對象的release執行個體方法。 }}void *objc_autoreleasePoolPush(void) { return AutoreleasePoolPage::push();}void objc_autoreleasePoolPop(void *ctxt) { AutoreleasePoolPage:pop(ctxt);}id *obj_autorelease( id obj) { return AutoreleasePoolPage::autorelease(obj);}
要注意的是當我們 pool autorelease會怎樣? 這樣做會發生異常, 在ob中,也就是foundation架構,無論調用那個對象的autorelease執行個體方法,實現上是調用的都是NSObject類的autorelease執行個體方法。 但是對於NSAutoreleasePool類,autorelease執行個體方法已經被該類重載了,所以出現了錯誤。
最後: 如何提高調用oc方法的速度 gnustep中,autorelease是用IMP(函數指標) Caching來實現的,他能高效地運行os x,ios應用程式頻繁調用autorelease方法。 在方法調用時,為瞭解決類名、方法名以及取得方法運行時的函數指標,要在架構初始化時對其結果值進行緩衝。
id autorelease_class [NSAutoreleasePool class];SEL autorelease_sel = @selector(addObject:);IMP autorelease_imp = [autorelease_class methodForSelector:autorelease_sel];實際:- (id)autorelease { (*autorelease_imp)(autorelease_class, autorelease_sel, self);}與- (id)autorelease { [NSAutoreleasePool addObject:self];}作用相同,但是第一種方法運行效率會快2倍。 但是他依賴於運行環境。
-----2014、3、14 beijing