內(nèi)存管理之引用計數(shù)

什么是引用計數(shù)合冀?

引用計數(shù)是最普遍的垃圾回收策略之一窥翩。每一個對象都會有一個額外的計數(shù)值來表示當前被引用的次數(shù)业岁。有新的引用,這個值就會+1寇蚊;結(jié)束引用叨襟,這個值會自動-1,直到計數(shù)值為0時幔荒,對象所指的內(nèi)存塊就會廢棄掉被系統(tǒng)回收糊闽,從而達到釋放內(nèi)存的目的。

在ARC還沒引入之前爹梁,引用計數(shù)大都是Coder該思考的事情右犹,所以我們可以先關(guān)掉項目的ARC來深入了解蘋果的內(nèi)存管理機制。(花括號 "{ }" 表示一個生命周期)

{
    // 初始化一個對象并被obj持有 reference count = 1
    NSObject *obj = [[NSObject alloc] init];
    
    // newObj 持有后姚垃,reference count = 2
    NSObject *newObj = [obj retain];
    
    // 連續(xù)釋放對象
    [newObj release];
    [obj release];
    
    /// 兩步release釋放操作的是同一個對象
    /// reference count = 0, 對象被廢棄回收
}

如何操作引用計數(shù)念链?

上一步操作中,其實已經(jīng)涉及到如何操作引用計數(shù)了积糯。顯然掂墓,在Foundation框架中,NSObject 類負擔了內(nèi)存管理的職責看成,大部分對象都是繼承自 NSObject君编。

+[NSObject alloc]        // 生成對象
-[NSObject retain]       // 持有對象,增加一個新的引用
-[NSObject release]      // 釋放對象川慌,減少一個引用
-[NSObject dealloc]      // 廢棄對象

從上面的方法中可以看出吃嘿,生成對象和持有對象是兩個完全獨立的過程祠乃,很多一開始接觸代碼就用ARC的童鞋會把這兩個過程搞混淆,并且以為是一個整體兑燥。蘋果主要有兩大類方式來管理生成和持有對象亮瓷。第一種是所有童鞋都知道的以 alloc / new / copy / mutableCopy 等開頭的初始化方法名,這種是自己生成即持有降瞳。除了第一種以外的初始化方法嘱支,比如 +[NSArray array], 就是第二種,這種是別人生成挣饥,自己持有斗塘,有點類似于寄生,也是被大部分童鞋所忽略的亮靴。通過舉栗子來分析兩者的差別。

第一種生成即持有的栗子:

{
    // 以 alloc 等字眼為開頭的初始化方法生成即持有
    NSArray *obj = [[NSArray alloc] init];
    
    // 釋放 obj
    [obj release];
    
}

第二種生成不持有的栗子:

{
    // newObj 不持有新生成的對象
    NSArray *newObj = [NSArray array];
   
    // 通過調(diào)用 retain 來持有非自己生成的對象
    [newObj retain];
    
    // 釋放對象于置。如果之前未調(diào)用retain茧吊,會導致程序崩潰
    [newObj release];
}

第二種生成方式需要顯示地調(diào)用 -retain 才能真正持有對象,實際上 NSArray 的靜態(tài)初始化方法八毯,是調(diào)用了[[NSArray alloc] init]搓侄,但是為什么我們還要手動再調(diào)一次 -retain 呢?话速,蘋果在這里用了自動釋放池 @autoreleasepool 讶踪,簡單地說就是注冊到釋放池的對象會隨著釋放池生命周期結(jié)束而自動釋放(如果博主勤快的話,自動釋放池會在后續(xù)的章節(jié)里詳細闡述)泊交。

+ (instancetype)array {
    // 這是一種通認的實現(xiàn)方式
    NSArray *obj = [[[NSArray alloc] init] autorelease];
    return obj;
    
    /// 新生成的對象實際上是由自動釋放池釋放
    /// 這邊引入釋放池可以保證自己生成對象由自己釋放
}

引用計數(shù)原理

蘋果管理引用計數(shù)的方法是通過哈希表來實現(xiàn)的乳讥,具體實現(xiàn)我們來看下蘋果的源碼 ,下面提取的源碼已進行過簡化整合廓俭,并刪除一些判斷條件以及表鎖云石。前面已經(jīng)提到,引用計數(shù)的操作是NSObject負責的研乒,我們可以在runtime/NSObject.mm類文件中找到相關(guān)源碼汹忠。

我們先看下 -[NSObject retain] 是如果實現(xiàn)增加引用計數(shù)的:

- (id)retain {
    // 獲取對象哈希表
    SideTable& table = SideTables()[this];
    
    // 獲取對象當前的引用計數(shù)
    size_t& refcntStorage = table.refcnts[this];
    
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
        // 如果當前引用計數(shù)未越界,增加引用計數(shù)
        refcntStorage += SIDE_TABLE_RC_ONE;
        return (id)this;
    }
    
    // 這一步是蘋果做的優(yōu)化
    // 如果前面操作引用計數(shù)失敗雹熬,會進入sidetable_retain_slow函數(shù)
    // sidetable_retain_slow 做的是前面類似的工作
    return sidetable_retain_slow(table);
}

相對于 -[NSObject retain]宽菜,-[NSObject release] 會多一步判斷引用計數(shù)和釋放內(nèi)存塊的過程:

