深入浅出ARC(中)

这篇主要探究一下dealloc额外做的两三事,autorelease的内幕,以及ARC的使用规则。本来不准备写dealloc和autorelease的,不过昨天在看源码的时候发现还是很有意思的,然后也是为了内存管理的完整性,就顺带着记录下来了。

dealloc搞得鬼

上一次看到这里发现了一个objc_destructInstance函数的调用,这次主要就是探究下,这个函数做了什么。

id 
object_dispose(id obj)
{
    if (!obj) return nil;
    objc_destructInstance(obj);    
#if SUPPORT_GC
    if (UseGC) {
        auto_zone_retain(gc_zone, obj); // gc free expects rc==1
    }
#endif
    free(obj);
    return nil;
}

在这里大致做了三件事,调用c++ destructors,移除关联对象,清理散列表中得一些值。

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = !UseGC && obj->hasAssociatedObjects();
        bool dealloc = !UseGC;

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

destructors

1.从名字上看能大概看出是一个c++的析构函数,如果不是isTaggedPointer则调用object_cxxDestructFromClass这个析构函数

void object_cxxDestruct(id obj)
{
    if (!obj) return;
    if (obj->isTaggedPointer()) return;
    object_cxxDestructFromClass(obj, obj->ISA());
}

2.这里关键就是取到真正的析构函数,所以用了个函数指针,因为需要做一系列的判断,最后调用lookupMethodInClassAndLoadCache函数。

static void object_cxxDestructFromClass(id obj, Class cls)
{
    void (*dtor)(id);

    // Call cls's dtor first, then superclasses's dtors.

    for ( ; cls; cls = cls->superclass) {
        if (!cls->hasCxxDtor()) return; 
        dtor = (void(*)(id))
            lookupMethodInClassAndLoadCache(cls, SEL_cxx_destruct);
        if (dtor != (void(*)(id))_objc_msgForward_impcache) {
            if (PrintCxxCtors) {
                _objc_inform("CXX: calling C++ destructors for class %s", 
                             cls->nameForLogging());
            }
            (*dtor)(obj);
        }
    }
}

3.这个就是查找析构函数的函数,先从缓存表中找,再从方法列表中找,如果找到还要放到缓存表中,找不到的话就调用方法转发,并且缓存起来。

IMP lookupMethodInClassAndLoadCache(Class cls, SEL sel)
{
    Method meth;
    IMP imp;

    // fixme this is incomplete - no resolver, +initialize, GC - 
    // but it's only used for .cxx_construct/destruct so we don't care
    assert(sel == SEL_cxx_construct  ||  sel == SEL_cxx_destruct);

    // Search cache first.
    imp = cache_getImp(cls, sel);
    if (imp) return imp;

    // Cache miss. Search method list.

    rwlock_read(&runtimeLock);

    meth = getMethodNoSuper_nolock(cls, sel);

    if (meth) {
        // Hit in method list. Cache it.
        cache_fill(cls, sel, meth->imp);
        rwlock_unlock_read(&runtimeLock);
        return meth->imp;
    } else {
        // Miss in method list. Cache objc_msgForward.
        cache_fill(cls, sel, _objc_msgForward_impcache);
        rwlock_unlock_read(&runtimeLock);
        return _objc_msgForward_impcache;
    }
}

关联对象

关联对象我记得之前有个大神讲过,看的有点迷糊,就自己翻了下源码,主要就一个函数,大致思路就是关联对象会存在一张映射表中,根据传入的object找到关联对象映射表,遍历映射表添加到vector中,然后最后清楚相关数据,最后的时候遍历vector,release关联对象。

void _object_remove_assocations(id object) {
    vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        if (associations.size() == 0) return;
        disguised_ptr_t disguised_object = DISGUISE(object);
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {
            // copy all of the associations that need to be removed.
            ObjectAssociationMap *refs = i->second;
            for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
                elements.push_back(j->second);
            }
            // remove the secondary table.
            delete refs;
            associations.erase(i);
        }
    }
    // the calls to releaseValue() happen outside of the lock.
    for_each(elements.begin(), elements.end(), ReleaseValue());
}

clearDeallocating

这里做的主要是清理weak变量,具体内容可以参考sunny大神的博客

inline void 
objc_object::clearDeallocating()
{
    sidetable_clearDeallocating();
}

