0%

iOS进阶——iOS(Objective-C)内存管理·二

在写 『iOS(Objective-C) 内存管理&Block』 一文时,我并没有发现 NSObject 的代码已经被开源了,所以分析的主要是 GNUStep 的源码,对 Apple 的部分只是通过猜测。

实质上,NSObject 的实现内容已经开源在 objc4-706 中。于是我便开始学习 objc4 中的内容。

下面就和大家扒一扒 Apple 的 NSObject 内存管理的一些内容。

SideTable

找到 NSObject.mm,首先来一些非常重要的信息,以便后面的理解。

objc4-706 NSObject.mm SideTable:

1
2
3
4
5
6
7
8
struct SideTable {
// 保证原子操作的自旋锁
spinlock_t slock;
// 引用计数的 hash 表
RefcountMap refcnts;
// weak 引用全局 hash 表
weak_table_t weak_table;
};

SideTable 结构体重定了几个非常重要的变量。

1
2
3
4
5
6
7
8
// The order of these bits is important.
#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
#define SIDE_TABLE_DEALLOCATING (1UL<<1) // MSB-ward of weak bit
#define SIDE_TABLE_RC_ONE (1UL<<2) // MSB-ward of deallocating bit
#define SIDE_TABLE_RC_PINNED (1UL<<(WORD_BITS-1))

#define SIDE_TABLE_RC_SHIFT 2
#define SIDE_TABLE_FLAG_MASK (SIDE_TABLE_RC_ONE-1)

以上定义的是几个重要偏移量。引用计数 retainCount 是保存在一个无符号整形中,也就是有 8 个字节。其结构可以用下图表示:

  1. SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)(表示对象所在内存的第 1 位),标识该对象是否有过 weak 对象;
  2. SIDE_TABLE_DEALLOCATING (1UL<<1)(表示对象所在内存的第 2 位),标识该对象是否正在 dealloc(析构)。
  3. SIDE_TABLE_RC_ONE (1UL<<2) (表示对象所在内存的第 3 位),存放引用计数数值(其实第三位之后都用来存放引用计数数值)。

retainCount

找到 retainCount 的实现,一层一层向下看。

objc4 NSObject.mm retainCount:

1
2
3
- (NSUInteger)retainCount {
return ((id)self)->rootRetainCount();
}

objc4 objc-object.h rootRetainCount:

1
2
3
4
5
6
inline uintptr_t 
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
return sidetable_retainCount();
}

objc4 NSObject.mm sidetable_retainCount:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
uintptr_t
objc_object::sidetable_retainCount()
{
SideTable& table = SideTables()[this];

size_t refcnt_result = 1;

table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
// this is valid for SIDE_TABLE_RC_PINNED too
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
}
table.unlock();
return refcnt_result;
}

it->second 指向的就是存放引用计数相关的那个 8 位的无符号整型。

上面介绍过 Sidetable 中的几个重要偏移量,通过位移 SIDE_TABLE_RC_SHIFT 可以获取真实的引用计数。

所以,sidetable_retainCount() 中的主要内容就是遍历引用计数表,查找对象获取引用计数 +1 并将结果返回。

retain

找到 retain 的实现。

objc4 NSObject.mm retain:

1
2
3
- (id)retain {
return ((id)self)->rootRetain();
}

objc4 objc-objc.h rootRetain:

1
2
3
4
5
6
7
8
9
// Base retain implementation, ignoring overrides.
// This does not check isa.fast_rr; if there is an RR override then
// it was already called and it chose to call [super retain].
inline id
objc_object::rootRetain()
{
if (isTaggedPointer()) return (id)this;
return sidetable_retain();
}

objc4 NSObject.mm sidetable_retain:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];

table.lock();
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();

return (id)this;
}

refcntStorage += SIDE_TABLE_RC_ONE 让人费解,实际是怎么回事呢?我们通过距离说明:

如果 obj 的引用计数数值为 1(二进制 00000100,因为第一位,第二位用来标识其他内容),现在如果进行 retain,需要对引用计数数值增加 1,那么需要由 00000100 => 00001000。所以实际上,从整型的角度,是 retainCount + 4,而不是我们理解的 +1。

SIDE_TABLE_RC_ONE 定义是的 1UL<<2,也就是4,所以这里 refcntStorage += SIDE_TABLE_RC_ONE;

release

objc4-706 NSObject.mm release:

1
2
3
- (oneway void)release {
((id)self)->rootRelease();
}

objc4-706 objc-object.h rootRelease:

1
2
3
4
5
6
inline bool 
objc_object::rootRelease()
{
if (isTaggedPointer()) return false;
return sidetable_release(true);
}

