oc runtime之weak

来源:互联网 发布:1hhhh域名升级访问中 编辑:程序博客网 时间:2024/06/05 11:54

二、weak

声明了弱引用,实际上调用了objc_initWeak函数

/**  * Initialize a fresh weak pointer to some object location.  * It would be used for code like:  * * (The nil case)  * __weak id weakPtr; * (The non-nil case)  * NSObject *o = ...; * __weak id weakPtr = o; *  * This function IS NOT thread-safe with respect to concurrent  * modifications to the weak variable. (Concurrent weak clear is safe.) * * @param location Address of __weak ptr.  * @param newObj Object ptr.  */idobjc_initWeak(id *location, id newObj){    if (!newObj) {        *location = nil;        return nil;    }    return storeWeak<false/*old*/, true/*new*/, true/*crash*/>        (location, (objc_object*)newObj);}

我们可以看到它最终调用了storeWeak函数,那我们再看看这个函数

// Update a weak variable.// If HaveOld is true, the variable has an existing value //   that needs to be cleaned up. This value might be nil.// If HaveNew is true, there is a new value that needs to be //   assigned into the variable. This value might be nil.// If CrashIfDeallocating is true, the process is halted if newObj is //   deallocating or newObj's class does not support weak references. //   If CrashIfDeallocating is false, nil is stored instead.template <bool HaveOld, bool HaveNew, bool CrashIfDeallocating>static id storeWeak(id *location, objc_object *newObj){    assert(HaveOld  ||  HaveNew);    if (!HaveNew) assert(newObj == nil);    Class previouslyInitializedClass = nil;    id oldObj;    SideTable *oldTable;    SideTable *newTable;    // 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:    if (HaveOld) {        oldObj = *location;        oldTable = &SideTables()[oldObj];    } else {        oldTable = nil;    }    if (HaveNew) {        newTable = &SideTables()[newObj];    } else {        newTable = nil;    }    SideTable::lockTwo<HaveOld, HaveNew>(oldTable, newTable);    if (HaveOld  &&  *location != oldObj) {        SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);        goto retry;    }    // Prevent a deadlock between the weak reference machinery    // and the +initialize machinery by ensuring that no     // weakly-referenced object has an un-+initialized isa.    if (HaveNew  &&  newObj) {        Class cls = newObj->getIsa();        if (cls != previouslyInitializedClass  &&              !((objc_class *)cls)->isInitialized())         {            SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));            // If this class is finished with +initialize then we're good.            // If this class is still running +initialize on this thread             // (i.e. +initialize called storeWeak on an instance of itself)            // then we may proceed but it will appear initializing and             // not yet initialized to the check above.            // Instead set previouslyInitializedClass to recognize it on retry.            previouslyInitializedClass = cls;            goto retry;        }    }    // Clean up old value, if any.    if (HaveOld) {        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);    }    // Assign new value, if any.    if (HaveNew) {        newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table,                                                       (id)newObj, location,                                                       CrashIfDeallocating);        // 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 = (id)newObj;    }    else {        // No new value. The storage is not changed.    }    SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);    return (id)newObj;}

这里先获得oldTable和newTable,当然如果有的话,然后对它们上锁。下面有两个重试,第一个情况是,如果old value被改了,则解锁重试去取新的sideTable;第二个情况是,为了避免死锁。在弱引用机制和+initialize机制这两个机制下,要保证所有的弱引用对象必须完成了initialize,如果没有完成,会去调用_class_initialize函数来完成,然后设置previouslyInitializedClass条件来标置已完成了,解锁重试。

1、weak_register_no_lock