2.还是取散列表SideTable,遍历是否有weak引用,it->second & SIDE_TABLE_WEAKLY_REFERENCED这里用到的就是前面所说的宏,作用就是标示是否有weak引用。如果有就调用weak_clear_no_lock这个函数,这个函数中会遍历清除所有weak引用,这就是Objc不需要我们手动清理weak引用的关键点。

void 
objc_object::sidetable_clearDeallocating()
{
    SideTable *table = SideTable::tableForPointer(this);

    // clear any weak table items
    // clear extra retain count and deallocating bit
    // (fixme warn or abort if extra retain count == 0 ?)
    spinlock_lock(&table->slock);
    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);
    }
    spinlock_unlock(&table->slock);
}

autorelease的内幕

这个专题基本讲烂了,先上几篇我参考的优质博客
sunnyxx的《黑幕背后的Autorelease》
雷纯锋的《Objective-C Autorelease Pool 的实现原理》

大神们已经讲的很详细了,不过为了内容的完整,还是硬着头皮自己去理一下逻辑,写一点不一样的东西,所以下面并不会去探究autoreleasePool实现原理,只看NSObject中autorelease函数的实现。

autorelease函数做了什么

1.前面两步都是简单调用底层函数,最终调用的是rootAutorelease2函数

- (id)autorelease {
    return ((id)self)->rootAutorelease();
}

inline id 
objc_object::rootAutorelease()
{
    assert(!UseGC);

    if (isTaggedPointer()) return (id)this;
    if (fastAutoreleaseForReturn((id)this)) return (id)this;

    return rootAutorelease2();
}

3.这一步会调用AutoreleasePoolPage的autorelease函数,关于AutoreleasePoolPage的实现可以看上面两位大神的讲解。

__attribute__((noinline,used))
id 
objc_object::rootAutorelease2()
{
    assert(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);
}

4.这两步都是AutoreleasePoolPage的方法,主要就是判断当前page是否已满,如果没有就直接add,如果已经满了则会去重新new一个page,然后设为当前页,并且添加进去,如果当前没有page,则会初始化第一个page然后添加。

static inline id autorelease(id obj)
    {
        assert(obj);
        assert(!obj->isTaggedPointer());
        id *dest __unused = autoreleaseFast(obj);
        assert(!dest  ||  *dest == obj);
        return obj;
    }

static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) {
            return page->add(obj);
        } else if (page) {
            return autoreleaseFullPage(obj, page);
        } else {
            return autoreleaseNoPage(obj);
        }
    }

ARC使用原则

修饰符

其实这些东西都是老掉牙的内容了,我就简单说几点吧。这些不是关键,同样是为了内容完整性考虑。

  • __strong 这个就是代表了强引用,在超出作用域的时候会release一下,大多数情况下strong已经能够保证内存管理正确了。
  • __weak 这个就是用来处理那些strong解决不了的情况,循环引用。只能在iOS5以上使用
  • __unsafe_unretain 这个就意味着不属于编译器的内存管理,如果指向的对象已经释放,野指针。
  • __autoreleasing 这个东西几乎很少用,因为大多数时间编译器知道哪些变量是需要autoreleasing。比如说在取得非自己持有的对象时,该对象自动被加入autoreleasing pool。再比如说访问id* 类型的变量。

每个id类型的对象必须有一种修饰符,在ARC下,其中__strong是默认的修饰符。除了unsafe_unretain,其余的可以保证初始化为nil。

规则

  1. 不能使用retain/release/retainCount/autorelease。
  2. 不能使用NSAllocateObject/NSDeallocateObject。
  3. 必须遵守内存管理的命名规则。
  4. 不能显示的调用dealloc,比如[super dealloc]。
  5. 用@autorelease代替NSAutoreleasePool。
  6. 不能使用NSZone。
  7. 对象类型变量不能作为C语言结构体成员(可以用__unsafe_unretain修饰之后使用)
  8. 不能显示转换id和void*(可以通过_bridge,\_bridge_retained,__bridge_transfer)。

这些都是书上有的,基本很全了。

萌萌的小尾巴

到此ARC内存管理这一块讲了很多了,还剩下ARC具体实现了,这个放到下面一篇分析,ARC是由编译器来管理内存了,但实际上单纯靠编译器是实现不了的,还需要runtime来辅助。所以实际上ARC使用了Clang和Objc库来完成内存管理的。