objc4-706 NSObject.mm sidetable_release:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// rdar://20206767
// return uintptr_t instead of bool so that the various raw-isa
// -release paths all return zero in eax
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];

bool do_dealloc = false;

table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end()) {
do_dealloc = true;
table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
} else if (it->second < SIDE_TABLE_DEALLOCATING) {
// SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
do_dealloc = true;
it->second |= SIDE_TABLE_DEALLOCATING;
} else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
it->second -= SIDE_TABLE_RC_ONE;
}
table.unlock();
if (do_dealloc && performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return do_dealloc;
}

看后面几个判断。

  1. 如果对象记录在引用计数表的最后一个:do_dealloc 设置为 true,引用计数数值设置为 SIDE_TABLE_DEALLOCATING(二进制 00000010)。
  2. 如果 8 位的引用计数小于 SIDE_TABLE_DEALLOCATING(二进制 00000010),也就如果是 00000001 或 00000000:do_dealloc 设置为 true,并添加 deallocating 标识位。(但至于有什么用不太理解,希望哪位大神指点一下)。
  3. 如果已经 8 位引用计数 & SIDE_TABLE_RC_PINNED ,即对象不在 deallocating,且没有被弱引用,且 8 位没有溢出:8 位引用计数减少 4,即真实引用计数数值 -1。
  4. 最后,如果 do_deallocperformDealloc(传入时就已经为 true)都为 ture,执行 SEL_dealloc 释放对象。
  5. 方法返回 do_dealloc。

如果你只想知道 ARC 引用计数相关,那么只需要看上面的代码就可以了。alloc 和 dealloc 主要是对对象的一些内存分配。


alloc

查看 alloc 相关代码。

objc4-706 NSObject.mm alloc:

1
2
3
+ (id)alloc {
return _objc_rootAlloc(self);
}

objc4-706 NSObject.mm _objc_rootAlloc:

1
2
3
4
5
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

objc4-706 NSObject.mm callAlloc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate 
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
if (slowpath(checkNil && !cls)) return nil;

#if __OBJC2__
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
// No alloc/allocWithZone implementation. Go straight to the allocator.
// fixme store hasCustomAWZ in the non-meta class and
// add it to canAllocFast's summary
if (fastpath(cls->canAllocFast())) {
// No ctors, raw isa, etc. Go straight to the metal.
bool dtor = cls->hasCxxDtor();
id obj = (id)calloc(1, cls->bits.fastInstanceSize());
if (slowpath(!obj)) return callBadAllocHandler(cls);
obj->initInstanceIsa(cls, dtor);
return obj;
}
else {
// Has ctor or raw isa or something. Use the slower path.
id obj = class_createInstance(cls, 0);
if (slowpath(!obj)) return callBadAllocHandler(cls);
return obj;
}
}
#endif

// No shortcuts available.
if (allocWithZone) return [cls allocWithZone:nil];
return [cls alloc];
}

进入方法后先进行 if (slowpath(checkNil && !cls)) return nil; 判断。

objc4-706 objc-os.h slowpath:

1
2
#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))

__builtin_expect(exp, n) 方法表示 exp 很有可能为 0,返回值为 exp。你可以将 fastpath(x) 理解成真值判断,slowpath(x) 理解成假值判断。

所以,根据传入值,checkNil 为 false,checkNil && !cls 也为 false。那么这里不会返回 nil。继续向下阅读。

其后是一个 Objective-C 2.0 的条件编译指令。当然我们现在用的都属于 Objctive-C 2.0,会执行其中代码。首先进行一个判断 if (fastpath(!cls->ISA()->hasCustomAWZ()))... else ...,这是判断一个类是否有自定义的 +allocWithZone 实现。

如果没有自定义的 +allocWithZone 实现。进行下一步,又是一个判断:if (fastpath(cls->canAllocFast()))... else ...,这里只有对象不存在、没有 isa 等情况才会为真值。所以之间看 else 内容。

else 代码块中调用了 id obj = class_createInstance(cls, 0);。查看内容时注意查看 objc-runtime-new.h 中的内容而不是 objc-runtime-old.mm 中的内容(你可以注意到 objc-runtime-new.h 顶部的 Coptyright 是 Copyright (c) 2005-2007 Apple Inc. All Rights Reserved.)

objc-runtime-new.mmcanAllocFast() 定义如下:

objc4-706 objc-runtime-new.h canAllocFast:

1
2
3
4
5
6
7
8
9
#if FAST_ALLOC
bool canAllocFast() {
return bits & FAST_ALLOC;
}
#else
bool canAllocFast() {
return false;
}
#endif

再看 FAST_ALLOC 定义,观察下图:

FAST_ALLOC

