Objective-C 内存管理之alloc/retain/release/dealloc实现原理

GNUstep开源框架的实现

以GNUstep开源框架为例(与Foundation框架相类似)

/**
 * Allocates a new instance of the receiver from the default
 * zone, by invoking +allocWithZone: with
 * <code>NSDefaultMallocZone()</code> as the zone argument.<br />
 * 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.
 *
 */
+ (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__)];
  gsrefcount_t	retained;
};
typedef	struct obj_layout *obj;


/*
 *	Now do conditional compilation of memory allocation functions
 *	depending on what information (if any) we are storing before
 *	the start of each object.
 */

inline id
NSAllocateObject (Class aClass, NSUInteger extraBytes, NSZone *zone)
{
  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是为防止内存碎片化而引入的结构。对内存分配的区域本身进行多重化管理,根据使用对象的目的、对象的大小分配内存、从而提高了内存管理的效率。但是苹果官方ARC文档中指出,现在的运行时系统只是简单地忽略了区域的概念。运行时系统中的内存管理本身已极具效率,使用区域来管理内存反而会引起内存使用效率低下以及源代码复杂化等问题。

alloc类方法用struct obj_layout中的retained来保存引用计数,并将其写入对象的内存头部,该内存块全部置0后返回。

注:置0个人理解为通过调用malloc方法,分配一个size为指定大小的内存块后进行初始化,相当于calloc(1,size)动作。

对象的引用计数可以通过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.<br />
* By convention, objects which should (or can) never be deallocated
* return the maximum unsigned integer value.
*/
- (NSUInteger) retainCount
{
 return NSExtraRefCount(self) + 1;
}

/**
* Return the extra reference count of anObject (a value in the range
* from 0 to the maximum unsigned integer value minus one).<br />
* The retain count for an object is this value plus one.
*/
inline NSUInteger
NSExtraRefCount(id anObject)
{
 return ((obj)anObject)[-1].retained;
}
复制代码

因为分配时全部置0,所以retained为0。由NSExtraRefCount(self) + 1得出,retainCount为1.可以推测出,retain方法使retained变量+1,而release方法使retained变量-1。

实现代码如下:

/**
* Increments the reference count and returns the receiver.<br />
* The default implementation does this by calling NSIncrementExtraRefCount()
*/
- (id) retain
{
 NSIncrementExtraRefCount(self);
 return self;
}

/**
* Increments the extra reference count for anObject.<br />
* The GNUstep version raises an exception if the reference count
* would be incremented to too large a value.<br />
* This is used by the [NSObject-retain] method.
*/
inline void
NSIncrementExtraRefCount(id anObject)
{
#if	defined(GSATOMICREAD)
 /* I have 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 > 0xfffffe)
   {
     [theLock unlock];
     [NSException raise: NSInternalInconsistencyException
       format: @"NSIncrementExtraRefCount() asked to increment too far"];
   }
 ((obj)anObject)[-1].retained++;
 [theLock unlock];
#endif	/* GSATOMICREAD */
}


复制代码
/**
* Decrements the retain count for the receiver if greater than zero,
* otherwise calls the dealloc method instead.<br />
* 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.<br />
* In GNUstep, the [NSObject+enableDoubleReleaseCheck:] method may be used
* to turn on checking for ratain/release errors in this method.
*/
- (oneway void) release
{
 if (NSDecrementExtraRefCountWasZero(self))
   {
#  ifdef OBJC_CAP_ARC
     objc_delete_weak_refs(self);
#  endif
     [self dealloc];
   }
}




/**
* Examines the extra reference count for the object and, if non-zero
* decrements it, otherwise leaves it unchanged.<br />
* Returns a flag to say whether the count was zero
* (and hence whether the extra reference count was decremented).<br />
* This function is used by the [NSObject-release] method.
*/
inline BOOL
NSDecrementExtraRefCountWasZero(id anObject)
{
 if (double_release_check_enabled)
   {
     NSUInteger release_count;
     NSUInteger retain_count = [anObject retainCount];
     release_count = [autorelease_class autoreleaseCountForObject: anObject];
     if (release_count >= retain_count)
       [NSException raise: NSGenericException
   	    format: @"Release would release object too many times."];
   }
 {
#if	defined(GSATOMICREAD)
   gsrefcount_t	result;

   result = GSAtomicDecrement((gsatomic_t)&(((obj)anObject)[-1].retained));
   if (result < 0)
     {
       if (result != -1)
         {
           [NSException raise: NSInternalInconsistencyException
             format: @"NSDecrementExtraRefCount() decremented too far"];
         }
       /* The counter has become negative so it must have been zero.
        * We reset it and return YES ... in a correctly operating
        * process we know we can safely reset back to zero without
        * worrying about atomicity, since there can be no other
        * thread accessing the object (or its reference count would
        * have been greater than zero)
        */
       (((obj)anObject)[-1].retained) = 0;
       return YES;
     }
#else	/* GSATOMICREAD */
   NSLock *theLock = GSAllocationLockForObject(anObject);

   [theLock lock];
   if (((obj)anObject)[-1].retained == 0)
     {
       [theLock unlock];
       return YES;
     }
   else
     {
       ((obj)anObject)[-1].retained--;
       [theLock unlock];
       return NO;
     }
#endif	/* GSATOMICREAD */
 }
 return NO;
}
复制代码
- (void) dealloc
{
  NSDeallocateObject (self);
}


inline void
NSDeallocateObject(id anObject)
{
  Class aClass = object_getClass(anObject);

  if ((anObject != nil) && !class_isMetaClass(aClass))
    {
      obj	o = &((obj)anObject)[-1];
      NSZone	*z = NSZoneFromPointer(o);

      /* Call the default finalizer to handle C++ destructors.
       */
      (*finalize_imp)(anObject, finalize_sel);

      AREM(aClass, (id)anObject);
      if (NSZombieEnabled == YES)
	{
	  GSMakeZombie(anObject, aClass);
	  if (NSDeallocateZombies == YES)
	    {
	      NSZoneFree(z, o);
	    }
	}
      else
	{
	  object_setClass((id)anObject, (Class)(void*)0xdeadface);
	  NSZoneFree(z, o);
	}
    }
  return;
}

复制代码

总结上述代码的逻辑为:

  • 在OC的对象中存有引用计数这一整数值。
  • 调用alloc或是retain方法后,引用计数值加1。
  • 调用release后,引用计数值-1。
  • 引用计数值为0后,调用dealloc方法废弃对象。

Foundation框架的实现

在NSOject类的alloc类方法上设置断点,追踪程序的执行。

以下列出了执行所调用的方法和函数

  • +alloc
  • +allocWithZone:
  • class_createInstance
  • calloc

alloc类方法首先调用allocWithZone:类方法,然后调用class_createInstance函数,最后通过调用calloc来分配内存块。

retainCount方法的执行:

  • -ratainCount
  • __CFDoExternRefOperation
  • CFBasicHashGetCountOfKey

retain方法的执行:

  • -ratain
  • __CFDoExternRefOperation
  • CFBasicHashAddValue

release方法的执行:

  • -release
  • __CFDoExternRefOperation
  • CFBasicHashRemoveValue(CFBasicHashRemoveValue返回为0时,-release调用dealloc)

apple通过保存引用计数表(散列表)来实现。

GNUstep将引用计数保存在内存快头部的优点:

  • 少量代码即可完成。
  • 能够统一管理引用计数内存块与对象用内存块。

苹果引用计数表保存的优点:

  • 对象用内存块的分配无需考虑内存块头部。
  • 引用计数表各记录中存由内存块地址,可从各个记录追溯到个对象的内存块。(即使出现故障导致对象占用的内存块损坏,但是只要引用计数表没有被破坏,依然可以确认各内存块的位置)。