- (oneway void)release {
    // 獲取對象哈希表
    SideTable& table = SideTables()[this];
    
    bool do_dealloc = false;

    if (table.trylock()) {
    
        // 獲取對象引用計數(shù)
        RefcountMap::iterator it = table.refcnts.find(this);
        
        if (it == table.refcnts.end()) {
            // 引用計數(shù)為0后設(shè)置delloc標記
            do_dealloc = true;
            
            // 重置引用計數(shù)
            table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
            
        } else if (it->second < SIDE_TABLE_DEALLOCATING) {
            // 蘋果還有一套弱引用管理機制,這里暫不討論
            // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
            do_dealloc = true;
            it->second |= SIDE_TABLE_DEALLOCATING;
            
        } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
        
            // 引用計數(shù)遞減
            it->second -= SIDE_TABLE_RC_ONE;
        }
        table.unlock();
            
        // release 傳進來的 performDealloc = true
        if (do_dealloc  &&  performDealloc) {
            // 廢棄對象
            ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
        }
        
        return do_dealloc;
    }

    return sidetable_release_slow(table, performDealloc);
}

另外竿报,分析下蘋果的源碼铅乡,就會發(fā)現(xiàn)蘋果對引用計數(shù)做的一些優(yōu)化:

  1. 使用哈希表管理引用計數(shù),可以通過鍵值對追溯到對象的內(nèi)存塊烈菌,在內(nèi)存泄露的時候很容易定位到問題的源頭;
  2. 哈希表是分段的隆判,也就是說犬庇,查找指定對象地址時,會先去查找對象的一個范圍區(qū)間侨嘀,再確定具體地址臭挽。類似于圖書館里的書都是分類整理的;
  3. 哈希表加入鎖,線程安全咬腕;
  4. 操作引用計數(shù)失敗欢峰,會有備案操作。
(經(jīng)驗水平有限涨共,如有錯誤歡迎指正)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末纽帖,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子举反,更是在濱河造成了極大的恐慌懊直,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,599評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件火鼻,死亡現(xiàn)場離奇詭異室囊,居然都是意外死亡,警方通過查閱死者的電腦和手機魁索,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評論 3 385
  • 文/潘曉璐 我一進店門融撞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人粗蔚,你說我怎么就攤上這事尝偎。” “怎么了鹏控?”我有些...
    開封第一講書人閱讀 158,084評論 0 348
  • 文/不壞的土叔 我叫張陵致扯,是天一觀的道長。 經(jīng)常有香客問我当辐,道長急前,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,708評論 1 284
  • 正文 為了忘掉前任瀑构,我火速辦了婚禮裆针,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘寺晌。我一直安慰自己世吨,他們只是感情好,可當我...
    茶點故事閱讀 65,813評論 6 386
  • 文/花漫 我一把揭開白布呻征。 她就那樣靜靜地躺著耘婚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪陆赋。 梳的紋絲不亂的頭發(fā)上沐祷,一...
    開封第一講書人閱讀 50,021評論 1 291
  • 那天嚷闭,我揣著相機與錄音,去河邊找鬼赖临。 笑死胞锰,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的兢榨。 我是一名探鬼主播嗅榕,決...
    沈念sama閱讀 39,120評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼吵聪!你這毒婦竟也來了凌那?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,866評論 0 268
  • 序言:老撾萬榮一對情侶失蹤吟逝,失蹤者是張志新(化名)和其女友劉穎帽蝶,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體块攒,經(jīng)...
    沈念sama閱讀 44,308評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡励稳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,633評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了局蚀。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,768評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡恕稠,死狀恐怖琅绅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鹅巍,我是刑警寧澤千扶,帶...
    沈念sama閱讀 34,461評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站骆捧,受9級特大地震影響澎羞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜敛苇,卻給世界環(huán)境...
    茶點故事閱讀 40,094評論 3 317
  • 文/蒙蒙 一妆绞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧枫攀,春花似錦括饶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蹦掐,卻和暖如春技羔,著一層夾襖步出監(jiān)牢的瞬間僵闯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評論 1 267
  • 我被黑心中介騙來泰國打工藤滥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鳖粟,地道東北人。 一個月前我還...
    沈念sama閱讀 46,571評論 2 362
  • 正文 我出身青樓超陆,卻偏偏與公主長得像牺弹,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子时呀,可洞房花燭夜當晚...
    茶點故事閱讀 43,666評論 2 350

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

  • 1.1 什么是自動引用計數(shù) 概念:在 LLVM 編譯器中設(shè)置 ARC(Automaitc Reference Co...
    __silhouette閱讀 5,120評論 1 17
  • 29.理解引用計數(shù) Objective-C語言使用引用計數(shù)來管理內(nèi)存谨娜,也就是說航攒,每個對象都有個可以遞增或遞減的計數(shù)...
    Code_Ninja閱讀 1,477評論 1 3
  • 什么是引用計數(shù) 原理 當我們創(chuàng)建一個新對象的時候,它的引用計數(shù)為1趴梢;當有一個新的指針指向這個對象時漠畜,我們將其引用計...
    陳炯閱讀 294評論 1 0
  • 內(nèi)存管理 簡述OC中內(nèi)存管理機制。與retain配對使用的方法是dealloc還是release坞靶,為什么憔狞?需要與a...
    丶逐漸閱讀 1,958評論 1 16
  • 萍蹤俠影少年行,犯禁須得化境功彰阴; 著我真心書義字瘾敢,管他假意走偏鋒。 江湖兄弟無遺憾尿这,段氏阿朱共鬼雄簇抵; 死志英杰傳美...
    銓齋閱讀 435評論 8 10