iOS內(nèi)存管理-week和關(guān)聯(lián)對(duì)象怎么釋放(1)

iOS是通過(guò)引用計(jì)數(shù)來(lái)管理對(duì)象的生命周期的,當(dāng)引用計(jì)數(shù)起為0的時(shí)候會(huì)調(diào)用delloc函數(shù)釋放該對(duì)象泻帮,在64bit中添寺,引用計(jì)數(shù)可以直接存儲(chǔ)在優(yōu)化過(guò)的isa指針中,也可能存儲(chǔ)在SideTable類中:

image.png
  • refcnts 是一個(gè)存放著對(duì)象引用計(jì)數(shù)的散列表,當(dāng)isa中存儲(chǔ)不下的時(shí)候會(huì)存儲(chǔ)在refcnts中捞蛋。執(zhí)行SideTable_release操作的時(shí)候引用計(jì)數(shù)會(huì)減一,引用計(jì)數(shù)器為0的時(shí)候會(huì)通過(guò)message_send執(zhí)行delloc函數(shù)柬姚,會(huì)在refcnts表中找到對(duì)象并釋放拟杉。

  • weak_table 一個(gè)全局的weak 引用哈希表:存放弱引用指針,會(huì)在弱引用的對(duì)象釋放的時(shí)候量承,通過(guò)該弱引用對(duì)象的地址找到弱引用指針搬设,釋放并至為nil穴店;

面試

  • runtime 是怎么實(shí)現(xiàn)weak置nil的
  • weak修飾的釋放則自動(dòng)被置為nil的實(shí)現(xiàn)原理
  • ARC幫我們做了什么?

delloc方法釋放對(duì)象

當(dāng)一個(gè)對(duì)象的引用計(jì)數(shù)為0的時(shí)候會(huì)執(zhí)行delloc函數(shù):

// Replaced by NSZombies
- (void)dealloc {
    _objc_rootDealloc(self);
}

void
_objc_rootDealloc(id obj)
{
    assert(obj);

    obj->rootDealloc();
}

然后會(huì)通過(guò)對(duì)象的isa指針判斷對(duì)象是否有弱引用,C++析構(gòu)函數(shù)拿穴,關(guān)聯(lián)對(duì)象泣洞,如果沒(méi)有直接執(zhí)行free()釋放該對(duì)象,如果有執(zhí)行object_dispose();

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

object_dispose在執(zhí)行free(obj)釋放對(duì)象前默色,會(huì)執(zhí)行objc_destructInstance(obj)釋放關(guān)聯(lián)對(duì)象球凰,弱引用,C++析構(gòu)函數(shù)

/***********************************************************************
* object_dispose
* fixme
* Locking: none
**********************************************************************/
id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}

釋放弱引用腿宰,C++析構(gòu)函數(shù)呕诉,然后執(zhí)行clearDeallocating釋放關(guān)聯(lián)對(duì)象;

/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory. 
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is nil.
**********************************************************************/
void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

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

    return obj;
}

根據(jù)isa.nonpointer判斷isa類型是否是64位優(yōu)化后的指針吃度;

inline void 
objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
        sidetable_clearDeallocating();
    }
    else if (slowpath(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());
}

weak_clear_no_lock(&table.weak_table, (id)this);獲取全局weak_table表甩挫, 并把對(duì)象地址傳入,通過(guò)對(duì)象地址&table 算出散列表中的下標(biāo)椿每,然后清除引用的對(duì)象捶闸;

// Slow path of clearDeallocating() 
// for objects with nonpointer 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.nonpointer  &&  (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();
}

通過(guò)這個(gè)方法獲取entry
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
然后執(zhí)行 weak_entry_remove(weak_table, entry) 從weak_table表中移除,然后執(zhí)行free()釋放該對(duì)象拖刃;

/** 
 * 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);
}

/** 
 * 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 begin = hash_pointer(referent) & weak_table->mask;
    size_t index = begin;
    size_t hash_displacement = 0;
    while (weak_table->weak_entries[index].referent != referent) {
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_table->weak_entries);
        hash_displacement++;
        if (hash_displacement > weak_table->max_hash_displacement) {
            return nil;
        }
    }
    
    return &weak_table->weak_entries[index];
}

面試

  • weak修飾的釋放則自動(dòng)被置為nil的實(shí)現(xiàn)原理?

1贪绘,創(chuàng)建person對(duì)象兑牡, 并被person2弱引用了,所以person2這個(gè)弱引用指針就會(huì)放在全局的weak_table表中税灌;

2均函,當(dāng)釋放要person對(duì)象的時(shí)候,通過(guò)runtime去weak_table表中找到person的弱引用person2菱涤,然后釋放person2這個(gè)弱引用指針苞也,并且設(shè)置person2=nil,然后再釋放person對(duì)象;

  __strong Person *person1;
  __weak Person *person2;
  __unsafe_unretained Person *person3;
    
    NSLog(@"111");
    
    {
        Person *person = [[Person alloc] init];
        
        person2 = person;
    }
    
    NSLog(@"222 - %@", person2);


 111
 [Person dealloc]
 222 - (null)

特別補(bǔ)充一點(diǎn)粘秆,如有需要會(huì)通過(guò)erase函數(shù)擦掉SideTable表中person的引用計(jì)數(shù):

 if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
  • ARC幫我們做了什么?

ARC 其實(shí)就是LLVM + runtime如迟,LLVM會(huì)在編譯階段自動(dòng)幫我們添加[object release],然后會(huì)在runtime階段根據(jù)引用計(jì)數(shù)判斷攻走,當(dāng)引用計(jì)數(shù)為0的時(shí)候會(huì)通過(guò)isa指針的信息抹除對(duì)象的一些引用殷勘,這些都是runtime的功勞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末昔搂,一起剝皮案震驚了整個(gè)濱河市玲销,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌摘符,老刑警劉巖贤斜,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件策吠,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡瘩绒,警方通過(guò)查閱死者的電腦和手機(jī)猴抹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)草讶,“玉大人洽糟,你說(shuō)我怎么就攤上這事《檎剑” “怎么了坤溃?”我有些...
    開(kāi)封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)嘱丢。 經(jīng)常有香客問(wèn)我薪介,道長(zhǎng),這世上最難降的妖魔是什么越驻? 我笑而不...
    開(kāi)封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任汁政,我火速辦了婚禮,結(jié)果婚禮上缀旁,老公的妹妹穿的比我還像新娘记劈。我一直安慰自己,他們只是感情好并巍,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布目木。 她就那樣靜靜地躺著,像睡著了一般懊渡。 火紅的嫁衣襯著肌膚如雪刽射。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天剃执,我揣著相機(jī)與錄音誓禁,去河邊找鬼。 笑死肾档,一個(gè)胖子當(dāng)著我的面吹牛摹恰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播怒见,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼戒祠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了速种?” 一聲冷哼從身側(cè)響起姜盈,我...
    開(kāi)封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎配阵,沒(méi)想到半個(gè)月后馏颂,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體示血,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年救拉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了难审。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡亿絮,死狀恐怖告喊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情派昧,我是刑警寧澤黔姜,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站蒂萎,受9級(jí)特大地震影響秆吵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜五慈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一纳寂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧泻拦,春花似錦毙芜、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至陆错,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間金赦,已是汗流浹背音瓷。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留夹抗,地道東北人绳慎。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像漠烧,于是被迫代替她去往敵國(guó)和親杏愤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容