iOS - 老生常談內存管理(四):內存管理方法源碼分析

走進蘋果源碼分析內存管理方法的實現

前面咱們只是講解了內存管理方法的使用以及使用注意,那麼這些方法的內部實現究竟是怎樣的?引用計數具體又是怎樣管理的呢?接下來咱們走進Runtime最新源碼objc4-779.1(寫該文章時的最新),分析allocretainCountretainreleasedealloc等方法的實現。c++

源碼下載地址:opensource.apple.com/tarballs/ob…程序員

alloc

alloc方法的函數調用棧爲:算法

// NSObject.mm
① objc_alloc
② callAlloc
// objc-runtime-new.mm
③ _objc_rootAllocWithZone
④ _class_createInstanceFromZone
⑤ calloc、
// objc-object.h
  initInstanceIsa->initIsa
複製代碼

① objc_alloc

// Calls [cls alloc].
id
objc_alloc(Class cls)
{
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
複製代碼

② callAlloc

// Call [cls alloc] or [cls allocWithZone:nil], with appropriate 
// shortcutting optimizations.
// 調用 [cls alloc] or [cls allocWithZone:nil] 會來到這個函數,使用適當的快捷方式優化
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
// 若是是 __OBJC2__ 代碼(判斷當前語言是不是 Objective-C 2.0)
#if __OBJC2__ 
    // 若是 (checkNil && !cls),直接返回 nil
    if (slowpath(checkNil && !cls)) return nil;  
    // 若是 cls 沒有實現自定義 allocWithZone 方法,調用 _objc_rootAllocWithZone
    if (fastpath(!cls->ISA()->hasCustomAWZ())) { 
        return _objc_rootAllocWithZone(cls, nil);
    }
#endif
       
    // No shortcuts available. 
    // 沒有可用的快捷方式
    // 若是 allocWithZone 爲 true,給 cls 發送 allocWithZone:nil 消息
    if (allocWithZone) { 
        return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
    }
    // 不然發送 alloc 消息
    return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc)); 
}
複製代碼

備註: slowpath & fastpath
這兩個宏的定義以下:數組

#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))
複製代碼

它們都使用了__builtin_expect()app

long __builtin_expect(long exp, long c);
複製代碼

__builtin_expect()是 GCC (version >= 2.96)提供給程序員使用的,因爲大部分程序員在分支預測方面作得很糟糕,因此 GCC 提供這個內建函數來幫助程序員處理分支預測,目的是將 「分支轉移」 的信息提供給編譯器,這樣編譯器能夠對代碼進行優化,以減小指令跳轉帶來的性能降低。它的意思是:exp == c的機率很大。
fastpath(x)表示x1的機率很大,slowpath(x)表示x0的機率很大。它和if一塊兒使用,if (fastpath(x))表示執行if語句的可能性大,if (slowpath(x))表示執行if語句的可能性小。less

callAlloc函數中主要執行如下步驟:
一、判斷類有沒有實現自定義allocWithZone方法,若是沒有,就調用_objc_rootAllocWithZone函數(這屬於快捷方式)。
二、若是不能使用快捷方式(即第 1 步條件不成立),根據allocWithZone的值給cls類發送消息。因爲allocWithZone傳的false,則給cls發送alloc消息。ide

咱們先來看一下第二種狀況,就是給cls發送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*/);
}
複製代碼

小朋友,你是否有不少問號?它怎麼又調用了callAlloc?但不一樣的是,此次傳參不同:源碼分析

  • checkNilfalsecheckNil做用是是否須要判空,因爲第一次調用該函數時已經進行判空操做了,因此此次傳false
  • allocWithZonetrue,因此接下來會給對象發送allocWithZone:nil消息。
// Replaced by ObjectAlloc
+ (id)allocWithZone:(struct _NSZone *)zone {
    return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}
複製代碼

能夠看到,第一種(快捷方式)和第二種(非快捷方式)調用的都是_objc_rootAllocWithZone函數,且傳參都是clsnilpost

備註: 在 ARC 下 NSZone 已被忽略。
《iOS - 老生常談內存管理(三):ARC 面世 —— ARC 實施新規則》章節中已經提到,對於如今的運行時系統(編譯器宏 __ OBJC2 __ 被設定的環境),不論是MRC仍是ARC下,區域(NSZone)都已單純地被忽略。因此如今allocWithZonealloc方法已經沒有區別。

③ _objc_rootAllocWithZone

// objc-runtime-new.mm
NEVER_INLINE
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
    // allocWithZone under __OBJC2__ ignores the zone parameter
    // allocWithZone 在 __OBJC2__ 下忽略 zone 參數
    return _class_createInstanceFromZone(cls, 0, nil,
                                         OBJECT_CONSTRUCT_CALL_BADALLOC);
}
複製代碼

該函數中調用了_class_createInstanceFromZone函數,能夠發現,參數zone已被忽略,直接傳nil

④ _class_createInstanceFromZone

/*********************************************************************** * class_createInstance * fixme * Locking: none * * Note: this function has been carefully written so that the fastpath * takes no branch. **********************************************************************/
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{
    ASSERT(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor(); // 獲取 cls 是否有構造函數
    bool hasCxxDtor = cls->hasCxxDtor();                 // 獲取 cls 是否有析構函數
    bool fast = cls->canAllocNonpointer();               // 獲取 cls 是否能夠分配 nonpointer,若是是的話表明開啓了內存優化 
    size_t size;

    // 獲取須要申請的空間大小
    size = cls->instanceSize(extraBytes);  
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    // zone == nil,調用 calloc 來申請內存空間
    if (zone) { 
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {    
        obj = (id)calloc(1, size);
    }
    // 若是內存空間申請失敗,調用 callBadAllocHandler
    if (slowpath(!obj)) { 
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    // 初始化 isa。若是是 nonpointer,就調用 initInstanceIsa
    if (!zone && fast) { 
        obj->initInstanceIsa(cls, hasCxxDtor); 
    } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    // 若是 cls 沒有構造函數,直接返回對象
    if (fastpath(!hasCxxCtor)) {
        return obj;
    }
    // 進行構造函數的處理,再返回
    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}
複製代碼

_class_createInstanceFromZone函數中,經過調用 C 函數calloc來申請內存空間,並初始化對象的isa

接着咱們來看一下初始化對象isa(nonpointer)的過程。

⑤ initInstanceIsa

// objc-object.h
inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}
複製代碼

initIsa

// objc-config.h
// Define SUPPORT_INDEXED_ISA=1 on platforms that store the class in the isa 
// field as an index into a class table.
// Note, keep this in sync with any .s files which also define it.
// Be sure to edit objc-abi.h as well.
#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif

// objc-object.h
inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    ASSERT(!isTaggedPointer()); 
    
    if (!nonpointer) {
        isa = isa_t((uintptr_t)cls);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());

        isa_t newisa(0);

#if SUPPORT_INDEXED_ISA // 對於 64 位系統,該值爲 0
        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 的 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;
    }
}
複製代碼