PRIVATE_EXTERN id weak_register_no_lock(weak_table_t *weak_table, id referent, id *referrer) {    if (referent) {        // ensure that the referenced object is viable        BOOL (*allowsWeakReference)(id, SEL) = (BOOL(*)(id, SEL))        class_getMethodImplementation(object_getClass(referent),                                      @selector(allowsWeakReference));        if ((IMP)allowsWeakReference != _objc_msgForward) {            if (! (*allowsWeakReference)(referent, @selector(allowsWeakReference))) {                _objc_fatal("cannot form weak reference to instance (%p) of class %s", referent, object_getClassName(referent));            }        }        else {            return NULL;        }        // now remember it and where it is being stored        weak_entry_t *entry;        if ((entry = weak_entry_for_referent(weak_table, referent))) {            append_referrer_no_lock(&entry->referrers, referrer);        }         else {            weak_entry_t new_entry;            new_entry.referent = referent;            new_entry.referrers.refs = NULL;            new_entry.referrers.num_refs = 0;            new_entry.referrers.num_allocated = 0;            append_referrer_no_lock(&new_entry.referrers, referrer);            weak_table->num_weak_refs++;            weak_grow_maybe_no_lock(weak_table);            weak_entry_insert_no_lock(weak_table, &new_entry);        }    }    // Do not set *referrer. objc_storeWeak() requires that the     // value not change.    return referent;}

函数先调用allowsWeakReference来判断是否允许弱引用,下面调用weak_entry_for_referent函数来查询weak_table里是否已有referent的entry,有的话将referrer加入到entry->referrers,没有的话创建new_entry,将其插入weak_table

2、weak_unregister_no_lock

// Unregister an already-registered weak reference. // This is used when referrer's storage is about to go away, but referent //   isn't dead yet. (Otherwise, zeroing referrer later would be a //   bad memory access.)// Does nothing if referent/referrer is not a currently active weak reference.// Does not zero referrer.// fixme currently requires old referent value to be passed in (lame)// fixme unregistration should be automatic if referrer is collectedPRIVATE_EXTERN void weak_unregister_no_lock(weak_table_t *weak_table, id referent, id *referrer){    weak_entry_t *entry;    if ((entry = weak_entry_for_referent(weak_table, referent))) {        remove_referrer_no_lock(&entry->referrers, referrer);        if (entry->referrers.num_refs == 0) {            weak_entry_remove_no_lock(weak_table, entry);            weak_table->num_weak_refs--;        }    }     // Do not set *referrer = NULL. objc_storeWeak() requires that the     // value not change.}
// Remove old_referrer from list, if it's present.// Does not remove duplicates.// fixme this is slow if old_referrer is not present.static void remove_referrer_no_lock(weak_referrer_array_t *list, id *old_referrer){    size_t index = hash_pointer(old_referrer) % list->num_allocated;    size_t start_index = index, hash_displacement = 0;    while (list->refs[index].referrer != old_referrer) {        index++;        hash_displacement++;        if (index == list->num_allocated)            index = 0;        if (index == start_index || hash_displacement > list->max_hash_displacement) {            malloc_printf("attempted to remove unregistered weak referrer %p\n", old_referrer);            return;        }    }    list->refs[index].referrer = NULL;    list->num_refs--;}
// Remove entry from the zone's table of weak references, and rehash// Does not update num_weak_refs.static void weak_entry_remove_no_lock(weak_table_t *weak_table, weak_entry_t *entry){    // remove entry    entry->referent = NULL;    if (entry->referrers.refs) _free_internal(entry->referrers.refs);    entry->referrers.refs = NULL;    entry->referrers.num_refs = 0;    entry->referrers.num_allocated = 0;    // rehash after entry    weak_entry_t *weak_entries = weak_table->weak_entries;    size_t table_size = weak_table->max_weak_refs;    size_t hash_index = entry - weak_entries;    size_t index = hash_index;    if (!weak_entries) return;    do {        index++; if (index == table_size) index = 0;        if (!weak_entries[index].referent) return;        weak_entry_t entry = weak_entries[index];        weak_entries[index].referent = NULL;        weak_entry_insert_no_lock(weak_table, &entry);    } while (index != hash_index);}

我们可以看到:
首先,通过weak_entry_for_referent从weak_table中查询entry,如果存在,则从entry->referrers中移除此referrer。
然后,如果referrers表为空就调用weak_entry_remove_no_lock函数将此entry从weak_table中移除。在这里可以看到entry->referent = NULL,即把引用者置空。

3、对象释放

通过apple的runtime源码,不难发现NSObject执行dealloc时调用_objc_rootDealloc继而调用object_dispose随后调用objc_destructInstance方法

/************************************************************************ objc_destructInstance* Destroys an instance without freeing memory. * Calls C++ destructors.* Calls ARR ivar cleanup.* Removes associative references.* Returns `obj`. Does nothing if `obj` is nil.* Be warned that GC DOES NOT CALL THIS. If you edit this, also edit finalize.* CoreFoundation and other clients do call this under GC.**********************************************************************/void *objc_destructInstance(id obj) {    if (obj) {        Class isa_gen = _object_getClass(obj);        class_t *isa = newcls(isa_gen);        // Read all of the flags at once for performance.        bool cxx = hasCxxStructors(isa);        bool assoc = !UseGC && _class_instancesHaveAssociatedObjects(isa_gen);        // This order is important.        if (cxx) object_cxxDestruct(obj);        if (assoc) _object_remove_assocations(obj);        if (!UseGC) objc_clear_deallocating(obj);    }    return obj;}

这个函数主要干了后面的三件事:
1、object_cxxDestruct来执行对象以及父类的.cxx_destruct(它是clang在编译时候动态插入的)方法,来释放成员变量。
2、_object_remove_assocations释放关联的变量。
3、objc_clear_deallocating清除weak表等。
我们来看objc_clear_deallocating

void objc_clear_deallocating(id obj) {    assert(obj);    if (obj->isTaggedPointer()) return;    obj->clearDeallocating();}
inline void objc_object::clearDeallocating(){    if (!isa.indexed) {        // Slow path for raw pointer isa.        sidetable_clearDeallocating();    }    else if (isa.weakly_referenced  ||  isa.has_sidetable_rc) {        // Slow path for non-pointer isa with weak refs and/or side table data.        clearDeallocating_slow();    }    assert(!sidetable_present());}

分别看一下这两个clear
1)sidetable_clearDeallocating

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();}
void  weak_clear_no_lock(weak_table_t *weak_table, id referent_id) {    objc_object *referent = (objc_object *)referent_id;    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);    if (entry == nil) {        /// XXX shouldn't happen, but does with mismatched CF/objc        //printf("XXX no entry for clear deallocating %p\n", referent);        return;    }    // zero out references    weak_referrer_t *referrers;    size_t count;    if (entry->out_of_line) {        referrers = entry->referrers;        count = TABLE_SIZE(entry);    }     else {        referrers = entry->inline_referrers;        count = WEAK_INLINE_COUNT;    }    for (size_t i = 0; i < count; ++i) {        objc_object **referrer = referrers[i];        if (referrer) {            if (*referrer == referent) {                *referrer = nil;            }            else if (*referrer) {                _objc_inform("__weak variable at %p holds %p instead of %p. "                             "This is probably incorrect use of "                             "objc_storeWeak() and objc_loadWeak(). "                             "Break on objc_weak_error to debug.\n",                              referrer, (void*)*referrer, (void*)referent);                objc_weak_error();            }        }    }    weak_entry_remove(weak_table, entry);}

可见它即是清除此对象关联的weak表,并将表中的引用者置空。
2)clearDeallocating_slow

// Slow path of clearDeallocating() // for objects with indexed isa// that were ever weakly referenced // or whose retain count ever overflowed to the side table.NEVER_INLINE void objc_object::clearDeallocating_slow(){    assert(isa.indexed  &&  (isa.weakly_referenced || isa.has_sidetable_rc));    SideTable& table = SideTables()[this];    table.lock();    if (isa.weakly_referenced) {        weak_clear_no_lock(&table.weak_table, (id)this);    }    if (isa.has_sidetable_rc) {        table.refcnts.erase(this);    }    table.unlock();

has_sidetable_rc是判断引用计数是否过大,clearDeallocating_slow最终也是调用weak_clear_no_lock

最后贴一张从网上找的图片来结束
这里写图片描述