发现,#elif 1 直接拦截了下面的 define,所以 #if FAST_ALLOC 不起作用(这里我也不是很确定,哪位大神指点一下)。所以,canAllocFast() 返回 false,fastpath(cls->canAllocFast()) 判断为假。

执行

1
2
3
4
// Has ctor or raw isa or something. Use the slower path.
id obj = class_createInstance(cls, 0);
if (slowpath(!obj)) return callBadAllocHandler(cls);
return obj;

objc4 objc-runtime-new.mm class_createInstance:

1
2
3
4
5
id 
class_createInstance(Class cls, size_t extraBytes)
{
return _class_createInstanceFromZone(cls, extraBytes, nil);
}

objc4 objc-runtime-new.mm _class_createInstanceFromZone:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
id 
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone)
{
void *bytes;
size_t size;

// Can't create something for nothing
if (!cls) return nil;

// Allocate and initialize
size = cls->alignedInstanceSize() + extraBytes;

// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;

if (zone) {
bytes = malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
bytes = calloc(1, size);
}

return objc_constructInstance(cls, bytes);
}

传入的 extraBytes 为0,zone 为 nil,那么主要执行的语句是 bytes = calloc(1, size);return objc_constructInstance(cls, bytes);。bytes 是对象所需内存空间。

FYI:
calloc(size_t __count, size_t __size) 是 C 语言中的方法,用来在内存的动态存储区中分配 n 个长度为size的连续空间,函数返回一个指向分配起始地址的指针。如果分配不成功,返回NULL。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
objc_constructInstance(Class cls, void *bytes) 
{
if (!cls || !bytes) return nil;

id obj = (id)bytes;

obj->initIsa(cls);

if (cls->hasCxxCtor()) {
return object_cxxConstructFromClass(obj, cls);
} else {
return obj;
}
}

objc_constructInstance 方法中,将 bytes(指向分对象的指针)定义为 obj,并将 obj 的 isa 赋值为传入的 cls。最后返回 obj。

FYI:
hasCxxCtor() 是判断当前 class 或者 superclass 是否有 .cxx_construct 构造方法的实现。
hasCxxDtor() 是判断判断当前 class 或者 superclass 是否有 .cxx_destruct 析构方法的实现。
参考:Objc 对象的今生今世

dealloc

找到 dealloc 的实现。

objc4 NSObject.mm dealloc:

1
2
3
- (void)dealloc {
_objc_rootDealloc(self);
}

objc4 NSObject.mm _objc_rootDealloc:

1
2
3
4
5
6
7
void
_objc_rootDealloc(id obj)
{
assert(obj);

obj->rootDealloc();
}

objc4 NSObject.mm _objc_rootDealloc:

1
2
3
4
5
6
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return;
object_dispose((id)this);
}

FYI:
深入理解Tagged Pointer

objc4 objc-runtime-new.mm object_dispose:

1
2
3
4
5
6
7
8
9
10
id 
object_dispose(id obj)
{
if (!obj) return nil;

objc_destructInstance(obj);
free(obj);

return nil;
}

objc4 objc-runtime-new.mm objc_destructInstance:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory.
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is nil.
**********************************************************************/
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();

// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}

return obj;
}

objc4 objc-object.h clearDeallocating:

1
2
3
4
5
6
inline void 
objc_object::clearDeallocating()
{
sidetable_clearDeallocating();
}

objc4 NSObject.mm sidetable_clearDeallocating:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void 
objc_object::sidetable_clearDeallocating()
{
SideTable& table = SideTables()[this];

// clear any weak table items
// clear extra retain count and deallocating bit
// (fixme warn or abort if extra retain count == 0 ?)
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
table.refcnts.erase(it);
}
table.unlock();
}

所以,objc_destructInstance(obj); 中进行了销毁实例但不释放内存,调用了 C++ 的析构函数(如果对象有),处理先关的对象(如果有),最后调用 obj->clearDeallocating(); 清除 weak 引用、清除多余的 retain count。objc_destructInstance(obj); 之后是 free(obj); 释放 obj 占用的内存空间。

#Other

(在写这边文章的时候,我怎么感觉我最大的感触是,C++ 不懂。。)

写本篇博文时参考的所有资料:

Tracy Wang-深入浅出ARC(上)

原来我非不快乐-我们的对象会经历什么

desgard-weak 弱引用的实现方式

Objc 对象的今生今世

玉令天下-Objective-C 引用计数原理

一缕殇流化隐半边冰霜-神经病院Objective-C Runtime入院第一天——isa和Class

sindrilin-闲聊内存管理


有什么问题都可以在博文后面留言,或者微博上私信我。

博主是 iOS 妹子一枚。

希望大家一起进步。

我的微博:Lotty周小鱼