initIsa方法中將isabits賦值爲ISA_MAGIC_VALUE。源碼註釋寫的是ISA_MAGIC_VALUE初始化了isamagicnonpointer字段,下面咱們加以驗證。

#if SUPPORT_PACKED_ISA

    // extra_rc must be the MSB-most field (so it matches carry/overflow flags)
    // nonpointer must be the LSB (fixme or get rid of it)
    // shiftcls must occupy the same bits that a real class pointer would
    // bits + RC_ONE is equivalent to extra_rc + 1
    // RC_HALF is the high bit of extra_rc (i.e. half of its range)

    // future expansion:
    // uintptr_t fast_rr : 1; // no r/r overrides
    // uintptr_t lock : 2; // lock for atomic property, @synch
    // uintptr_t extraBytes : 1; // allocated with extra bytes

# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL // here
# define ISA_BITFIELD \
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
# define RC_ONE (1ULL<<45) # define RC_HALF (1ULL<<18) # elif __x86_64__ # define ISA_MASK 0x00007ffffffffff8ULL # define ISA_MAGIC_MASK 0x001f800000000001ULL # define ISA_MAGIC_VALUE 0x001d800000000001ULL # define ISA_BITFIELD \ uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ uintptr_t has_cxx_dtor : 1; \ uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \ uintptr_t magic : 6; \ uintptr_t weakly_referenced : 1; \ uintptr_t deallocating : 1; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 8 # define RC_ONE (1ULL<<56) # define RC_HALF (1ULL<<7) # else # error unknown architecture for packed isa # endif // SUPPORT_PACKED_ISA #endif 複製代碼

__arm64__下,ISA_MAGIC_VALUE的值爲0x000001a000000001ULL

對應到ISA_BITFIELD中,ISA_MAGIC_VALUE確實是用於初始化isamagicnonpointer字段。

在初始化isa的時候,並無對extra_rc進行操做。也就是說alloc方法實際上並無設置對象的引用計數值爲 1。

Why? alloc 竟然沒有讓引用計數值爲 1?
不急,咱們先留着疑問分析其它內存管理方法。

小結: alloc方法通過一系列的函數調用棧,最終經過調用 C 函數calloc來申請內存空間,並初始化對象的isa,但並無設置對象的引用計數值爲 1。

init

// NSObject.mm
// Calls [[cls alloc] init].
id
objc_alloc_init(Class cls)
{
    return [callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/) 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;
}
複製代碼

基類的init方法啥都沒幹,只是將alloc建立的對象返回。咱們能夠重寫init方法來對alloc建立的實例作一些初始化操做。

new

// Calls [cls new]
id
objc_opt_new(Class cls)
{
#if __OBJC2__
    if (fastpath(cls && !cls->ISA()->hasCustomCore())) {
        return [callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/) init];
    }
#endif
    return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(new));
}

+ (id)new {
    return [callAlloc(self, false/*checkNil*/) init];
}
複製代碼

new方法很簡單,只是嵌套了allocinit

copy & mutableCopy

- (id)copy {
    return [(id)self copyWithZone:nil];
}

- (id)mutableCopy {
    return [(id)self mutableCopyWithZone:nil];
}
複製代碼

copymutableCopy也很簡單,只是調用了copyWithZonemutableCopyWithZone方法。

retainCount

咱們都知道,retainCount方法是取出對象的引用計數值。那麼,它是從哪裏取值,怎麼取值的呢?相信大家已經想到了,isaSidetable,下面咱們進入源碼看看它的取值過程。

retainCount方法的函數調用棧爲:

// NSObject.mm
① retainCount
② _objc_rootRetainCount
// objc-object.h
③ rootRetainCount
// NSObject.mm
④ sidetable_getExtraRC_nolock / sidetable_retainCount
複製代碼

① retainCount

- (NSUInteger)retainCount {
    return _objc_rootRetainCount(self);
}
複製代碼

② _objc_rootRetainCount

uintptr_t
_objc_rootRetainCount(id obj)
{
    ASSERT(obj);

    return obj->rootRetainCount();
}
複製代碼

③ rootRetainCount

