alloc/retain/release/dealloc的底层实现(上)

Posted Haley_Wong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了alloc/retain/release/dealloc的底层实现(上)相关的知识,希望对你有一定的参考价值。

要理解底层实现,需要用到如下开源代码。

下载GNUstep的地址:GNUstep Core

Runtime源码objc4-750:Runtime源码objc4-750

GNUstep就是使用早期的Objective-C来实现的,通过其开源的Foundation框架能否一窥NSObject的实现。

为了便于理解,还是先看下GNUstep中的实现,因为GNUstep中的实现会简单一点,易于理解。

GNUstep 中的alloc/retain/release/dealloc的底层实现

因为GNUstep是开源的,所以我们先来看一下GNUstep中的源码实现。即使苹果现在的实现有变更,也是在其基础上,所以GNUstep的实现,依然有参考意义。
截止到目前为止,GNUstep最近的更新是2019-01-07呢,说明这并不是一个无人维护的开源项目!!!

1.alloc的底层实现(GNUstep)

通过GNUstep中的【Source/Foundation/NSObject.m】源码来看:

+ (id) alloc

  return [self allocWithZone: NSDefaultMallocZone()];


+ (id) allocWithZone: (NSZone*)z

  return NSAllocateObject(self, 0, z);


inline id
NSAllocateObject(Class aClass, NSUInteger extraBytes, NSZone *zone)

  id	new;

#ifdef OBJC_CAP_ARC
  if ((new = class_createInstance(aClass, extraBytes)) != nil)
    
      AADD(aClass, new);
    
#else
  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);
    

  if (0 == cxx_construct)
    
      cxx_construct = sel_registerName(".cxx_construct");
      cxx_destruct = sel_registerName(".cxx_destruct");
    
  callCXXConstructors(aClass, new);
#endif

  return new;

1.1 NSZone 的内部结构

这里先简单看下NSDefaultMallocZone()这个函数,该函数返回一个zone指针对象。

NSZone*
NSDefaultMallocZone (void)

  return &default_zone;

而这个default_zone其实是个静态全局变量:

static NSZone default_zone =

  default_malloc, default_realloc, default_free, default_recycle,
  default_check, default_lookup, default_stats, 0, @"default", 0
;

NSZone其实是对_NSZone 结构体重命名而已,其内部结构如下:

struct _NSZone

  /* Functions for zone. */
  void *(*malloc)(struct _NSZone *zone, size_t size);
  void *(*realloc)(struct _NSZone *zone, void *ptr, size_t size);
  void (*free)(struct _NSZone *zone, void *ptr);
  void (*recycle)(struct _NSZone *zone);
  BOOL (*check)(struct _NSZone *zone);
  BOOL (*lookup)(struct _NSZone *zone, void *ptr);
  struct NSZoneStats (*stats)(struct _NSZone *zone);
  
  size_t gran; // Zone granularity
  __unsafe_unretained NSString *name; // Name of zone (default is 'nil')
  NSZone *next;
;

其实就是包含一堆函数指针的一个结构体而已(大部分是函数的指针,少部分是一些变量)。

1.2 NSAllocateObject 函数分析

这里,就重点对NSAllocateObject函数来分析一下。
先来将NSAllocateObject()函数简化一下,其中obj_layout其实是这样一个结构体:

struct obj_layout 
  char	padding[__BIGGEST_ALIGNMENT__ - ((UNP % __BIGGEST_ALIGNMENT__)
    ? (UNP % __BIGGEST_ALIGNMENT__) : __BIGGEST_ALIGNMENT__)];
  gsrefcount_t	 retained;
;
typedef	struct obj_layout *obj;

这里的gsrefcount_t其实就是long类型的别名(在i386架构下)。

typedef intptr_t gsrefcount_t;
typedef __darwin_intptr_t	intptr_t;
typedef long  __darwin_intptr_t;

所以,retained 就是个整形的值而已。

inline id
NSAllocateObject(Class aClass, NSUInteger extraBytes, NSZone *zone)

    // 计算对象所需要的内存大小
    int size = class_getInstanceSize(aClass) + extraBytes + sizeof(struct obj_layout);
    // 利用zone来管理内存,并为对象分配内存
    id new = NSZoneMalloc(zone, size);
    memset (new, 0, size);
    
    new = (id)&((obj_layout *)new)[1];
    // 为对象实例设置class
    object_setClass(new, aClass);
    
    return new;

NSZone 是为了防止内存碎片化而引起的结构。对内存分配的区域本身进行多重化管理,根据使用对象的目的、对象的大小分配内存,从而提高内存管理的效率。
但是现在的运行时系统中的内存管理本身已经非常的高效,使用NSZone来管理内存反而会引起内存使用效率低下以及源代码复杂化等问题。所以现在的运行时其实并没有用到NSZone。

NSZoneMalloc()内部其实调用的即使这个函数,可见zone并没有被用到:

static void*
default_malloc (NSZone *zone, size_t size)

  void *mem;

  mem = malloc(size);
  if (mem != NULL)
    
      return mem;
    
  [NSException raise: NSMallocException
              format: @"Default zone has run out of memory"];
  return 0;

