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 做++操作。
注意这里的
obj
是struct 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的底层实现(上)的主要内容,如果未能解决你的问题,请参考以下文章