inline uintptr_t 
objc_object::rootRetainCount()
{
    // 若是是 tagged pointer,直接返回 this
    if (isTaggedPointer()) return (uintptr_t)this; 

    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits); // 獲取 isa 
    ClearExclusive(&isa.bits);
    // 若是 isa 是 nonpointer
    if (bits.nonpointer) { 
        uintptr_t rc = 1 + bits.extra_rc; // 引用計數 = 1 + isa 中 extra_rc 的值
        // 若是還額外使用 sidetable 存儲引用計數
        if (bits.has_sidetable_rc) { 
            rc += sidetable_getExtraRC_nolock(); // 加上 sidetable 中引用計數的值
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    // 若是 isa 不是 nonpointer,返回 sidetable_retainCount() 的值
    return sidetable_retainCount(); 
}
複製代碼

④ sidetable_getExtraRC_nolock / sidetable_retainCount

size_t 
objc_object::sidetable_getExtraRC_nolock()
{
    ASSERT(isa.nonpointer);
    SideTable& table = SideTables()[this];               // 得到 SideTable
    RefcountMap::iterator it = table.refcnts.find(this); // 得到 refcnts
    if (it == table.refcnts.end()) return 0;       // 若是沒找到,返回 0
    else return it->second >> SIDE_TABLE_RC_SHIFT; // 若是找到了,經過 SIDE_TABLE_RC_SHIFT 位掩碼獲取對應的引用計數
}

#define SIDE_TABLE_RC_SHIFT 2
複製代碼

若是isanonpointer,則對象的引用計數就存儲在它的isa_textra_rc中以及SideTableRefCountMap中。因爲extra_rc存儲的對象自己以外的引用計數值,因此須要加上對象自己的引用計數 1;再加上SideTable中存儲的引用計數值,經過sidetable_getExtraRC_nolock()函數獲取。

sidetable_getExtraRC_nolock()函數中進行了兩次哈希查找:

  • ① 第一次根據當前對象的內存地址,通過哈希查找從SideTables()中取出它所在的SideTable
  • ② 第二次根據當前對象的內存地址,通過哈希查找從SideTable中的refcnts中取出它的引用計數表。
uintptr_t
objc_object::sidetable_retainCount()
{
    SideTable& table = SideTables()[this];

    size_t refcnt_result = 1; // 設置對象自己的引用計數爲1
    
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        // this is valid for SIDE_TABLE_RC_PINNED too
        refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT; // 引用計數 = 1 + SideTable 中存儲的引用計數
    }
    table.unlock();
    return refcnt_result;
}
複製代碼

若是isa不是nonpointer,它直接存儲着ClassMeta-Class對象的內存地址,沒辦法存儲引用計數,因此引用計數都存儲在SideTable中,這時候就經過sidetable_retainCount()得到引用計數。

小結:retainCount方法:

  • arm64以前,isa不是nonpointer。對象的引用計數全都存儲在SideTable中,retainCount方法返回的是對象自己的引用計數值 1,加上SideTable中存儲的值;
  • arm64開始,isanonpointer。對象的引用計數先存儲到它的isa中的extra_rc中,若是 19 位的extra_rc不夠存儲,那麼溢出的部分再存儲到SideTable中,retainCount方法返回的是對象自己的引用計數值 1,加上isa中的extra_rc存儲的值,加上SideTable中存儲的值。
  • 因此,其實咱們經過retainCount方法打印alloc建立的對象的引用計數爲 1,這是retainCount方法的功勞,alloc方法並無設置對象的引用計數。

Why: 那也不對啊,alloc方法沒有設置對象的引用計數爲 1,並且它內部也沒有調用retainCount方法啊。那咱們經過alloc建立出來的對象的引用計數豈不是就是 0,那不是會直接dealloc嗎?

dealloc方法是在release方法內部調用的。只有你直接調用了dealloc,或者調用了release且在release方法中判斷對象的引用計數爲 0 的時候,纔會調用dealloc。詳情請參閱release源碼分析。

retain

《iOS - 老生常談內存管理(二):從 MRC 提及》文章中已經講解過,持有對象有兩種方式,一是經過 alloc/new/copy/mutableCopy等方法建立對象,二是經過retain方法。retain方法會將對象的引用計數 +1。

retain方法的函數調用棧爲:

// NSObject.mmretain
② _objc_rootRetain
// objc-object.h
③ rootRetain
④ sidetable_retain
   addc // objc-os.h
   rootRetain_overflow
   sidetable_addExtraRC_nolock
複製代碼

① retain

// Replaced by ObjectAlloc
- (id)retain {
    return _objc_rootRetain(self);
}
複製代碼

② _objc_rootRetainCount

NEVER_INLINE id
_objc_rootRetain(id obj)
{
    ASSERT(obj);

    return obj->rootRetain();
}
複製代碼

③ rootRetain

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

ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    // 若是是 tagged pointer,直接返回 this
    if (isTaggedPointer()) return (id)this; 

    bool sideTableLocked = false;
    bool transcribeToSideTable = false; // 是否須要將引用計數存儲在 sideTable 中

    isa_t oldisa;
    isa_t newisa;

    do {
        transcribeToSideTable = false;
        // 獲取 isa
        oldisa = LoadExclusive(&isa.bits);  
        newisa = oldisa; 
        // 若是 isa 不是 nonpointer
        if (slowpath(!newisa.nonpointer)) { 
            ClearExclusive(&isa.bits);
            if (rawISA()->isMetaClass()) return (id)this;
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            // tryRetain == false,調用 sidetable_retain
            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;
        }
        uintptr_t carry; // 用於判斷 isa 的 extra_rc 是否溢出,這裏指上溢,即存滿
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        // 若是 extra_rc 上溢
        if (slowpath(carry)) { 
            // newisa.extra_rc++ overflowed
            // 若是 handleOverflow == false,調用 rootRetain_overflow
            if (!handleOverflow) { 
                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.
            // 保留一半的引用計數在 extra_rc 中
            // 準備把另外一半引用計數存儲到 Sidetable 中
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;   // 設置 transcribeToSideTable 爲 true
            newisa.extra_rc = RC_HALF;      // 設置 extra_rc 的值爲 RC_HALF # define RC_HALF (1ULL<<18)
            newisa.has_sidetable_rc = true; // 設置 has_sidetable_rc 爲 true
        }
    } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits))); // 保存更新後的 isa.bits

    // 若是須要將溢出的引用計數存儲到 sidetable 中
    if (slowpath(transcribeToSideTable)) { 
        // Copy the other half of the retain counts to the side table.
        // 將 RC_HALF 個引用計數存儲到 Sidetable 中
        sidetable_addExtraRC_nolock(RC_HALF); 
    }

    if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    return (id)this;
}
複製代碼

④ sidetable_retain

咱們先來看幾個偏移量:

// The order of these bits is important.
#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0) #define SIDE_TABLE_DEALLOCATING (1UL<<1) // MSB-ward of weak bit #define SIDE_TABLE_RC_ONE (1UL<<2) // MSB-ward of deallocating bit #define SIDE_TABLE_RC_PINNED (1UL<<(WORD_BITS-1)) #define SIDE_TABLE_RC_SHIFT 2 #define SIDE_TABLE_FLAG_MASK (SIDE_TABLE_RC_ONE-1) 複製代碼
  • SIDE_TABLE_WEAKLY_REFERENCED:標記對象是否有弱引用
  • SIDE_TABLE_DEALLOCATING:標記對象是否正在 dealloc
  • SIDE_TABLE_RC_ONE:對象引用計數存儲的開始位,引用計數存儲在第 2~63 位
  • SIDE_TABLE_RC_PINNED:引用計數的溢出標誌位(最後一位)

