你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> [IOS學習]之五:引用計數

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

編輯:IOS開發綜合
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 id
NSAllocateObject (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 NSUInteger
NSExtraRefCount(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 void
NSIncrementExtraRefCount(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
  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved