Objective-C runtime機制(8)——OC對象從創建到銷燬

在我們前面的幾章中,分析了OC的runtime一些底層的數據結構以及實現機制。今天,我們就從一個OC對象的生命週期的角度,來解析在runtime底層是如何實現的。

我們創建一個對象(或對象引用)有幾種方式?

Student *student = [[Student alloc] init];
Student *student2 = [Student new];

__weak Student *weakStudent = [Student new];

NSDictionary *dict = [[NSDictionary alloc] init];
NSDictionary *autoreleaseDict = [NSDictionary dictionary];

有很多種方式,我們就來依次看一下這些方式的背後實現。

alloc

要創建一個對象,第一步就是需要爲對象分配內存。在創建內存時,我們會調用alloc方法。查看runtime的NSObject +alloc方法實現:

+ (id)alloc {
    return _objc_rootAlloc(self);
}
// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

alloc方法會將self作爲參數傳入_objc_rootAlloc(Class cls) 方法中注意,因爲alloc是一個類方法,因此此時的self是一個Class類型

最終該方法會落腳到callAlloc方法。

static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    if (slowpath(checkNil && !cls)) return nil;

#if __OBJC2__
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        // No alloc/allocWithZone implementation. Go straight to the allocator.
        // fixme store hasCustomAWZ in the non-meta class and 
        // add it to canAllocFast's summary
        if (fastpath(cls->canAllocFast())) { // 如果可以fast alloc,走這裏
            // No ctors, raw isa, etc. Go straight to the metal.
            bool dtor = cls->hasCxxDtor();
            id obj = (id)calloc(1, cls->bits.fastInstanceSize()); // 直接調用 calloc方法,申請1塊大小爲bits.fastInstanceSize()的內存
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            obj->initInstanceIsa(cls, dtor);
            return obj;
        }
        else { // 如果不可以fast alloc,走這裏,
            // Has ctor or raw isa or something. Use the slower path.
            id obj = class_createInstance(cls, 0); // (1)需要讀取cls 的class_ro_t 中的instanceSize,並使之大於16 byte, Because : CF requires all objects be at least 16 bytes. (2)initInstanceIsa
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            return obj;
        }
    }
#endif

    // No shortcuts available.
    if (allocWithZone) return [cls allocWithZone:nil];
    return [cls alloc];
}

在callAlloc方法裏面,做了三件事:

  1. 調用calloc方法,爲類實例分配內存
  2. 調用obj->initInstanceIsa(cls, dtor)方法,初始化obj的isa
  3. 返回obj

在第一件事中,調用calloc方法,你需要提供需要申請內存的大小。在OC中有兩條分支:
(1)can alloc fast
(2)can’t alloc fast

對於可以alloc fast的類,應該是經過編譯器優化的類。這種類的實例大小直接被放到了bits

struct class_data_bits_t {

    // Values are the FAST_ flags above.
    uintptr_t bits;
	...
}

而不需要通過bits找到class_rw_t->class_ro_t->instanceSize。省略了這一條查找路徑,而是直接讀取位值,其創建實例的速度自然比不能alloc fast的類要快。

而對於不能alloc fast的類,則會進入第二條路徑,代碼會通過上面所說的通過bits找到class_rw_t->class_ro_t->instanceSize來確定需要申請內存的大小。

當申請了對象的內存後,還需要初始化類實例對象的isa成員變量:

 obj->initInstanceIsa(cls, hasCxxDtor);
inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    assert(!cls->instancesRequireRawIsa());
    assert(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}
inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    assert(!isTaggedPointer()); 
    
    if (!nonpointer) { // 如果沒有啓用isa 優化,則直接將cls賦值給isa.cls,來表明當前object 是哪個類的實例
        isa.cls = cls;
    } else { // 如果啓用了isa 優化,則初始化isa的三個內容(1) isa基本的內容,包括nonpointer置1以及設置OC magic vaule (2)置位has_cxx_dtor (3) 記錄對象所屬類的信息。 通過 newisa.shiftcls = (uintptr_t)cls >> 3;
        assert(!DisableNonpointerIsa);
        assert(!cls->instancesRequireRawIsa());

        isa_t newisa(0);

#if SUPPORT_INDEXED_ISA
        assert(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif

        // This write must be performed in a single store in some cases
        // (for example when realizing a class because other threads
        // may simultaneously try to use the class).
        // fixme use atomics here to guarantee single-store and to
        // guarantee memory order w.r.t. the class index table
        // ...but not too atomic because we don't want to hurt instantiation
        isa = newisa;
    }
}

結合代碼註釋,以及我們在Objective-C runtime機制(5)——iOS 內存管理中提到的關於isa的描述,應該可以理解isa初始化的邏輯。

init

我們再來看一下init方法:

- (id)init {
    return _objc_rootInit(self);
}
id _objc_rootInit(id obj)
{
    // In practice, it will be hard to rely on this function.
    // Many classes do not properly chain -init calls.
    return obj;
}

實現很簡單,就是將自身返回,沒有做任何其他操作。

__strong

Student *student = [[Student alloc] init];
Student *student2 = [Student new];

在等號的左邊,我們通過allocnew的方式創建了兩個OC對象。而在右面,我們通過Student *的方式來引用這些對象。

在OC中,對對象所有的引用都是有所有權修飾符的,所有權修飾符會告訴編譯器,該如何處理對象的引用關係。如果代碼中沒有顯示指明所有權修飾符,則默認爲__strong所有權。

因此上面代碼實際是:

__strong Student *student = [[Student alloc] init];
__strong Student *student2 = [Student new];

對於new方法,蘋果的文檔解釋爲:

Allocates a new instance of the receiving class, sends it an initmessage, and returns the initialized object.

其實就是alloc + init 方法的簡寫。因此,這裏的兩種創建實例對象的方式可以理解是一個。

那麼,當所有權修飾符是__strong時,runtime是如何管理對象引用的呢?

runtime會通過 void objc_storeStrong(id *location, id obj) 方法來處理__strong 引用。 這裏的location就是引用指針,即Student *student,而obj就是被引用的對象,即Student實例

void objc_storeStrong(id *location, id obj)
{
    id prev = *location;
    if (obj == prev) {
        return;
    }
    objc_retain(obj); 	    //1.  retain obj
    *location = obj;		    //2.  將location 指向 obj
    objc_release(prev);   //3. release location之前指向的obj
}

代碼邏輯很簡單,主要是調用了objc_retain和objc_release兩個方法。
我們分別來看一下它們的實現。

objc_retain

id 
objc_retain(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}

inline id 
objc_object::retain()
{
    assert(!isTaggedPointer());

    if (fastpath(!ISA()->hasCustomRR())) {
        return rootRetain();
    }

    return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}

可以看到,objc_retain方法最終會調到objc_object類的rootRetain方法:

