iOS中weak原理學(xué)習(xí)

在iOS中創(chuàng)建一個(gè)弱引用:

NSObject *myObject = [NSObject new];
__weak NSObject *weakMyObjetc = myObject;

下面是NSObject.mm中的一段源碼:

/** 
 * 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. 
 */
id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
        (location, (objc_object*)newObj);
}

我們創(chuàng)建一個(gè)弱引用對(duì)象時(shí)其實(shí)是來(lái)到這個(gè)objc_initWeak方法,它的任務(wù)是創(chuàng)建一個(gè)指向某個(gè)對(duì)象的weak指針。而且這個(gè)方法不是線程安全的似枕。location__weak指針的內(nèi)存地址拱雏,這個(gè)函數(shù)里面會(huì)修改這個(gè)地址所保存的值报账,也就是修改它指向的對(duì)象攻臀,其實(shí)就是newObj轰传。

storeWeak是一個(gè)函數(shù):

// 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)
{
  ......

這個(gè)函數(shù)的泛型定義了多種情況幔嫂,代碼有點(diǎn)長(zhǎng)辆它,為了簡(jiǎn)化我們先看插入新的值,也就是HaveNewtrue時(shí)的情況:

    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;
    }

weak_register_no_lock負(fù)責(zé)真正的插入:

    // now remember it and where it is being stored
    weak_entry_t *entry;
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer);
    } 
    else {
        weak_entry_t new_entry;
        new_entry.referent = referent;
        new_entry.out_of_line = 0;
        new_entry.inline_referrers[0] = referrer;
        for (size_t i = 1; i < WEAK_INLINE_COUNT; i++) {
            new_entry.inline_referrers[i] = nil;
        }
        
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);
    }

首先調(diào)用weak_entry_for_referent函數(shù)判斷被引用的對(duì)象在weak_table中是否已經(jīng)存在履恩。

weak_table_t

這里的weak_table_t是全局的記錄弱引用的哈希表:

/**
 * The global weak references table. Stores object ids as keys,
 * and weak_entry_t structs as their values.
 */
struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};

這里的屬性后面會(huì)提到锰茉,weak_entry_t就是這個(gè)哈希表保存的元素。

在插入之前先判斷容量是否充足weak_grow_maybe

// Grow the given zone's table of weak references if it is full.
static void weak_grow_maybe(weak_table_t *weak_table)
{
    size_t old_size = TABLE_SIZE(weak_table);

    // Grow if at least 3/4 full.
    if (weak_table->num_entries >= old_size * 3 / 4) {
        weak_resize(weak_table, old_size ? old_size*2 : 64);
    }
}

static void weak_resize(weak_table_t *weak_table, size_t new_size)
{
    size_t old_size = TABLE_SIZE(weak_table);

    weak_entry_t *old_entries = weak_table->weak_entries;
    weak_entry_t *new_entries = (weak_entry_t *)
        calloc(new_size, sizeof(weak_entry_t));

    weak_table->mask = new_size - 1;
    weak_table->weak_entries = new_entries;
    weak_table->max_hash_displacement = 0;
    weak_table->num_entries = 0;  // restored by weak_entry_insert below
    
    if (old_entries) {
        weak_entry_t *entry;
        weak_entry_t *end = old_entries + old_size;
        for (entry = old_entries; entry < end; entry++) {
            if (entry->referent) {
                weak_entry_insert(weak_table, entry);
            }
        }
        free(old_entries);
    }
}

這里擴(kuò)容是把舊的元素一個(gè)一個(gè)地插到新的數(shù)組切心。

/** 
 * Add new_entry to the object's table of weak references.
 * Does not check whether the referent is already in the table.
 */
static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{
    weak_entry_t *weak_entries = weak_table->weak_entries;
    assert(weak_entries != nil);

    size_t index = hash_pointer(new_entry->referent) & (weak_table->mask);
    size_t hash_displacement = 0;
    while (weak_entries[index].referent != nil) {
        index = (index+1) & weak_table->mask;
        hash_displacement++;
    }

    weak_entries[index] = *new_entry;
    weak_table->num_entries++;

    if (hash_displacement > weak_table->max_hash_displacement) {
        weak_table->max_hash_displacement = hash_displacement;
    }
}

weak_grow_maybe初始化table的大小為new_size = 64飒筑,二進(jìn)制就是1000000片吊,mask = new_size - 1 = 0111111,其實(shí)就是最大的下標(biāo)协屡。