如下是對象的引用計數表:

id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
    ASSERT(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];          // 獲取 SideTable
    
    table.lock();
    size_t& refcntStorage = table.refcnts[this];    // 獲取 refcnt
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) { // 若是獲取到了,且未溢出
        refcntStorage += SIDE_TABLE_RC_ONE;         // 將引用計數加 1
    }
    table.unlock();

    return (id)this;
}
複製代碼

若是isa不是nonpointer,就會調用sidetable_retain,通過兩次哈希查找獲得對象的引用計數表,將引用計數 +1。

addc

static ALWAYS_INLINE uintptr_t 
addc(uintptr_t lhs, uintptr_t rhs, uintptr_t carryin, uintptr_t *carryout)
{
    return __builtin_addcl(lhs, rhs, carryin, carryout);
}
複製代碼

若是isanonpointer,就會調用addcextra_rc中的引用計數 +1。這個函數的做用就是增長引用計數。

rootRetain_overflow

NEVER_INLINE id 
objc_object::rootRetain_overflow(bool tryRetain)
{
    return rootRetain(tryRetain, true);
}
複製代碼

若是extra_rc中存儲滿了,就會調用rootRetain_overflow,該函數又調用了rootRetain,但參數handleOverflowtrue

sidetable_addExtraRC_nolock

// Move some retain counts to the side table from the isa field.
// Returns true if the object is now pinned.
// 將一些引用計數從 isa 中轉移到 sidetable
bool 
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
    ASSERT(isa.nonpointer);
    SideTable& table = SideTables()[this];

    size_t& refcntStorage = table.refcnts[this];
    size_t oldRefcnt = refcntStorage;
    // isa-side bits should not be set here
    ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
    ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);

    if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;

    uintptr_t carry;
    size_t newRefcnt = 
        addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
    if (carry) {
        refcntStorage =
            SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
        return true;
    }
    else {
        refcntStorage = newRefcnt;
        return false;
    }
}
複製代碼

若是extra_rc中存儲滿了,就會調用sidetable_addExtraRC_nolockextra_rc中的RC_HALFextra_rc滿值的一半)個引用計數轉移到sidetable中存儲,也是調用addcrefcnt引用計數表進行引用計數增長操做。

小結:retain方法:

  • 若是isa不是nonpointer,那麼就對Sidetable中的引用計數進行 +1;
  • 若是isanonpointer,就將isa中的extra_rc存儲的引用計數進行 +1,若是溢出,就將extra_rcRC_HALFextra_rc滿值的一半)個引用計數轉移到sidetable中存儲。 從rootRetain函數中咱們能夠看到,若是extra_rc溢出,設置它的值爲RC_HALF,這時候又對sidetable中的refcnt增長引用計數RC_HALFextra_rc19位,而RC_HALF宏是(1ULL<<18),實際上相等於進行了 +1 操做。

release

當咱們在不須要使用(持有)對象的時候,須要調用一下release方法進行釋放。release方法會將對象的引用計數 -1。

release方法的函數調用棧爲:

// NSObject.mm
① release
② _objc_rootRelease
// objc-object.h
③ rootRelease
④ sidetable_release
   subc // objc-os.h
   rootRelease_underflow
   sidetable_subExtraRC_nolock
   overrelease_error
複製代碼

① release

-(void) release
{
    _objc_rootRelease(self);
}
複製代碼

② _objc_rootRelease

NEVER_INLINE void
_objc_rootRelease(id obj)
{
    ASSERT(obj);

    obj->rootRelease();
}
複製代碼

③ rootRelease

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

ALWAYS_INLINE bool 
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
    // 若是是 tagged pointer,直接返回 false
    if (isTaggedPointer()) return false; 

    bool sideTableLocked = false;

    isa_t oldisa;
    isa_t newisa;

 retry:
    do {
        // 獲取 isa
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        // 若是 isa 不是 nonpointer
        if (slowpath(!newisa.nonpointer)) { 
            ClearExclusive(&isa.bits);
            if (rawISA()->isMetaClass()) return false;
            if (sideTableLocked) sidetable_unlock();
            // 調用 sidetable_release
            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--
        // 若是發現溢出的狀況,這裏是下溢,指 extra_rc 中的引用計數已經爲 0 了
        if (slowpath(carry)) { 
            // don't ClearExclusive()
            // 執行 underflow 處理下溢
            goto underflow; 
        }
    } while (slowpath(!StoreReleaseExclusive(&isa.bits, 
                                             oldisa.bits, newisa.bits))); // 保存更新後的 isa.bits

    if (slowpath(sideTableLocked)) sidetable_unlock();
    return false;

 underflow:
    // newisa.extra_rc-- underflowed: borrow from side table or deallocate
    // abandon newisa to undo the decrement
    // extra_rc-- 下溢,從 sidetable 借用或者 dealloc 對象
    newisa = oldisa;

    // 若是 isa 的 has_sidetable_rc 字段值爲 1
    if (slowpath(newisa.has_sidetable_rc)) { 
        // 若是 handleUnderflow == false,調用 rootRelease_underflow
        if (!handleUnderflow) { 
            ClearExclusive(&isa.bits);
            return rootRelease_underflow(performDealloc); 
        }

        // Transfer retain count from side table to inline storage.
        // 將引用計數從 sidetable 中轉到 extra_rc 中存儲

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

        // Try to remove some retain counts from the side table. 
        // 嘗試從 sidetable 中刪除(借出)一些引用計數,傳入 RC_HALF
        // borrowed 爲 sidetable 實際刪除(借出)的引用計數
        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.
        // 爲了不競爭,has_sidetable_rc 必須保持設置
        // 即便 sidetable 中的引用計數如今是 0

        if (borrowed > 0) { // 若是 borrowed > 0
            // Side table retain count decreased.
            // Try to add them to the inline count.
            // 將它進行 -1,賦值給 extra_rc 
            newisa.extra_rc = borrowed - 1;  // redo the original decrement too
            // 存儲更改後的 isa.bits
            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);
                    }
                }
            }
            // 若是仍是存儲失敗,把引用計數再從新保存到 sidetable 中
            if (!stored) {
                // Inline update failed.
                // Put the retains back in the side table.
                sidetable_addExtraRC_nolock(borrowed);
                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.
        }
    }

    // 若是引用計數爲 0,dealloc 對象
    // Really deallocate.
    // 若是當前 newisa 處於 deallocating 狀態,保證對象只會 dealloc 一次
    if (slowpath(newisa.deallocating)) { 
        ClearExclusive(&isa.bits);
        if (sideTableLocked) sidetable_unlock();
        // 調用 overrelease_error
        return overrelease_error(); 
        // does not actually return
    }
    // 設置 newisa 爲 deallocating 狀態
    newisa.deallocating = true; 
    // 若是存儲失敗,繼續重試
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry; 

    if (slowpath(sideTableLocked)) sidetable_unlock();

    __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);

    // 若是 performDealloc == true,給對象發送一條 dealloc 消息
    if (performDealloc) { 
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return true;
}
複製代碼