ALWAYS_INLINE id 
objc_object::rootRetain()
{
    return rootRetain(false, false);
}

ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    if (isTaggedPointer()) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {  // 如果沒有采用isa優化, 則返回sidetable記錄的內容, 用slowpath表明這不是一個大概率事件
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain();
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        if (slowpath(tryRetain && newisa.deallocating)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            return nil;
        }
        // 採用了isa優化,做extra_rc++,同時檢查是否extra_rc溢出,若溢出,則extra_rc減半,並將另一半轉存至sidetable
        uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        if (slowpath(carry)) { // 有carry值,表示extra_rc 溢出
            // newisa.extra_rc++ overflowed
            if (!handleOverflow) {  // 如果不處理溢出情況,則在這裏會遞歸調用一次,再進來的時候,handleOverflow會被rootRetain_overflow設置爲true,從而進入到下面的溢出處理流程
                ClearExclusive(&isa.bits);
                return rootRetain_overflow(tryRetain);
            }
            // Leave half of the retain counts inline and 
            // prepare to copy the other half to the side table.
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            
            // 進行溢出處理:邏輯很簡單,先在extra_rc中留一半計數,同時把has_sidetable_rc設置爲true,表明借用了sidetable,然後把另一半放到sidetable中
            sideTableLocked = true;
            transcribeToSideTable = true;
            newisa.extra_rc = RC_HALF;
            newisa.has_sidetable_rc = true;
        }
    } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits))); // 將oldisa 替換爲 newisa,並賦值給isa.bits(更新isa_t), 如果不成功,do while再試一遍

    if (slowpath(transcribeToSideTable)) { //isa的extra_rc溢出,將一半的refer count值放到sidetable中
        // Copy the other half of the retain counts to the side table.
        sidetable_addExtraRC_nolock(RC_HALF);
    }

    if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    return (id)this;
}

這一段rootRetain方法,在我們之前的文章Objective-C runtime機制(5)——iOS 內存管理已經做過分析。

我們就在總結一下rootRetain方法的流程:

  1. 取出當前對象的isa.bits值
  2. isa.bits分別賦值給oldisanewisa
  3. 根據isa_t的標誌位newisa.nonpointer,來判斷runtime是否只開啓了isa優化。
  4. 如果newisa.nonpointer爲0,則走老的流程,調用sidetable_retain方法,在SideTable中找到this對應的節點,side table refcntStorage + 1
  5. 如果newisa.nonpointer爲1,則在newisa.extra_rc上做引用計數+1操作。同時,需要判斷是否計數溢出。
  6. 如果newisa.extra_rc溢出,則進行溢出處理:newisa.extra_rc計數減半,將計數的另一半放到SideTable中。並設置newisa.has_sidetable_rc = true,表明引用計數借用了SideTable
  7. 最後,調用StoreExclusive,更新對象的isa.bits

總結:
__strong引用會使得被引用對象計數+1,同時,會使得之前的飲用對象計數-1。

__weak

__weak Student *weakStudent = [Student new];

當使用__weak所有權修飾符來引用對象時?會發生什麼呢?

當weakStudent弱引用Student對象時,會調用objc_initWeak方法。當weakStudent超出其作用域要銷燬時,會調用objc_destoryWeak方法。
我們分別看一下它們的實現:

/** 
 * 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. 
 */

// @param location __weak 指針的地址
// @param newObj 被弱引用的對象指針
// @return __weak 指針

id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}
template <HaveOld haveOld, HaveNew haveNew,
          CrashIfDeallocating 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) { // 如果weak ptr之前弱引用過一個obj,則將這個obj所對應的SideTable取出,賦值給oldTable
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil; // 如果weak ptr之前沒有弱引用過一個obj,則oldTable = nil
    }
    if (haveNew) { // 如果weak ptr要weak引用一個新的obj,則將該obj對應的SideTable取出,賦值給newTable
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil; // 如果weak ptr不需要引用一個新obj,則newTable = nil
    }
    
    // 加鎖操作,防止多線程中競爭衝突
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    // location 應該與 oldObj 保持一致,如果不同,說明當前的 location 已經處理過 oldObj 可是又被其他線程所修改
    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())  // 如果cls還沒有初始化,先初始化,再嘗試設置weak
        {
            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; // 這裏記錄一下previouslyInitializedClass, 防止改if分支再次進入

            goto retry; // 重新獲取一遍newObj,這時的newObj應該已經初始化過了
        }
    }

    // Clean up old value, if any.
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); // 如果weak_ptr之前弱引用過別的對象oldObj,則調用weak_unregister_no_lock,在oldObj的weak_entry_t中移除該weak_ptr地址
    }

    // Assign new value, if any.
    if (haveNew) { // 如果weak_ptr需要弱引用新的對象newObj
        // (1) 調用weak_register_no_lock方法,將weak ptr的地址記錄到newObj對應的weak_entry_t中
        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
        
        // (2) 更新newObj的isa的weakly_referenced bit標誌位
        // 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.
        // (3)*location 賦值,也就是將weak ptr直接指向了newObj。可以看到,這裏並沒有將newObj的引用計數+1
        *location = (id)newObj; // 將weak ptr指向object
    }
    else {
        // No new value. The storage is not changed.
    }
    
    // 解鎖,其他線程可以訪問oldTable, newTable了
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj; // 返回newObj,此時的newObj與剛傳入時相比,weakly-referenced bit位置1
}

