[IOS學習]之五:引用計數

來源:互聯網
上載者:User

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.