④ sidetable_release

// rdar://20206767
// return uintptr_t instead of bool so that the various raw-isa 
// -release paths all return zero in eax
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
    ASSERT(!isa.nonpointer);
#endif
    // 獲取 SideTable
    SideTable& table = SideTables()[this]; 

    bool do_dealloc = false; // 標識是否須要執行 dealloc 方法

    table.lock();
    auto it = table.refcnts.try_emplace(this, SIDE_TABLE_DEALLOCATING);
    // 獲取 refcnts
    auto &refcnt = it.first->second; 
    if (it.second) {
        do_dealloc = true;
    // 若是對象處於 deallocating 狀態
    } else if (refcnt < SIDE_TABLE_DEALLOCATING) { 
        // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
        do_dealloc = true;
        refcnt |= SIDE_TABLE_DEALLOCATING;
    // 若是引用計數有值
    } else if (! (refcnt & SIDE_TABLE_RC_PINNED)) { 
        // 引用計數 -1
        refcnt -= SIDE_TABLE_RC_ONE; 
    }
    table.unlock();
    // 若是符合判斷條件,dealloc 對象
    if (do_dealloc  &&  performDealloc) { 
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return do_dealloc;
}
複製代碼

若是isa不是nonpointer,那麼就對Sidetable中的引用計數進行 -1,若是引用計數 =0,就dealloc對象;

subc

static ALWAYS_INLINE uintptr_t 
subc(uintptr_t lhs, uintptr_t rhs, uintptr_t carryin, uintptr_t *carryout)
{
    return __builtin_subcl(lhs, rhs, carryin, carryout);
}
複製代碼

subc就是addc的反操做,用來減小引用計數。

rootRelease_underflow

NEVER_INLINE bool 
objc_object::rootRelease_underflow(bool performDealloc)
{
    return rootRelease(performDealloc, true);
}
複製代碼

若是extra_rc下溢,就會調用rootRelease_underflow,該函數又調用了rootRelease,但參數handleUnderflowtrue

sidetable_subExtraRC_nolock

// Move some retain counts from the side table to the isa field.
// Returns the actual count subtracted, which may be less than the request.
size_t 
objc_object::sidetable_subExtraRC_nolock(size_t delta_rc)
{
    ASSERT(isa.nonpointer);
    // 獲取 SideTable
    SideTable& table = SideTables()[this];

    // 獲取 refcnt
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()  ||  it->second == 0) {
        // Side table retain count is zero. Can't borrow.
        return 0;
    }
    size_t oldRefcnt = it->second;

    // isa-side bits should not be set here
    ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
    ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);

    // 減小引用計數
    size_t newRefcnt = oldRefcnt - (delta_rc << SIDE_TABLE_RC_SHIFT);
    ASSERT(oldRefcnt > newRefcnt);  // shouldn't underflow
    it->second = newRefcnt;
    return delta_rc;
}
複製代碼

sidetable_subExtraRC_nolock目的就是請求將sidetable中存儲的一些引用計數值轉移到isa中。返回減去的實際引用計數,該值可能小於請求值。

overrelease_error

NEVER_INLINE uintptr_t
objc_object::overrelease_error()
{
    _objc_inform_now_and_on_crash("%s object %p overreleased while already deallocating; break on objc_overrelease_during_dealloc_error to debug", object_getClassName((id)this), this);
    objc_overrelease_during_dealloc_error();
    return 0; // allow rootRelease() to tail-call this
}
複製代碼

若是當前對象處於deallocating狀態,再次release就會執行overrelease_error,該函數就是用來在過分調用release的時候報錯用的。

小結:release方法:

  • 若是isa不是nonpointer,那麼就對Sidetable中的引用計數進行 -1,若是引用計數 =0,就dealloc對象;
  • 若是isanonpointer,就將isa中的extra_rc存儲的引用計數進行 -1。若是下溢,即extra_rc中的引用計數已經爲 0,判斷has_sidetable_rc是否爲true便是否有使用Sidetable存儲。若是有的話就申請從Sidetable中申請RC_HALF個引用計數轉移到extra_rc中存儲,若是不足RC_HALF就有多少申請多少,而後將Sidetable中的引用計數值減去RC_HALF(或是小於RC_HALF的實際值),將實際申請到的引用計數值 -1 後存儲到extra_rc中。若是extra_rc中引用計數爲 0 且has_sidetable_rcfalse或者Sidetable中的引用計數也爲 0 了,那就dealloc對象。

    爲何須要這麼作呢?直接先從Sidetable中對引用計數進行 -1 操做不行嗎? 我想應該是爲了性能吧,畢竟訪問對象的isa更快。

autorelease

autorelease方法的函數調用棧爲:

// NSObject.mm
① autorelease
// objc-object.h
② rootAutorelease
// NSObject.mm
③ rootAutorelease2
複製代碼

① autorelease

// Replaced by ObjectAlloc
- (id)autorelease {
    return ((id)self)->rootAutorelease();
}
複製代碼

② rootAutorelease

// Base autorelease implementation, ignoring overrides.
inline id 
objc_object::rootAutorelease()
{
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}
複製代碼