可以看到,storeWeak函數會根據haveOld參數來決定是否需要處理weak 指針之前弱引用的對象。我們這裏的weakStudent是第一次弱引用對象(a fresh weak pointer),因此,haveOld = false。關於haveOld = false的情況,我們稍後分析。

當haveOld = false時,storeWeak函數做的事情如下:

  1. 取出引用對象對應的SideTable節點SideTable *newTable;
  2. 調用weak_register_no_lock方法,將weak pointer的地址記錄到對象對應的weak_entry_t中。
  3. 更新對象isa的weakly_referenced bit標誌位,表明該對象被弱引用了。
  4. 將weak pointer指向對象
  5. 返回對象

關於weak_register_no_lock以及weak相關的數據結構,我們在Objective-C runtime機制(6)——weak引用的底層實現原理有相關探討,就不再複述。

下面看另一種情況:

 __weak Son *son = [Son new];
 son = [Son new];

當weakStudent再次指向另一個對象時,則不會調用objc_initWeak方法,而是會調用objc_storeWeak方法:

/** 
 * This function stores a new value into a __weak variable. It would
 * be used anywhere a __weak variable is the target of an assignment.
 * 
 * @param location The address of the weak pointer itself
 * @param newObj The new object this weak ptr should now point to
 * 
 * @return \e newObj
 */
id
objc_storeWeak(id *location, id newObj)
{
    return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object *)newObj);
}

其實還是調用了storeWeak方法,只不過DontHaveOld參數換成了DoHaveOld

當傳入DoHaveOld時,storeWeak會進入分支:

  // Clean up old value, if any.
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); // 如果weak_ptr之前弱引用過別的對象oldObj,則調用weak_unregister_no_lock,在oldObj的weak_entry_t中移除該weak_ptr地址
    }
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                        id *referrer_id)
{
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;

    weak_entry_t *entry;

    if (!referent) return;

    if ((entry = weak_entry_for_referent(weak_table, referent))) { // 查找到referent所對應的weak_entry_t
        remove_referrer(entry, referrer);  // 在referent所對應的weak_entry_t的hash數組中,移除referrer
       
        // 移除元素之後, 要檢查一下weak_entry_t的hash數組是否已經空了
        bool empty = true;
        if (entry->out_of_line()  &&  entry->num_refs != 0) {
            empty = false;
        }
        else {
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i]) {
                    empty = false; 
                    break;
                }
            }
        }

        if (empty) { // 如果weak_entry_t的hash數組已經空了,則需要將weak_entry_t從weak_table中移除
            weak_entry_remove(weak_table, entry);
        }
    }

    // Do not set *referrer = nil. objc_storeWeak() requires that the 
    // value not change.
}

weak_unregister_no_lock方法中,將weak pointer的地址從對象的weak_entry_t中移除,同時會判斷weak_entry_t是否已經空了,如果空了,則需要把weak_entry_tweak_table中移除。

總結:
__weak引用對象時,會在對象的weak_entry_t中登記該weak pointer的地址(這也就是爲什麼當對象釋放時,weak pointer會被置爲nil)。如果weak pointer之前已經弱引用過其他對象,則要先將weak pointer地址從其他對象的weak_entry_t中移除,同時,需要對weak_entry_t進行判空邏輯。

autorelease