假設(shè)pointer_hash = 0000000俏脊,那么index = pointer_hash&mask = 0000000。這里與mask做&運(yùn)算肤晓,而不是直接用pointer_hash當(dāng)成index爷贫,可以保證pointer_hash小于或等于mask也就是new_size - 1。

如果發(fā)生碰撞那么index = (pointer_hash + 1) & mask补憾,然后一直往后遍歷數(shù)組漫萄,到數(shù)組末尾時(shí)又回到對(duì)頭直到遍歷整個(gè)數(shù)組,這就是開(kāi)放尋址法盈匾。

每次插入entry時(shí)會(huì)記錄最大偏移量max_hash_displacement腾务,等到下次查找entry,根據(jù)計(jì)算得到的索引相繼累加查找對(duì)象威酒,如果累加次數(shù)大于max_hash_displacement就不繼續(xù)檢索了窑睁,直接返回nil,避免遍歷整個(gè)數(shù)組去找對(duì)象葵孤,浪費(fèi)性能担钮。

查找:

/** 
 * Return the weak reference table entry for the given referent. 
 * If there is no entry for referent, return NULL. 
 * Performs a lookup.
 *
 * @param weak_table 
 * @param referent The object. Must not be nil.
 * 
 * @return The table of weak referrers to this object. 
 */
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
    assert(referent);

    weak_entry_t *weak_entries = weak_table->weak_entries;

    if (!weak_entries) return nil;

    size_t index = hash_pointer(referent) & weak_table->mask;
    size_t hash_displacement = 0;
    while (weak_table->weak_entries[index].referent != referent) {
        index = (index+1) & weak_table->mask;
        hash_displacement++;
        if (hash_displacement > weak_table->max_hash_displacement) {
            return nil;
        }
    }
    
    return &weak_table->weak_entries[index];
}

在插入之前先去查找是否已經(jīng)有該對(duì)象的弱引用了,如果有的話直接插到原有的數(shù)組里:

/** 
 * Add the given referrer to set of weak pointers in this entry.
 * Does not perform duplicate checking (b/c weak pointers are never
 * added to a set twice). 
 *
 * @param entry The entry holding the set of weak pointers. 
 * @param new_referrer The new weak pointer to be added.
 */
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    if (! entry->out_of_line) {
        // Try to insert inline.
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == nil) {
                entry->inline_referrers[i] = new_referrer;
                return;
            }
        }

        // Couldn't insert inline. Allocate out of line.
        weak_referrer_t *new_referrers = (weak_referrer_t *)
            calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
        // This constructed table is invalid, but grow_refs_and_insert
        // will fix it and rehash it.
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            new_referrers[i] = entry->inline_referrers[i];
        }
        entry->referrers = new_referrers;
        entry->num_refs = WEAK_INLINE_COUNT;
        entry->out_of_line = 1;
        entry->mask = WEAK_INLINE_COUNT-1;
        entry->max_hash_displacement = 0;
    }

    assert(entry->out_of_line);

    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
        return grow_refs_and_insert(entry, new_referrer);
    }
    size_t index = w_hash_pointer(new_referrer) & (entry->mask);
    size_t hash_displacement = 0;
    while (entry->referrers[index] != NULL) {
        index = (index+1) & entry->mask;
        hash_displacement++;
    }
    if (hash_displacement > entry->max_hash_displacement) {
        entry->max_hash_displacement = hash_displacement;
    }
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    entry->num_refs++;
}

weak_entry_t

weak_entry_t的定義:

struct weak_entry_t {
    DisguisedPtr<objc_object> referent;
    union {
        struct {
            weak_referrer_t *referrers;
            uintptr_t        out_of_line : 1;
            uintptr_t        num_refs : PTR_MINUS_1;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line=0 is LSB of one of these (don't care which)
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
};

referent就是引用的對(duì)象inline_referrersreferrers都用來(lái)保存弱引用指針的地址尤仍,開(kāi)始的時(shí)候先保存到inline_referrers中箫津,等到inline_referrers滿了之后再放到inline_referrers中,當(dāng)inline_referrers快滿時(shí)進(jìn)行擴(kuò)容宰啦。

/** 
 * Grow the entry's hash table of referrers. Rehashes each
 * of the referrers.
 * 
 * @param entry Weak pointer hash set for a particular object.
 */
__attribute__((noinline, used))
static void grow_refs_and_insert(weak_entry_t *entry, 
                                 objc_object **new_referrer)
{
    assert(entry->out_of_line);

    size_t old_size = TABLE_SIZE(entry);
    size_t new_size = old_size ? old_size * 2 : 8;

    size_t num_refs = entry->num_refs;
    weak_referrer_t *old_refs = entry->referrers;
    entry->mask = new_size - 1;
    
    entry->referrers = (weak_referrer_t *)
        calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t));
    entry->num_refs = 0;
    entry->max_hash_displacement = 0;
    