③ rootAutorelease2

__attribute__((noinline,used))
id 
objc_object::rootAutorelease2()
{
    assert(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);
}
複製代碼

在該函數中調用了AutoreleasePoolPage類的autorelease方法。 關於AutoreleasePoolPage類以及autorelease@autoreleasepool,可參閱《iOS - 聊聊 autorelease 和 @autoreleasepool》

dealloc

dealloc方法的函數調用棧爲:

// NSObject.mm
① dealloc
② _objc_rootDealloc
// objc-object.h
③ rootDealloc
// objc-runtime-new.mm
④ object_dispose
⑤ objc_destructInstance
// objc-object.h
⑥ clearDeallocating
// NSObject.mm
⑦ sidetable_clearDeallocating
   clearDeallocating_slow
複製代碼

① dealloc

// Replaced by NSZombies
- (void)dealloc {
    _objc_rootDealloc(self);
}
複製代碼

② _objc_rootDealloc

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

    obj->rootDealloc();
}
複製代碼

③ rootDealloc

inline void
objc_object::rootDealloc()
{
    // 判斷是否爲 TaggerPointer 內存管理方案,是的話直接 return
    if (isTaggedPointer()) return;  // fixme necessary? * 

    if (fastpath(isa.nonpointer  &&          // 若是 isa 爲 nonpointer
                 !isa.weakly_referenced  &&  // 沒有弱引用
                 !isa.has_assoc  &&          // 沒有關聯對象
                 !isa.has_cxx_dtor  &&       // 沒有 C++ 的析構函數
                 !isa.has_sidetable_rc))     // 沒有額外採用 SideTabel 進行引用計數存儲
    {
        assert(!sidetable_present());
        free(this);               // 若是以上條件成立,直接調用 free 函數銷燬對象
    } 
    else {
        object_dispose((id)this); // 若是以上條件不成立,調用 object_dispose 函數
    }
}
複製代碼

④ object_dispose

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

    objc_destructInstance(obj); // 調用 objc_destructInstance 函數
    free(obj);                  // 調用 free 函數銷燬對象

    return nil;
}
複製代碼

⑤ objc_destructInstance

/*********************************************************************** * 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++ 的析構函數,調用 object_cxxDestruct 函數
        if (assoc) _object_remove_assocations(obj); // 若是有關聯對象,調用 _object_remove_assocations 函數,移除關聯對象
        obj->clearDeallocating();                   // 調用 clearDeallocating 函數
    }

    return obj;
}
複製代碼

⑥ clearDeallocating

inline void 
objc_object::clearDeallocating()
{
    // 若是 isa 不是 nonpointer
    if (slowpath(!isa.nonpointer)) {     
        // Slow path for raw pointer isa.
        // 調用 sidetable_clearDeallocating 函數
        sidetable_clearDeallocating();   
    }
    // 若是 isa 是 nonpointer,且有弱引用或者有額外使用 SideTable 存儲引用計數
    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 函數
        clearDeallocating_slow();        
    }

    assert(!sidetable_present());
}
複製代碼

⑦ sidetable_clearDeallocating

void 
objc_object::sidetable_clearDeallocating()
{
    // 獲取 SideTable
    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();
    // 獲取 refcnts
    RefcountMap::iterator it = table.refcnts.find(this); 
    if (it != table.refcnts.end()) {
        if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
            // 調用 weak_clear_no_lock:將指向該對象的弱引用指針置爲 nil
            weak_clear_no_lock(&table.weak_table, (id)this); 
        }
        // 調用 table.refcnts.erase:從引用計數表中擦除該對象的引用計數
        table.refcnts.erase(it); 
    }
    table.unlock();
}
複製代碼

clearDeallocating_slow

// 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
    SideTable& table = SideTables()[this]; 
    table.lock();
    // 若是有弱引用
    if (isa.weakly_referenced) { 
        // 調用 weak_clear_no_lock:將指向該對象的弱引用指針置爲 nil
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    // 若是有使用 SideTable 存儲引用計數
    if (isa.has_sidetable_rc) {  
        // 調用 table.refcnts.erase:從引用計數表中擦除該對象的引用計數
        table.refcnts.erase(this);
    }
    table.unlock();
}
複製代碼

小結:dealloc方法:

  • ① 判斷 5 個條件(1.isanonpointer;2.沒有弱引用;3.沒有關聯對象;4.沒有C++的析構函數;5.沒有額外採用SideTabel進行引用計數存儲),若是這 5 個條件都成立,直接調用free函數銷燬對象,不然調用object_dispose作一些釋放對象前的處理;
  • ② 1.若是有C++的析構函數,調用object_cxxDestruct
     2.若是有關聯對象,調用_object_remove_assocations函數,移除關聯對象;
     3.調用weak_clear_no_lock將指向該對象的弱引用指針置爲nil
     4.調用table.refcnts.erase從引用計數表中擦除該對象的引用計數(若是isanonpointer,還要先判斷isa.has_sidetable_rc
  • ③ 調用free函數銷燬對象。

  根據dealloc過程,__weak修飾符的變量在對象被dealloc時,會將該__weak置爲nil。可見,若是大量使用__weak變量的話,則會消耗相應的 CPU 資源,因此建議只在須要避免循環引用的時候使用__weak修飾符。
  在《iOS - 老生常談內存管理(三):ARC 面世 —— 全部權修飾符》章節中提到,__weak對性能會有必定的消耗,當一個對象dealloc時,須要遍歷對象的weak表,把表裏的全部weak指針變量值置爲nil,指向對象的weak指針越多,性能消耗就越多。因此__unsafe_unretained__weak快。當明確知道對象的生命週期時,選擇__unsafe_unretained會有一些性能提高。

weak

清除 weak

以上從dealloc方法實現咱們知道了在對象dealloc的時候,會調用weak_clear_no_lock函數將指向該對象的弱引用指針置爲nil,那麼該函數的具體實現是怎樣的呢?

weak_clear_no_lock
// objc-weak.mm
/** * 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) 
{
    // 得到 weak 指向的地址,即對象內存地址
    objc_object *referent = (objc_object *)referent_id; 
    
    // 找到管理 referent 的 entry 容器
    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent); 
    // 若是 entry == nil,表示沒有弱引用須要置爲 nil,直接返回
    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 是一個數組,存儲全部指向 referent_id 的弱引用
        referrers = entry->referrers; 
        // 弱引用數組長度
        count = TABLE_SIZE(entry);    
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    
    // 遍歷弱引用數組,將全部指向 referent_id 的弱引用所有置爲 nil
    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_table 中移除對應的弱引用的管理容器
    weak_entry_remove(weak_table, entry);
}
複製代碼

小結: 清除weak
當一個對象被銷燬時,在dealloc方法內部通過一系列的函數調用棧,經過兩次哈希查找,第一次根據對象的地址找到它所在的Sidetable,第二次根據對象的地址在Sidetableweak_table中找到它的弱引用表。弱引用表中存儲的是對象的地址(做爲key)和weak指針地址的數組(做爲value)的映射。weak_clear_no_lock函數中遍歷弱引用數組,將指向對象的地址的weak變量全都置爲nil

添加 weak

接下來咱們來看一下weak變量是怎樣添加到弱引用表中的。

一個被聲明爲__weak的指針,在通過編譯以後。經過objc_initWeak函數初始化附有__weak修飾符的變量,在變量做用域結束時經過objc_destroyWeak函數銷燬該變量。

{
    id obj = [[NSObject alloc] init];
    id __weak obj1 = obj;
}
    /*----- 編譯 -----*/
    id obj1;
    objc_initWeak(&obj1,obj);
    objc_destroyWeak(&obj1);