NSDictionary *dict = [[NSDictionary alloc] init];
NSDictionary *autoreleaseDict = [NSDictionary dictionary];

當我們創建NSDictionary對象時,有這麼兩種方式。那麼,這兩種方式有什麼區別呢?

在ARC時代,若方法名以下列詞語開頭,則其返回對象歸調用者所有(意爲需調用者負責釋放內存,但對ARC來說,其實並沒有手動release的必要)

  • alloc
  • new
  • copy
  • mutableCopy

而不使用這些詞語開頭的方法,如[NSDictionary dictionary]
根據蘋果官方文檔,當調用[NSDictionary dictionary]時:

This method is declared primarily for use with mutable subclasses of NSDictionary.
If you don’t want a temporary object, you can also create an empty dictionary using alloc and init.

似乎是說,當調用[NSDictionary dictionary]的形式時,會產生一個臨時的對象。類似的,還有[NSArray array], [NSData data]

關於這種形式生成的變量,則表示“方法所返回的對象並不歸調用者所有”。在這種情況下,返回的對象會自動釋放。

其實我們可以理解爲:當調用dictionary形式生成對象時,NSDictionary對象的引用計數管理,就不需要用戶參與了(這在MRC時代有很大的區別,但是對於ARC來說,其實和alloc形式沒有太大的區別了)。用[NSDictionary dictionary]其實相當於代碼

[[NSDictionary alloc] init] autorelease];

這裏會將NSDictionary對象交給了autorelease pool來管理。

事實是這樣的嗎?我們查看[NSDictionary dictionary]的彙編代碼(Product->Perform Action->Assemble),可以看到,編譯器會調用objc_retainAutoreleasedReturnValue方法。而objc_retainAutoreleasedReturnValue又是什麼鬼?這其實是編譯器的一個優化,前面我們說[NSDictionary dictionary]會在方法內部爲NSDictionary實例調用autorelease,而如果這時候在外面用一個強引用來引用這個NSDictionary對象的話,還是需要調用一個retain,而此時,的autorelease和retain其實是可以相互抵消的。於是,編譯器就給了一個優化,不是直接調用autorelease方法,而是調用objc_retainAutoreleasedReturnValue來做這樣的判斷,如果autorelease後面緊跟了retain,則將autorelease和retain都抵消掉,不再代碼裏面出現。(詳見《Effective Objective-C 2.0》 P126)。

OK,上面是一些題外話,我們回到autorelease的主題上來。在ARC時代,我們通過如下形式使用autorelease:

@autorelease {
		// do your code
}

實質上,編譯器會將如上形式的代碼轉換爲:

objc_autoreleasePoolPush();
// do your code
objc_autoreleasePoolPop();

查看它們在runtime中的定義:

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

 static inline void *push() 
    {
        id *dest;
        if (DebugPoolAllocation) {
            // Each autorelease pool starts on a new pool page.
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }
 static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) {
            return page->add(obj);
        } else if (page) {
            return autoreleaseFullPage(obj, page);
        } else {
            return autoreleaseNoPage(obj);
        }
    }

可以看到,當push到autorelease時,最終會調用到autoreleaseFast, 在autoreleaseFast中,會首先取出當前線程的hotPage,根據當前hotPage的三種狀態:

  1. hot page存在且未滿,調用page->add(obj)
  2. hot page存在但已滿, 調用autoreleaseFullPage(obj, page)
  3. hot page不存在,調用 autoreleaseNoPage(obj)

關於這三個方法的實現細節,我們在Objective-C runtime機制(5)——iOS 內存管理有詳細的分析。

當需要pop autorelease pool時,則會調用objc_autoreleasePoolPop()

void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

