专栏名称: 唤之
目录
相关文章推荐
程序员小灰  ·  DeepSeek做AI代写,彻底爆了! ·  3 天前  
OSC开源社区  ·  李彦宏:DeepSeek让我们明白要将最优秀 ... ·  3 天前  
程序猿  ·  TCP 才不傻! ·  4 天前  
程序员小灰  ·  DeepSeek创始人梁文峰牛逼的个人经历 ·  3 天前  
51好读  ›  专栏  ›  唤之

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

唤之  · 掘金  · 程序员  · 2017-12-08 08:26

正文

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






请到「今天看啥」查看全文