複製代碼

objc_initWeak函數調用棧以下:

// NSObject.mm
① objc_initWeak
② storeWeak
// objc-weak.mm
③ weak_register_no_lock
   weak_unregister_no_lock
複製代碼
① 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. */
id
objc_initWeak(id *location, id newObj) // *location 爲 __weak 指針地址,newObj 爲對象地址
{
    // 若是對象爲 nil,那就將 weak 指針置爲 nil
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (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.
// 更新 weak 變量
// 若是 HaveOld == true,表示變量有舊值,它須要被清理,這個舊值可能爲 nil
// 若是 HaveNew == true,表示一個新值須要賦值給變量,這個新值可能爲 nil
// 若是 CrashIfDeallocating == true,則若是對象正在銷燬或者對象不支持弱引用,則中止更新
// 若是 CrashIfDeallocating == false,則存儲 nil
enum CrashIfDeallocating {
    DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};
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;  // 舊錶,用來存放已有的 weak 變量
    SideTable *newTable;  // 新表,用來存放新的 weak 變量

    // 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:
    // 分別獲取新舊值相關聯的弱引用表
    // 若是 weak 變量有舊值,獲取已有對象(該舊值對象)和舊錶
    if (haveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    // 若是有新值要賦值給變量,建立新表
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }
    
    // 對 haveOld 和 haveNew 分別加鎖
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    // 判斷 oldObj 和 location 指向的值是否相等,便是否是同一對象,若是不是就從新獲取舊值相關聯的表
    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.
    // 若是有新值,判斷新值所屬的類是否已經初始化
    // 若是沒有初始化,則先執行初始化,防止 +initialize 內部調用 storeWeak 產生死鎖
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            class_initialize(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;
        }
    }

    // 若是有舊值,調用 weak_unregister_no_lock 清除舊值
    // Clean up old value, if any.
    if (haveOld) {
        // 移除全部指向舊值的 weak 引用,而不是賦值爲 nil
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // 若是有新值要賦值,調用 weak_register_no_lock 將全部 weak 指針從新指向新的對象
    // 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

        // 若是存儲成功
        // 若是對象是 Tagged Pointer,不作操做
        // 若是 isa 不是 nonpointer,設置 SideTable 中弱引用標誌位
        // 若是 isa 是 nonpointer,設置 isa 的 weakly_referenced 弱引用標誌位
        // Set is-weakly-referenced bit in refcount table.
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }
 
        // 將 location 指向新的對象
        // 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;
}
複製代碼

store_weak函數的執行過程以下:

  • 分別獲取新舊值相關聯的弱引用表;
  • 若是有舊值,就調用weak_unregister_no_lock函數清除舊值,移除全部指向舊值的weak引用,而不是賦值爲nil
  • 若是有新值,就調用weak_register_no_lock函數分配新值,將全部weak指針從新指向新的對象;
  • 判斷isa是否爲nonpointer來設置弱引用標誌位。若是不是nonpointer,設置SideTable中的弱引用標誌位,不然設置isaweakly_referenced弱引用標誌位。
③ weak_register_no_lock
/** * Registers a new (object, weak pointer) pair. Creates a new weak * object entry if it does not exist. * * @param weak_table The global weak table. * @param referent The object pointed to by the weak reference. * @param referrer The weak pointer address. */
id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, bool crashIfDeallocating)
{
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;

    if (!referent  ||  referent->isTaggedPointer()) return referent_id;

    // ensure that the referenced object is viable
    bool deallocating;
    if (!referent->ISA()->hasCustomRR()) {
        deallocating = referent->rootIsDeallocating();
    }
    else {
        BOOL (*allowsWeakReference)(objc_object *, SEL) = 
            (BOOL(*)(objc_object *, SEL))
            object_getMethodImplementation((id)referent, 
                                           SEL_allowsWeakReference);
        if ((IMP)allowsWeakReference == _objc_msgForward) {
            return nil;
        }
        deallocating =
            ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
    }

    if (deallocating) {
        if (crashIfDeallocating) {
            _objc_fatal("Cannot form weak reference to instance (%p) of "
                        "class %s. It is possible that this object was "
                        "over-released, or is in the process of deallocation.",
                        (void*)referent, object_getClassName((id)referent));
        } else {
            return nil;
        }
    }

    // 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(referent, referrer);
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);
    }

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

    return referent_id;
}
複製代碼