    for (size_t i = 0; i < old_size && num_refs > 0; i++) {
        if (old_refs[i] != nil) {
            append_referrer(entry, old_refs[i]);
            num_refs--;
        }
    }
    // Insert
    append_referrer(entry, new_referrer);
    if (old_refs) free(old_refs);
}

當(dāng)對(duì)象被釋放時(shí) 來(lái)到這個(gè)方法:

void 
objc_clear_deallocating(id obj) 
{
    assert(obj);
    assert(!UseGC);

    if (obj->isTaggedPointer()) return;
    obj->clearDeallocating();
}

跟蹤調(diào)用最終來(lái)到

/** 
 * Called by dealloc; nils out all weak pointers that point to the 
 * provided object so that they can no longer be used.
 * 
 * @param weak_table 
 * @param referent The object being deallocated. 
 */
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);
}

這個(gè) 方法用來(lái)清除對(duì)象的弱引用苏遥,先把weak_entry_t的弱引用指針全部指向?yàn)閚il,然后在weak_table_t中移除這個(gè)weak_entry_t赡模。

總結(jié):有一個(gè)哈希表田炭,這里面保存的是對(duì)象以及它所有的弱引用指針數(shù)組,創(chuàng)建弱引用或釋放對(duì)象都是對(duì)這個(gè)表進(jìn)行操作漓柑。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末辆布,一起剝皮案震驚了整個(gè)濱河市瞬矩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌锋玲,老刑警劉巖景用,帶你破解...
    沈念sama閱讀 222,378評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異惭蹂,居然都是意外死亡伞插,警方通過(guò)查閱死者的電腦和手機(jī)割粮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蜂怎,“玉大人穆刻,你說(shuō)我怎么就攤上這事「懿剑” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 168,983評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵榜轿,是天一觀的道長(zhǎng)幽歼。 經(jīng)常有香客問(wèn)我,道長(zhǎng)谬盐,這世上最難降的妖魔是什么甸私? 我笑而不...
    開(kāi)封第一講書人閱讀 59,938評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮飞傀,結(jié)果婚禮上皇型,老公的妹妹穿的比我還像新娘。我一直安慰自己砸烦,他們只是感情好弃鸦,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,955評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著幢痘,像睡著了一般唬格。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上颜说,一...
    開(kāi)封第一講書人閱讀 52,549評(píng)論 1 312
  • 那天购岗,我揣著相機(jī)與錄音,去河邊找鬼门粪。 笑死喊积,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的玄妈。 我是一名探鬼主播乾吻,決...
    沈念sama閱讀 41,063評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼措近!你這毒婦竟也來(lái)了溶弟?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,991評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤瞭郑,失蹤者是張志新(化名)和其女友劉穎辜御,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體屈张,經(jīng)...
    沈念sama閱讀 46,522評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡擒权,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,604評(píng)論 3 342
  • 正文 我和宋清朗相戀三年袱巨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碳抄。...
    茶點(diǎn)故事閱讀 40,742評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡愉老,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出剖效,到底是詐尸還是另有隱情嫉入,我是刑警寧澤,帶...
    沈念sama閱讀 36,413評(píng)論 5 351
  • 正文 年R本政府宣布璧尸,位于F島的核電站咒林,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏爷光。R本人自食惡果不足惜垫竞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,094評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蛀序。 院中可真熱鬧欢瞪,春花似錦、人聲如沸徐裸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,572評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)倦逐。三九已至譬正,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間檬姥,已是汗流浹背曾我。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,671評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留健民,地道東北人抒巢。 一個(gè)月前我還...
    沈念sama閱讀 49,159評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像秉犹,于是被迫代替她去往敵國(guó)和親蛉谜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,747評(píng)論 2 361