static inline void pop(void *token) 
    {
        AutoreleasePoolPage *page;
        id *stop;

        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            if (hotPage()) {
                // Pool was used. Pop its contents normally.
                // Pool pages remain allocated for re-use as usual.
                pop(coldPage()->begin());
            } else {
                // Pool was never used. Clear the placeholder.
                setHotPage(nil);
            }
            return;
        }

        page = pageForPointer(token);
        stop = (id *)token;
        if (*stop != POOL_BOUNDARY) {
            if (stop == page->begin()  &&  !page->parent) {
                // Start of coldest page may correctly not be POOL_BOUNDARY:
                // 1. top-level pool is popped, leaving the cold page in place
                // 2. an object is autoreleased with no pool
            } else {
                // 這是爲了兼容舊的SDK,看來在新的SDK裏面,token 可能的取值只有兩個:(1)POOL_BOUNDARY, (2)page->begin() && !page->parent也就是第一個page
                // Error. For bincompat purposes this is not
                // fatal in executables built with old SDKs.
                return badPop(token);
            }
        }

        if (PrintPoolHiwat) printHiwat();

        page->releaseUntil(stop);  // 對token之前的object,每一個都調用objc_release方法

        // memory: delete empty children
        if (DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) {
            // special case: delete everything for pop(top) 
            // when debugging missing autorelease pools
            page->kill();
            setHotPage(nil);
        } 
        else if (page->child) {
            // hysteresis: keep one empty child if page is more than half full
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }
    }

在Pop中,會根據傳入的token,調用 page->releaseUntil(stop) 方法,對每一個存儲於page上的object調用objc_release(obj)方法。

之後,還會根據當前page的狀態:page->lessThanHalfFull()或其他,來決定其child的處理方式:

  1. 如果當前page存儲的object已經不滿半頁,則講page的child釋放
  2. 如果當前page存儲的object仍滿半頁,則保留一個空的child,並且將空child之後的所有child都釋放掉。

retain count

當我們需要獲取對象的引用計數時,在ARC下可以調用如下方法:

CFGetRetainCount((__bridge CFTypeRef)(obj))

這是CF的方法調用,而在runtime中,我們可以調用NSObject的方法:

- (NSUInteger)retainCount OBJC_ARC_UNAVAILABLE;

通過註釋,可以知道在ARC環境下,該方法是不可用的,但是不影響我們瞭解它的具體實現。

- (NSUInteger)retainCount {
    return ((id)self)->rootRetainCount();
}

方法裏面講self轉爲id類型,即objc_object類型,然後調用objc_objectrootRetainCount()方法。

inline uintptr_t 
objc_object::rootRetainCount()
{
    //case 1: 如果是tagged pointer,則直接返回this,因爲tagged pointer是不需要引用計數的
    if (isTaggedPointer()) return (uintptr_t)this;

    // 將objcet對應的sidetable上鎖
    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    // case 2: 如果採用了優化的isa指針
    if (bits.nonpointer) {
        uintptr_t rc = 1 + bits.extra_rc; // 先讀取isa.extra_rc
        if (bits.has_sidetable_rc) { // 如果使用了sideTable來存儲retain count, 還需要讀取sidetable中的數據
            rc += sidetable_getExtraRC_nolock(); // 總引用計數= rc + sidetable
        }
        sidetable_unlock();
        return rc;
    }
    // case 3:如果沒采用優化的isa指針,則直接返回sidetable中的值
    sidetable_unlock();
    return sidetable_retainCount();
}

