深入浅出ARC(下)

终于到最后一篇了,前面两篇几乎没有涉及到ARC,全是内存管理,这一次要好好研究一下ARC的底层实现,在看的过程中感觉以前学得C++忘的差不多了。这一篇主要讲ARC的strong,weak,__autorelease修饰符背后发生了什么。

__strong

找了很多资料,发现还是书上的解释比较靠谱,在strong类型对象超出作用域后会调用objc_release函数,当然在属性为strong的情况下赋值是会走这个方法objc_storeStrong,这个也是比较标准的setter方法的写法。

1.先取得原先的对象,然后和新的对象比较,如果是同一个则直接返回,然后对新的对象retain,赋给老得对象,然后再release老的对象,来保证赋值给strong类型的属性时可以正常的retainCount+1.

void
objc_storeStrong(id *location, id obj)
{
    id prev = *location;
    if (obj == prev) {
        return;
    }
    objc_retain(obj);
    *location = obj;
    objc_release(prev);
}

__strong中的autorelease

对于一个非持有对象赋值给strong类型的变量时,编译器会插入两个比较有意思的函数,这两个函数可以极大减少开销,减轻autoreleasePool的负担。

1.两个函数都会做一个校验,看是否能够不加入autoreleasePool,直接返回对象。

id 
objc_autoreleaseReturnValue(id obj)
{
    if (fastAutoreleaseForReturn(obj)) return obj;

    return objc_autorelease(obj);
}

id
objc_retainAutoreleasedReturnValue(id obj)
{
    if (fastRetainFromReturn(obj)) return obj;

    return objc_retain(obj);
}

2.这两个函数则是揭秘了如何做到不加入autereleasePool而延长生命周期的,具体并没有看太懂,貌似是和线程有关,通过线程的内存中转一下。看了一下,不同的cpu架构实现方式都不同。

static ALWAYS_INLINE 
bool fastAutoreleaseForReturn(id obj)
{
    assert(tls_get_direct(AUTORELEASE_POOL_RECLAIM_KEY) == nil);

    if (callerAcceptsFastAutorelease(__builtin_return_address(0))) {
        tls_set_direct(AUTORELEASE_POOL_RECLAIM_KEY, obj);
        return true;
    }

    return false;
}


static ALWAYS_INLINE
bool fastRetainFromReturn(id obj)
{
    if (obj == tls_get_direct(AUTORELEASE_POOL_RECLAIM_KEY)) {
        tls_set_direct(AUTORELEASE_POOL_RECLAIM_KEY, 0);
        return true;
    }

    return false;
}

__weak

赋值给weak类型变量的对象主要会经过这几步

1.编译器增加objc_initWeak函数调用,然后会调用objc_storeWeak函数。

id
objc_initWeak(id *addr, id val)
{
    *addr = 0;
    if (!val) return nil;
    return objc_storeWeak(addr, val);
}

2.这个函数看上去做了很多事,其实很多都可以略过不看,主要做了通过weak_unregister_no_lock函数把原来的对象unregister,通过weak_register_no_lock函数把新的对象register进去。最后一步就是通过setWeaklyReferenced_nolock函数使得引用计数那张散列表的weak引用对象的引用计数中标识为weak引用。

id
objc_storeWeak(id *location, id newObj)
{
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;
    spinlock_t *lock1;
#if SIDE_TABLE_STRIPE > 1
    spinlock_t *lock2;
#endif

    // Acquire locks for old and new values.
    // Order by lock address to prevent lock ordering problems. 
    // Retry if the old value changes underneath us.
 retry:
    oldObj = *location;

    oldTable = SideTable::tableForPointer(oldObj);
    newTable = SideTable::tableForPointer(newObj);

    lock1 = &newTable->slock;
#if SIDE_TABLE_STRIPE > 1
    lock2 = &oldTable->slock;
    if (lock1 > lock2) {
        spinlock_t *temp = lock1;
        lock1 = lock2;
        lock2 = temp;
    }
    if (lock1 != lock2) spinlock_lock(lock2);
#endif
    spinlock_lock(lock1);

    if (*location != oldObj) {
        spinlock_unlock(lock1);
#if SIDE_TABLE_STRIPE > 1
        if (lock1 != lock2) spinlock_unlock(lock2);
#endif
        goto retry;
    }

    weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    newObj = weak_register_no_lock(&newTable->weak_table, newObj, location);
    // weak_register_no_lock returns nil if weak store should be rejected

    // Set is-weakly-referenced bit in refcount table.
    if (newObj  &&  !newObj->isTaggedPointer()) {
        newObj->setWeaklyReferenced_nolock();
    }

    // Do not set *location anywhere else. That would introduce a race.
    *location = newObj;

    spinlock_unlock(lock1);
#if SIDE_TABLE_STRIPE > 1
    if (lock1 != lock2) spinlock_unlock(lock2);
#endif

    return newObj;
}

__weak(没有证实的功能)

这里是书上看到的,同时也是在源码中能找到的,但是我却没有证实的一个知识点,就是使用了附有__weak修饰符的变量,会自动注册到autoreleasePool中。

1.不管是从注释还是代码,都证实了在使用weak变量时为了保证对象没有释放,会添加到autoreleasePool中

/** 
 * This loads the object referenced by a weak pointer and returns it, after
 * retaining and autoreleasing the object to ensure that it stays alive
 * long enough for the caller to use it. This function would be used
 * anywhere a __weak variable is used in an expression.
 * 
 * @param location The weak pointer address
 * 
 * @return The object pointed to by \e location, or \c nil if \e location is \c nil.
 */
id
objc_loadWeak(id *location)
{
    if (!*location) return nil;
    return objc_autorelease(objc_loadWeakRetained(location));
}

2.这里weak_read_no_lock函数会retain这个对象,然后通过外部autorelease。

id
objc_loadWeakRetained(id *location)
{
    id result;

    SideTable *table;
    spinlock_t *lock;

 retry:
    result = *location;
    if (!result) return nil;

    table = SideTable::tableForPointer(result);
    lock = &table->slock;

    spinlock_lock(lock);
    if (*location != result) {
        spinlock_unlock(lock);
        goto retry;
    }

    result = weak_read_no_lock(&table->weak_table, location);

    spinlock_unlock(lock);
    return result;
}

由此可知正在使用的weak变量不会被释放,但是如果大量使用weak变量会造成autoreleasePool中得对象大量增加,所以我们需要找到一个平衡点。

__autorelease

这个修饰符很少用,等同于ARC无效时调用autorelease方法,一般可以配合@autorelease使用来达到减少瞬时内存峰值。

ARC到这里也告一段落了,回过头来发现在这一段学习的过程中有一种豁然开朗的感觉,以前可能大概知道一点内存管理,但总是有一些点不能理解,在学习的道路上又进了一步。下一次准备看一下GCD那一块的知识,也算是对这本书的一个交代。