从以上代码,可以看出 alloc类方法用 struct obj_layout中的retained整数来保存引用计数,并将其写入对象内存头部。这里是先将对象用内存块全部置为0后,然后将引用计数写入对象内存头部,然后将obj_layout的地址加1,就得到了对象的地址,然后返回该地址。

2. retainCount的底层实现(GNUstep)

我们可以使用对象的 retainCount实例方法获取对象的引用计数:

HLPerson *person = [[HLPerson alloc] init];
NSLog(@"retainCount = %lu", (unsigned long)[person retainCount]);

//打印结果
retainCount = 1

而GNUstep中的实现是这样的:

- (NSUInteger) retainCount

    return getRetainCount(self);


size_t getRetainCount(id anObject)

    // 已去掉了ARC下weak支持相关的代码
    return object_getRetainCount_np_internal(anObject);


size_t object_getRetainCount_np_internal(id anObject)

    return ((obj)anObject)[-1].retained + 1;

这里从对象地址 减1,找到内部 struct obj_layout的指针地址,然后就可以获取到内部保存的retained变量。

而因为retained变量,默认为0,所以 由 retained + 1,就获取到对象的引用计数1。

3. retain的底层实现(GNUstep)

我们都知道retain会使对象的引用计数增加,接下来看看是如何让引用计数增加的:

// Increments the reference count and returns the receiver
- (id) retain

  return retain_fast(self);


static id retain_fast(id anObject)

    // 已去掉arc相关的逻辑处理
    return objc_retain_fast_np_internal(anObject);


static id objc_retain_fast_np_internal(id anObject)

    // 已去掉一些无关的逻辑
    
    // 先创建一个锁
    pthread_mutex_t *theLock = GSAllocationLockForObject(anObject);
    
    // 在读取引用计数前锁住
    pthread_mutex_lock(theLock);
    // 如果引用计数大于某个值,则做个标记后面特殊处理
    if (((obj)anObject)[-1].retained > 0xfffffe)
    
        tooFar = YES;
    
    else
    
        // 没有大于阙值,就将对象内部 struct obj_layout中的引用计数 +1。
        ((obj)anObject)[-1].retained++;
    
    pthread_mutex_unlock(theLock);
    return anObject;

可以看出这里的引用计数的增加,其实就是将对象顶部的的obj_layout中的retained 做++操作。

注意这里的objstruct obj_layout的别名。

4.release的底层实现(GNUstep)

release会使对象的引用计数减1,那这里猜想一下,release的实现,应该也是将obj_layout中retained 做–操作。

- (oneway void) release

  release_fast(self);


static void release_fast(id anObject)

    // 已去掉arc相关的逻辑
    objc_release_fast_np_internal(anObject);


static void objc_release_fast_np_internal(id anObject)

  if (release_fast_no_destroy(anObject))
    
      [anObject dealloc];
    


static BOOL objc_release_fast_no_destroy_internal(id anObject)

    // 已删掉一些不太相关的逻辑代码
    pthread_mutex_t *theLock = GSAllocationLockForObject(anObject);
    
    pthread_mutex_lock(theLock);
    if (((obj)anObject)[-1].retained == 0)
    
        pthread_mutex_unlock(theLock);
        return YES;
    
    else
    
        ((obj)anObject)[-1].retained--;
        pthread_mutex_unlock(theLock);
        return NO;
    

可以看出,release函数其实却是是先获取到对象的retained 是否等于0。
如果已经等于0,则会直接调用dealloc函数,将对象销毁。
如果不等于0,则就是将对象中的obj_layout里的retained 做–操作。

5.dealloc的底层实现(GNUstep)

- (void) dealloc

  NSDeallocateObject(self);


inline void
NSDeallocateObject(id anObject)

    // 简化后的代码如下
    Class aClass = object_getClass(anObject);
    
    if ((anObject != nil) && !class_isMetaClass(aClass))
    
        /* Call the default finalizer to handle C++ destructors.
         */
        (*finalize_imp)(anObject, finalize_sel);
        
        AREM(aClass, (id)anObject);
        object_setClass((id)anObject, (Class)(void*)0xdeadface);
        NSZoneFree(z, o);
    
    return;


void
NSZoneFree (NSZone *zone, void *ptr)

  if (!zone)
    zone = NSDefaultMallocZone();
  (zone->free)(zone, ptr);


// 其实zone->free就是下面这个`default_free`函数
static void
default_free (NSZone *zone, void *ptr)

  free(ptr);

这里其实仅废弃alloc分配的内存块。

以上就是alloc/retain/release/dealloc在GNUstep中的实现。具体总结如下:

  • 在Objective-C的对象中存有引用计数这一整数值。
  • 调用alloc或是retain方法后,引用你计数值加1。
  • 调用release后,引用计数值减1。
  • 引用计数值为0时,调用dealloc方法销毁对象。

以上是关于alloc/retain/release/dealloc的底层实现(上)的主要内容,如果未能解决你的问题,请参考以下文章