獲取retain count的方法很簡單:

  1. 判斷object是否使用了isa優化
  2. 如果使用了isa優化,先取出1 + bits.extra_rc
  3. 再判斷是否需要讀取side talbe( if (bits.has_sidetable_rc)
  4. 如果需要,則加上side table 中存儲的retain count
  5. 如果沒有使用isa優化,則直接讀取side table 中的retain count,並加1,作爲引用計數。

還有一種特殊的情況是,如果object pointer是tagged pointer,則不參與任何操作。

release

當object需要引用計數減一時,會調用release方法。

objc_object::rootRelease()
{
    return rootRelease(true, false);
}

ALWAYS_INLINE bool 
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
    if (isTaggedPointer()) return false;

    bool sideTableLocked = false;

    isa_t oldisa;
    isa_t newisa;

 retry:
    do {
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) { // 慢路徑 : 如果沒有開啓isa優化,則到sidetable中引用計數減一
            ClearExclusive(&isa.bits); // 空方法
            if (sideTableLocked) sidetable_unlock();
            return sidetable_release(performDealloc);
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        uintptr_t carry;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
        if (slowpath(carry)) { // 如果下溢出,則goto underflow
            // don't ClearExclusive()
            goto underflow;
        }
    } while (slowpath(!StoreReleaseExclusive(&isa.bits, 
                                             oldisa.bits, newisa.bits))); // 修改isa bits(如果不成功,則進入while循環,再試一把,直到成功爲止)

    if (slowpath(sideTableLocked)) sidetable_unlock();
    return false;  // 如果沒有溢出,則在這裏就會返回false(表明引用計數不等於0,沒有dealloc)

    // 只有isa.extra_rc  -1 下溢出後,纔會進入下面的代碼。下溢出有兩種情況:
    // 1. borrow from side table . isa.extra_rc 有從side table存儲。這是假溢出,只需要將side table中的RC_HALF移回到isa.extra_rc即可。並返回false
    // 2. deallocate。 這種情況是真下溢出。此時isa.extra_rc < 0,且沒有newisa.has_sidetable_rc 沒有想side table 借位。說明object引用計數==0,(1) 設置newisa.deallocating = true;
    //  (2)觸發object 的dealloc方法, (3)並返回true,表明對象deallocation
    //
        // Really deallocate.
        //    if (slowpath(newisa.deallocating)) {
        //        ClearExclusive(&isa.bits);
        //        if (sideTableLocked) sidetable_unlock();
        //        return overrelease_error();
        //        // does not actually return
        //    }
        //    newisa.deallocating = true;
        //    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
        //
        //    if (slowpath(sideTableLocked)) sidetable_unlock();
        //
        //    __sync_synchronize();
        //    if (performDealloc) {
        //        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
        //    }
        //    return true;
    //
    //
    ////////////////////////////////////////////////////////////////////////////////////////////////////////
 underflow:
    // newisa.extra_rc-- underflowed: borrow from side table or deallocate

    // abandon newisa to undo the decrement
    newisa = oldisa;

    if (slowpath(newisa.has_sidetable_rc)) {  // 如果借用了 sideTable 做 rc,走這裏
        if (!handleUnderflow) {
            ClearExclusive(&isa.bits);
            return rootRelease_underflow(performDealloc);
        }

        // Transfer retain count from side table to inline storage.

        if (!sideTableLocked) {
            ClearExclusive(&isa.bits); // ClearExclusive 是一個空函數
            sidetable_lock();
            sideTableLocked = true;
            // Need to start over to avoid a race against 
            // the nonpointer -> raw pointer transition.
            goto retry;
        }

        // 如果extra_rc 減1後,其值carryout(小於0),則要處理side table,如果之前有在side talbe中借位RC_HALF,則把這RC_HALF在拿回來到extrc_rc中,並保留side table剩下的值
        // Try to remove some retain counts from the side table.        
        size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF); 

        // To avoid races, has_sidetable_rc must remain set 
        // even if the side table count is now zero.

        if (borrowed > 0) {
            // Side table retain count decreased.
            // Try to add them to the inline count.
            newisa.extra_rc = borrowed - 1;  // redo the original decrement too
            bool stored = StoreReleaseExclusive(&isa.bits, 
                                                oldisa.bits, newisa.bits);
            if (!stored) {
                // Inline update failed. 
                // Try it again right now. This prevents livelock on LL/SC 
                // architectures where the side table access itself may have 
                // dropped the reservation.
                isa_t oldisa2 = LoadExclusive(&isa.bits);
                isa_t newisa2 = oldisa2;
                if (newisa2.nonpointer) {
                    uintptr_t overflow;
                    newisa2.bits = 
                        addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
                    if (!overflow) {
                        stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits, 
                                                       newisa2.bits);
                    }
                }
            }

            if (!stored) {
                // Inline update failed.
                // Put the retains back in the side table.
                sidetable_addExtraRC_nolock(borrowed); // 如果更新 isa extra_rc 失敗,則把side table中的數再放回去 (好尷尬),然後再試一把
                goto retry;
            }

            // Decrement successful after borrowing from side table.
            // This decrement cannot be the deallocating decrement - the side 
            // table lock and has_sidetable_rc bit ensure that if everyone 
            // else tried to -release while we worked, the last one would block.
            sidetable_unlock();
            return false;
        }
        else {
            // Side table is empty after all. Fall-through to the dealloc path.
        }
    }

    // Really deallocate.

    if (slowpath(newisa.deallocating)) {
        ClearExclusive(&isa.bits);
        if (sideTableLocked) sidetable_unlock();
        return overrelease_error();
        // does not actually return
    }
    newisa.deallocating = true;
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;

    if (slowpath(sideTableLocked)) sidetable_unlock();

    __sync_synchronize();
    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return true;
}