weak_register_no_lock用來保存弱引用信息,具體實現以下:

  • 判斷對象是否正在釋放,是否支持弱引用allowsWeakReference,若是實例對象的allowsWeakReference方法返回NO,則調用_objc_fatal並在控制檯打印"Cannot form weak reference to instance (%p) of class %s. It is possible that this object was over-released, or is in the process of deallocation."
    (關於allowsWeakReference已經在《iOS - 老生常談內存管理(三):ARC 面世》中講到)
  • 查詢weak_table,判斷弱引用表中是否已經保存有與對象相關聯的弱引用信息;
  • 若是已經有相關弱引用信息,則調用append_referrer函數將弱引用信息添加進如今entry容器中;若是沒有相關聯信息,則建立一個entry,而且插入到weak_table弱引用表中。
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 collected * * @param weak_table The global weak table. * @param referent The object. * @param referrer The weak reference. */
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))) {
        remove_referrer(entry, referrer);
        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_remove(weak_table, entry);
        }
    }

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

weak_unregister_no_lock用來移除弱引用信息,具體實現以下:

  • 查詢weak_table,判斷弱引用表中是否已經保存有與對象相關聯的弱引用信息;
  • 若是有,則調用remove_referrer方法移除相關聯的弱引用信息;接着判斷存儲數組是否爲空,若是爲空,則調用weak_entry_remove移除entry容器。

objc_destroyWeak函數調用棧以下:

// NSObject.mm
① objc_destroyWeak
② storeWeak
複製代碼
objc_destroyWeak
/** * Destroys the relationship between a weak pointer * and the object it is referencing in the internal weak * table. If the weak pointer is not referencing anything, * there is no need to edit the weak table. * * This function IS NOT thread-safe with respect to concurrent * modifications to the weak variable. (Concurrent weak clear is safe.) * * @param location The weak pointer address. */
void
objc_destroyWeak(id *location)
{
    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (location, nil);
}
複製代碼

objc_initWeakobjc_destroyWeak函數中都調用了storeWeak,可是傳的參數不一樣。

  • objc_initWeak將對象地址傳入,且DontHaveOldDoHaveNewDoCrashIfDeallocating
  • objc_destroyWeaknil傳入,且DoHaveOldDontHaveNewDontCrashIfDeallocating

storeWeak函數把參數二的賦值的對象地址做爲key,把參數一的附有__weak修飾符的變量的地址註冊到weak表中。若是參數二爲nil,則把變量的地址從weak表中刪除。

小結: 添加weak
一個被標記爲__weak的指針,在通過編譯以後會調用objc_initWeak函數,objc_initWeak函數中初始化weak變量後調用storeWeak。添加weak的過程以下: 通過一系列的函數調用棧,最終在weak_register_no_lock()函數當中,進行弱引用變量的添加,具體添加的位置是經過哈希算法來查找的。若是對應位置已經存在當前對象的弱引用表(數組),那就把弱引用變量添加進去;若是不存在的話,就建立一個弱引用表,而後將弱引用變量添加進去。

總結

以上就是內存管理方法的具體實現,接下來作個小總結:

內存管理方法 具體實現
alloc 通過一系列的函數調用棧,最終經過調用 C 函數calloc來申請內存空間,並初始化對象的isa,但並無設置對象的引用計數值爲 1。
init 基類的init方法啥都沒幹,只是將alloc建立的對象返回。咱們能夠重寫init方法來對alloc建立的實例作一些初始化操做。
new new方法很簡單,只是嵌套了allocinit
copy、mutableCopy 調用了copyWithZonemutableCopyWithZone方法。
retainCount ① 若是isa不是nonpointer,引用計數值 = SideTable中的引用計數表中存儲的值 + 1;
② 若是isanonpointer,引用計數值 = isa中的extra_rc存儲的值 + 1 +SideTable中的引用計數表中存儲的值。
retain ① 若是isa不是nonpointer,就對Sidetable中的引用計數進行 +1;
② 若是isanonpointer,就將isa中的extra_rc存儲的引用計數進行 +1,若是溢出,就將extra_rcRC_HALFextra_rc滿值的一半)個引用計數轉移到sidetable中存儲。
release ① 若是isa不是nonpointer,就對Sidetable中的引用計數進行 -1,若是引用計數 =0,就dealloc對象;
② 若是isanonpointer,就將isa中的extra_rc存儲的引用計數進行 -1。若是下溢,即extra_rc中的引用計數已經爲 0,判斷has_sidetable_rc是否爲true便是否有使用Sidetable存儲。若是有的話就申請從Sidetable中申請RC_HALF個引用計數轉移到extra_rc中存儲,若是不足RC_HALF就有多少申請多少,而後將Sidetable中的引用計數值減去RC_HALF(或是小於RC_HALF的實際值),將實際申請到的引用計數值 -1 後存儲到extra_rc中。若是extra_rc中引用計數爲 0 且has_sidetable_rcfalse或者Sidetable中的引用計數也爲 0 了,那就dealloc對象。
dealloc ① 判斷銷燬對象前有沒有須要處理的東西(如弱引用、關聯對象、C++的析構函數、SideTabel的引用計數表等等);
② 若是沒有就直接調用free函數銷燬對象;
③ 若是有就先調用object_dispose作一些釋放對象前的處理(置弱引用指針置爲nil、移除關聯對象、object_cxxDestruct、在SideTabel的引用計數表中擦出引用計數等待),再用free函數銷燬對象。
清除weakweak指針置爲nil的過程 當一個對象被銷燬時,在dealloc方法內部通過一系列的函數調用棧,經過兩次哈希查找,第一次根據對象的地址找到它所在的Sidetable,第二次根據對象的地址在Sidetableweak_table中找到它的弱引用表。遍歷弱引用數組,將指向對象的地址的weak變量全都置爲nil
添加weak 通過一系列的函數調用棧,最終在weak_register_no_lock()函數當中,進行弱引用變量的添加,具體添加的位置是經過哈希算法來查找的。若是對應位置已經存在當前對象的弱引用表(數組),那就把弱引用變量添加進去;若是不存在的話,就建立一個弱引用表,而後將弱引用變量添加進去。

建議你們本身經過objc4源碼看一遍,這樣印象會更深一些。另外本篇文章的源碼分析並無分析得很細節,若是你們感興趣能夠本身研究一遍,刨根問底當然是好。若是之後有時間,我會再具體分析並更新本文章。

相關文章
相關標籤/搜索