您的位置:首页 > 移动开发 > IOS开发

IOS内存管理--手动引用计数实现

2015-08-05 21:53 344 查看
我的上一篇博客从手动引用计数、ARC两个方面介绍了IOS的内存管理机制,接下来简单介绍手动引用计数管理方式的部分实现,源码参照的是objc4-646,下载链接:http://opensource.apple.com/source/objc4/。

在手动引用计数管理方式中,最重要的是如何管理对象的引用计数,那么我们就从引用计数及其管理方式的实现入手。首先我们建立一个简单的命令行程序,在Build Settings 中将ARC设置为NO:



然后我们在main函数中写下如下简单的代码:
//初始化对象
id object = [[[NSObject alloc]init] autorelease];
// 引用计数加 1
[object retain];
// 打印引用计数为2
NSLog(@"object1 %lu",(unsigned long)[object retainCount]);
// 引用计数减 1,为1
[object release];
// 引用计数减 1 ,为0,然后释放该对象
[object release];

上面的代码段中涉及了我们手动引用计数管理的部分基本方法,那么我想知道上面的方法都调用了系统的哪些方法,该怎么查看?Xcode给出了我们很简单的方式,如下图所示:



我们看到Callees列出了我们上面代码段所调用的方法,Callers列出我们的方法被哪些方法调用,大家可以自行查看,据我观察调用顺序跟你所写的代码顺序没有明确的对应关系。
首先我们查看 +[NSObject alloc],通过查看源代码,方法的调用顺序是:

+(id)alloc;
id _objc_rootAlloc(Class cls);
id callAlloc(Class cls, bool checkNil, bool allocWithZone=false);
id class_createInstance(Class cls, size_t extraByte,nil);
calloc;
对象初始化,大都和申请内存相关,具体不再多说。

然后我们来看retain都涉及到哪些方法调用:

id objc_retain(id obj);
- (id)retain;
inline id objc_object::rootRetain();
id objc_object::sidetable_retain();
然后我们可以看看最后一个函数的具体实现:

id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.indexed);
#endif
// 获得该对象的table对象
SideTable *table = SideTable::tableForPointer(this);
// 类似上锁的操作
if (spinlock_trylock(&table->slock)) {
// 在table表中的refcnts属性中获取 引用计数的引用
size_t& refcntStorage = table->refcnts[this];
// 如果引用计数存在且计数没有越界
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
//#define SIDE_TABLE_RC_ONE            (1UL<<2)  // MSB-ward of deallocating bit
refcntStorage += SIDE_TABLE_RC_ONE;
}
spinlock_unlock(&table->slock);
return (id)this;
}
return sidetable_retain_slow(table);
}

首先我们看一下SideTable中是怎么存储对象的引用计数:RefcountMap refcnts;然后RefcountMap的定义如下:
typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;

从上面我们可以看到,对象的引用计数实现是类似的散列表,保存着引用计数值和对应拥有者对象的地址,这种实现方式在检测内存泄露的时候可以直接定位到具体的对象。
只要明白了上面的实现原理,其他release/retainCount的实现就很容易理解了,接下来我们看看release的方法调用简单罗列如下:

release
rootRelease
sidetable_release

同样我们还是来看最后一个函数的实现:
bool
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.indexed);
#endif
SideTable *table = SideTable::tableForPointer(this);

bool do_dealloc = false;

if (spinlock_trylock(&table->slock)) {
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;
}
spinlock_unlock(&table->slock);
if (do_dealloc  &&  performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return do_dealloc;
}

return sidetable_release_slow(table, performDealloc);
}


从代码中我们可以看出,首先要判断该引用计数是否为引用计数表中的最后一个,如果是则表示release后需要执行dealloc清除对象所占用的内存。
那么接下来我们看看retainCount的具体实现,我们从最后一个函数:
uintptr_t
objc_object::sidetable_retainCount()
{
SideTable *table = SideTable::tableForPointer(this);

size_t refcnt_result = 1;

spinlock_lock(&table->slock);
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;
}
spinlock_unlock(&table->slock);
return refcnt_result;
}

从这里我们看出,在计算一个对象的引用计数的时候,会遍历该对象引用计数表,将所有的引用计数求和。

那么有关引用计数管理操作的简单实现就跟大家分享到这里,如有错误还请大家指正。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息