這裏的邏輯主要有兩塊:

  1. 如果沒有使用isa.extra_rc作引用計數,則調用sidetable_release,該方法會到side table中做計數減一,同時,會check 計數是否爲0,如果爲0,則調用對象的dealloc方法。
  2. 如果使用了isa.extra_rc作引用計數,則在isa.extra_rc中做引用計數減一,同時需要判斷是否下溢出(carry > 0)
 newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--

這裏要注意處理下溢出的邏輯:

  1. 首先,下溢出是針對isa.extra_rc來說的。也就是啓用了isa優化引用計數纔會走到 underflow: 代碼段。
  2. 造成isa.extra_rc下溢出其實有兩個原因:borrow from side table or deallocate。要注意對這兩個下溢出原因的不同處理。

dealloc

當對象引用計數爲0時,會調用對象的dealloc方法,這在上面的release方法中,是通過

((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);

來調用的。

我們來看一下NSObjectdealloc方法是怎樣實現的:

- (void)dealloc {
    _objc_rootDealloc(self);
}

void _objc_rootDealloc(id obj)
{
    assert(obj);
    obj->rootDealloc();
}
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))
    {
        // 如果沒有weak引用 & 沒有關聯對象 & 沒有c++析構 & 沒有side table借位
        // 就直接free 
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}
id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);    // step 1. 先調用runtime的objc_destructInstance
    free(obj);  // step 2. free 掉這個obj

    return nil;
}
/***********************************************************************
* 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); // 調用C++析構函數
        if (assoc) _object_remove_assocations(obj); // 移除所有的關聯對象,並將其自身從Association Manager的map中移除
        obj->clearDeallocating(); // 清理相關的引用
    }

    return obj;
}

在對象dealloc的過程中,會根據當前對象isa_t的各個標誌位,來做對應的清理工作,清理完畢後,會調用free(obj)來釋放內存。

清理工作會在objc_destructInstance方法中進行,主要包括:

  • 如果有C++析構函數,調用C++析構
  • 如果有關聯對象,調用_object_remove_assocations(obj)將關聯在該對象的對象移除
  • 調用obj->clearDeallocating()方法,主要是(1)將weak 引用置爲nil,並在weak_table_t中刪除對象節點。(2)如果有side table計數借位,則side table中對應的節點移除

總結

本篇文章從[[NSObject alloc] init]方法說起,講解了alloc,init背後的實現邏輯,以及OC中的所有權修飾符__strong, __weak。並講述了autoreleasepool的背後實現。同時,分析了retain 和 release引用計數相關函數。

最終,我們分析了對象dealloc所做的清理工作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章