蘋果iOS系統原始碼思考:物件的引用計數儲存在哪裡?--從runtime原始碼得到的啟示

陳滿iOS發表於2018-07-16

引言:這篇文章旨在從runtime原始碼中分析出 引用計數 值本身的儲存位置,適合對底層原理有興趣的朋友,或者面試造火箭的同學(比如百度的面試官非常喜歡問底層原理:好,我知道你說了深淺複製的區別一大堆,如果我讓你自己實現一個copy,你能實現嗎?如果我讓你實現引用計數的功能,你有思路嗎?)。因而本文並 不適用於 專注業務層快速開發的同學,因為這裡將貼有大量的原始碼。沒有耐心的同學可以先收藏暫時迴避一下,日後造火箭造飛機的時候再來。

核心問題

iOS開發者都知道OC裡面的記憶體管理是通過物件的引用計數來管理的,或手動MRC,或自動ARC,有些操作可以讓引用計數加1,有些可以減1,一旦一個物件的引用計數為0,就回收記憶體了。

可是,你僅僅知道這裡就行了嗎?指望你能造火箭造飛機的面試官可不這麼想了,比如問你一句,一個物件的 引用計數本身 儲存在哪裡??不關注底層的面試者,這時候可能會懵逼。

研究方式

這篇文章不同於其它文章通過 clang編譯 一個類檔案以檢視它的實現原理(筆者曾用clang編譯分析Block的原理,傳送門),而是直接通過下載runtime的原始碼來檢視分析。

依據版本

蘋果開源了runtime的程式碼,檢視的方式既可以通過 線上網頁版 預覽,也可以 下載歸檔檔案 到本地檢視。本篇檔案討論的版本是 objc4-723

目錄

    1. 類與物件
    • 1.1 物件 -- Object
    • 1.2 物件 -- NSObject
    • 1.3 物件 -- objc_object
    • 1.4 類 -- objc_class
    • 1.5 NSObject,objc_object,objc_class 三者的關係
    1. 手動引用對引用計數的影響 -- retain操作
    • 2.1 兩種物件:NSObject與Object的引用增加
    • 2.2 歸根結底 -- NSObject物件的rootRetain()
    1. isa與Tagged Pointer
    • 3.1 NSObject的唯一成員變數 -- isa
    • 3.2 isa_t聯合體裡面的資料含義
    • 3.3 isa_t聯合體裡面的巨集
    • 3.4 是否Tagged Pointer的判斷
    • 3.5 與isa型別有關的巨集
    • 3.6 怎麼判斷是否支援優化的isa指標?-- 看裝置、自己設定。
    • 3.7 怎麼判斷是否Tagged Pointer的物件?-- 看物件、自己設定
    • 3.8 引用計數的儲存形式 -- 雜湊表
    1. 雜湊表
  • 4.1 增加引用計數 -- sidetable_retain()
  • 4.2 增加引用計數 -- sidetable_tryRetain()
  • 4.3 獲取雜湊表 -- SideTable()
    1. 設定變數導致的引用計數變化 -- objc_retain操作
    • 5.1 情況1
    • 5.2 情況2
    • 5.3 objc_storeStrong導致的retain
    1. 新建物件(分配記憶體與初始化)導致的引用計數變化 -- alloc 和 init 操作
    • 6.1 分配記憶體 -- alloc
    • 6.2 初始化 -- init
    1. 獲取引用計數
    1. 結論
    1. 擴充閱讀

1. 類與物件

下載完工程,開啟檢視

蘋果iOS系統原始碼思考:物件的引用計數儲存在哪裡?--從runtime原始碼得到的啟示

module.modulemap 標頭檔案描述檔案

module ObjectiveC [system] [extern_c] {
  umbrella "."
  export *
  module * { 
    export *
  }

  module NSObject {
    requires objc
    header "NSObject.h"
    export *
  }

#if defined(BUILD_FOR_OSX)
  module List {
    // Uses @defs, which does not work in ObjC++ or non-ARC.
    requires objc, !objc_arc, !cplusplus
    header "List.h"
    export *
  }

  module Object {
    requires objc
    header "Object.h"
    export *
  }

  module Protocol {
    requires objc
    header "Protocol.h"
    export *
  }
#endif

#if !defined(BUILD_FOR_OSX)
  // These file are not available outside macOS.
  exclude header "hashtable.h"
  exclude header "hashtable2.h"
#endif
}
複製程式碼

這裡的Module本質上是一個描述檔案,用來描述Module中包涵的內容,每個Module中必須包涵一個umbrella標頭檔案,這個檔案用來#import所有這個Module下的檔案,比如#import <UIKit/UIKit.h>這個UIKit.h就是一個umbrella檔案。關於Module更多參考 這篇文章

#if defined(BUILD_FOR_OSX)這句邏輯判斷可知, Object是針對macOS的,iOS開發暫時只關心NSObject即可。

1.1 物件 -- Object

Object.mm Object

#include "objc-private.h"

#undef id
#undef Class

typedef struct objc_class *Class;
typedef struct objc_object *id;

#if __OBJC2__

__OSX_AVAILABLE(10.0) 
__IOS_UNAVAILABLE __TVOS_UNAVAILABLE
__WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE
OBJC_ROOT_CLASS
@interface Object { 
    Class isa; 
} 
@end

@implementation Object

+ (id)initialize
{
    return self; 
}

+ (id)class
{
    return self;
}

-(id) retain
{
    return _objc_rootRetain(self);
}

-(void) release
{
    _objc_rootRelease(self);
}

-(id) autorelease
{
    return _objc_rootAutorelease(self);
}

+(id) retain
{
    return self;
}

+(void) release
{
}

+(id) autorelease
{
    return self;
}


@end
複製程式碼

1.2 物件 -- NSObject

NSObject.h NSObject

#ifndef _OBJC_NSOBJECT_H_
#define _OBJC_NSOBJECT_H_

#if __OBJC__

#include <objc/objc.h>
#include <objc/NSObjCRuntime.h>

@class NSString, NSMethodSignature, NSInvocation;

@protocol NSObject

- (BOOL)isEqual:(id)object;
@property (readonly) NSUInteger hash;

@property (readonly) Class superclass;
- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'type(of: anObject)' instead");
- (instancetype)self;

- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

- (BOOL)isProxy;

- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;

- (BOOL)respondsToSelector:(SEL)aSelector;

- (instancetype)retain OBJC_ARC_UNAVAILABLE;
- (oneway void)release OBJC_ARC_UNAVAILABLE;
- (instancetype)autorelease OBJC_ARC_UNAVAILABLE;
- (NSUInteger)retainCount OBJC_ARC_UNAVAILABLE;

- (struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;

@property (readonly, copy) NSString *description;
@optional
@property (readonly, copy) NSString *debugDescription;

@end


OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
OBJC_ROOT_CLASS
OBJC_EXPORT
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

+ (void)load;

+ (void)initialize;
- (instancetype)init
#if NS_ENFORCE_NSOBJECT_DESIGNATED_INITIALIZER
    NS_DESIGNATED_INITIALIZER
#endif
    ;

+ (instancetype)new OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
+ (instancetype)allocWithZone:(struct _NSZone *)zone OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
+ (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
- (void)dealloc OBJC_SWIFT_UNAVAILABLE("use 'deinit' to define a de-initializer");

- (void)finalize OBJC_DEPRECATED("Objective-C garbage collection is no longer supported");

- (id)copy;
- (id)mutableCopy;

+ (id)copyWithZone:(struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;
+ (id)mutableCopyWithZone:(struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;

+ (BOOL)instancesRespondToSelector:(SEL)aSelector;
+ (BOOL)conformsToProtocol:(Protocol *)protocol;
- (IMP)methodForSelector:(SEL)aSelector;
+ (IMP)instanceMethodForSelector:(SEL)aSelector;
- (void)doesNotRecognizeSelector:(SEL)aSelector;

- (id)forwardingTargetForSelector:(SEL)aSelector OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
- (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE("");
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");

+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");

- (BOOL)allowsWeakReference UNAVAILABLE_ATTRIBUTE;
- (BOOL)retainWeakReference UNAVAILABLE_ATTRIBUTE;

+ (BOOL)isSubclassOfClass:(Class)aClass;

+ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

+ (NSUInteger)hash;
+ (Class)superclass;
+ (Class)class OBJC_SWIFT_UNAVAILABLE("use 'aClass.self' instead");
+ (NSString *)description;
+ (NSString *)debugDescription;

@end

#endif

#endif
複製程式碼

1.3 物件 -- objc_object

關鍵資訊

  • isa: isa_t型別的指標,詳情可見下面3.2節。簡單的說,它是這樣的一個聯合體,包含了bits (是一個 uintptr_t 型別的值,作為isa初始化列表中必初始化的值,可以用來獲取isa結構體)和 cls (該變數會指向物件所屬的類的結構,在 64 位裝置上會佔用 8byte)。

objc-private.h objc_object

struct objc_object {
private:
    isa_t isa;

public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();

    // initIsa() should be used to init the isa of new objects only.
    // If this object already has an isa, use changeIsa() for correctness.
    // initInstanceIsa(): objects with no custom RR/AWZ
    // initClassIsa(): class objects
    // initProtocolIsa(): protocol objects
    // initIsa(): other objects
    void initIsa(Class cls /*nonpointer=false*/);
    void initClassIsa(Class cls /*nonpointer=maybe*/);
    void initProtocolIsa(Class cls /*nonpointer=maybe*/);
    void initInstanceIsa(Class cls, bool hasCxxDtor);

    // changeIsa() should be used to change the isa of existing objects.
    // If this is a new object, use initIsa() for performance.
    Class changeIsa(Class newCls);

    bool hasNonpointerIsa();
    bool isTaggedPointer();
    bool isBasicTaggedPointer();
    bool isExtTaggedPointer();
    bool isClass();

    // object may have associated objects?
    bool hasAssociatedObjects();
    void setHasAssociatedObjects();

    // object may be weakly referenced?
    bool isWeaklyReferenced();
    void setWeaklyReferenced_nolock();

    // object may have -.cxx_destruct implementation?
    bool hasCxxDtor();

    // Optimized calls to retain/release methods
    id retain();
    void release();
    id autorelease();

    // Implementations of retain/release methods
    id rootRetain();
    bool rootRelease();
    id rootAutorelease();
    bool rootTryRetain();
    bool rootReleaseShouldDealloc();
    uintptr_t rootRetainCount();

    // Implementation of dealloc methods
    bool rootIsDeallocating();
    void clearDeallocating();
    void rootDealloc();

private:
    void initIsa(Class newCls, bool nonpointer, bool hasCxxDtor);

    // Slow paths for inline control
    id rootAutorelease2();
    bool overrelease_error();

#if SUPPORT_NONPOINTER_ISA
    // Unified retain count manipulation for nonpointer isa
    id rootRetain(bool tryRetain, bool handleOverflow);
    bool rootRelease(bool performDealloc, bool handleUnderflow);
    id rootRetain_overflow(bool tryRetain);
    bool rootRelease_underflow(bool performDealloc);

    void clearDeallocating_slow();

    // Side table retain count overflow for nonpointer isa
    void sidetable_lock();
    void sidetable_unlock();

    void sidetable_moveExtraRC_nolock(size_t extra_rc, bool isDeallocating, bool weaklyReferenced);
    bool sidetable_addExtraRC_nolock(size_t delta_rc);
    size_t sidetable_subExtraRC_nolock(size_t delta_rc);
    size_t sidetable_getExtraRC_nolock();
#endif

    // Side-table-only retain count
    bool sidetable_isDeallocating();
    void sidetable_clearDeallocating();

    bool sidetable_isWeaklyReferenced();
    void sidetable_setWeaklyReferenced_nolock();

    id sidetable_retain();
    id sidetable_retain_slow(SideTable& table);

    uintptr_t sidetable_release(bool performDealloc = true);
    uintptr_t sidetable_release_slow(SideTable& table, bool performDealloc = true);

    bool sidetable_tryRetain();

    uintptr_t sidetable_retainCount();
#if DEBUG
    bool sidetable_present();
#endif
};
複製程式碼

1.4 類 -- objc_class

關鍵資訊

  • isa: 繼承於objc_object
  • superclass: 指向自己父類的指標
  • cache: 方法快取
  • bits: 它是一個class_data_bits_t型別的指標。作為本類的例項方法連結串列。

注意區別

這裡的bitsclass_data_bits_t型別的,上一節objc_object的isa_t型別資料中也有一個uintptr_t型別的bits,但是這是兩種結構。

class_data_bits_t

由此可見,objc_class 繼承於 objc_object, 所以也是包含一個isa的類。在OC裡,不只是物件的例項包含一個isa,這個物件的類本身也有這麼一個isa,類本身也是一個物件。

objc-runtime-new.h objc_class

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }

    void setInfo(uint32_t set) {
        assert(isFuture()  ||  isRealized());
        data()->setFlags(set);
    }

    void clearInfo(uint32_t clear) {
        assert(isFuture()  ||  isRealized());
        data()->clearFlags(clear);
    }

    // set and clear must not overlap
    void changeInfo(uint32_t set, uint32_t clear) {
        assert(isFuture()  ||  isRealized());
        assert((set & clear) == 0);
        data()->changeFlags(set, clear);
    }

    bool hasCustomRR() {
        return ! bits.hasDefaultRR();
    }
    void setHasDefaultRR() {
        assert(isInitializing());
        bits.setHasDefaultRR();
    }
    void setHasCustomRR(bool inherited = false);
    void printCustomRR(bool inherited);

    bool hasCustomAWZ() {
        return ! bits.hasDefaultAWZ();
    }
    void setHasDefaultAWZ() {
        assert(isInitializing());
        bits.setHasDefaultAWZ();
    }
    void setHasCustomAWZ(bool inherited = false);
    void printCustomAWZ(bool inherited);

    bool instancesRequireRawIsa() {
        return bits.instancesRequireRawIsa();
    }
    void setInstancesRequireRawIsa(bool inherited = false);
    void printInstancesRequireRawIsa(bool inherited);

    bool canAllocNonpointer() {
        assert(!isFuture());
        return !instancesRequireRawIsa();
    }
    bool canAllocFast() {
        assert(!isFuture());
        return bits.canAllocFast();
    }


    bool hasCxxCtor() {
        // addSubclass() propagates this flag from the superclass.
        assert(isRealized());
        return bits.hasCxxCtor();
    }
    void setHasCxxCtor() { 
        bits.setHasCxxCtor();
    }

    bool hasCxxDtor() {
        // addSubclass() propagates this flag from the superclass.
        assert(isRealized());
        return bits.hasCxxDtor();
    }
    void setHasCxxDtor() { 
        bits.setHasCxxDtor();
    }


    bool isSwift() {
        return bits.isSwift();
    }


    // Return YES if the class's ivars are managed by ARC, 
    // or the class is MRC but has ARC-style weak ivars.
    bool hasAutomaticIvars() {
        return data()->ro->flags & (RO_IS_ARC | RO_HAS_WEAK_WITHOUT_ARC);
    }

    // Return YES if the class's ivars are managed by ARC.
    bool isARC() {
        return data()->ro->flags & RO_IS_ARC;
    }


#if SUPPORT_NONPOINTER_ISA
    // Tracked in non-pointer isas; not tracked otherwise
#else
    bool instancesHaveAssociatedObjects() {
        // this may be an unrealized future class in the CF-bridged case
        assert(isFuture()  ||  isRealized());
        return data()->flags & RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS;
    }

    void setInstancesHaveAssociatedObjects() {
        // this may be an unrealized future class in the CF-bridged case
        assert(isFuture()  ||  isRealized());
        setInfo(RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS);
    }
#endif

    bool shouldGrowCache() {
        return true;
    }

    void setShouldGrowCache(bool) {
        // fixme good or bad for memory use?
    }

    bool isInitializing() {
        return getMeta()->data()->flags & RW_INITIALIZING;
    }

    void setInitializing() {
        assert(!isMetaClass());
        ISA()->setInfo(RW_INITIALIZING);
    }

    bool isInitialized() {
        return getMeta()->data()->flags & RW_INITIALIZED;
    }

    void setInitialized();

    bool isLoadable() {
        assert(isRealized());
        return true;  // any class registered for +load is definitely loadable
    }

    IMP getLoadMethod();

    // Locking: To prevent concurrent realization, hold runtimeLock.
    bool isRealized() {
        return data()->flags & RW_REALIZED;
    }

    // Returns true if this is an unrealized future class.
    // Locking: To prevent concurrent realization, hold runtimeLock.
    bool isFuture() { 
        return data()->flags & RW_FUTURE;
    }

    bool isMetaClass() {
        assert(this);
        assert(isRealized());
        return data()->ro->flags & RO_META;
    }

    // NOT identical to this->ISA when this is a metaclass
    Class getMeta() {
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }

    bool isRootClass() {
        return superclass == nil;
    }
    bool isRootMetaclass() {
        return ISA() == (Class)this;
    }

    const char *mangledName() { 
        // fixme can't assert locks here
        assert(this);

        if (isRealized()  ||  isFuture()) {
            return data()->ro->name;
        } else {
            return ((const class_ro_t *)data())->name;
        }
    }
    
    const char *demangledName(bool realize = false);
    const char *nameForLogging();

    // May be unaligned depending on class's ivars.
    uint32_t unalignedInstanceStart() {
        assert(isRealized());
        return data()->ro->instanceStart;
    }

    // Class's instance start rounded up to a pointer-size boundary.
    // This is used for ARC layout bitmaps.
    uint32_t alignedInstanceStart() {
        return word_align(unalignedInstanceStart());
    }

    // May be unaligned depending on class's ivars.
    uint32_t unalignedInstanceSize() {
        assert(isRealized());
        return data()->ro->instanceSize;
    }

    // Class's ivar size rounded up to a pointer-size boundary.
    uint32_t alignedInstanceSize() {
        return word_align(unalignedInstanceSize());
    }

    size_t instanceSize(size_t extraBytes) {
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }

    void setInstanceSize(uint32_t newSize) {
        assert(isRealized());
        if (newSize != data()->ro->instanceSize) {
            assert(data()->flags & RW_COPIED_RO);
            *const_cast<uint32_t *>(&data()->ro->instanceSize) = newSize;
        }
        bits.setFastInstanceSize(newSize);
    }

    void chooseClassArrayIndex();

    void setClassArrayIndex(unsigned Idx) {
        bits.setClassArrayIndex(Idx);
    }

    unsigned classArrayIndex() {
        return bits.classArrayIndex();
    }

};
複製程式碼

1.5 NSObject,objc_object,objc_class 三者的關係

1)NSObject與objc_class

NSObject有一個Class型別,名為isa成員變數

NSObject有一個Class型別,名為isa成員變數

繼續檢視Class的本質,可以發現Class 其實就是 C 語言定義的結構體型別(struct objc_class)的指標,這個宣告說明 Objective-C 的 實際上就是 struct objc_class

Class的本質

另外,第二個定義是經常遇到的 id 型別,這裡可以看出 id 型別是 C 語言定義的結構體型別(struct objc_object)的指標,我們知道我們可以用 id 來宣告一個物件,所以這也說明了 Objective-C 的 物件 實際上就是 struct objc_object

2)objc_object與objc_class

繼續檢視objc_class的本質,可以發現objc_class是一個 繼承 自objc_object的結構體。所以 Objective-C 中的 自身也是一個 物件,只是除了 objc_object 中定義的成員變數外,還有另外三個成員變數:superclass、cache 和 bits。

objc_class繼承自objc_object

注意,這裡面的 “結構體” 並非 C語言 裡面的結構體,而是 C++語言 裡面的結構體,而且這個概念僅限字面意思的結構體。嚴格來講,其實struct關鍵字定義的是 ,跟class關鍵字定義的類除了預設訪問許可權的區別,沒有區別。這一點,國內人寫的C++書籍卻很少有注意到。下面是比較權威的《C++ Primer》(第546頁)一書關於這點的說明。

C++ Primer

3)知識補課

C++中的struct對C中的struct進行了擴充,它已經不再只是一個包含不同資料型別的資料結構了,它已經獲取了太多的功能。下面簡單列一下C++的struct跟C中的struct不一樣的地方:

  • struct能包含成員函式
  • struct能繼承
  • struct能實現多型

2. 手動引用對引用計數的影響 -- retain操作

2.1 兩種物件:NSObject與Object的引用增加

① NSObject的retain

NSObject.mm retain

+ (id)retain {
    return (id)self;
}

// Replaced by ObjectAlloc
- (id)retain {
    return ((id)self)->rootRetain();
}
複製程式碼
② Object的retain

Object.mm retain

+(id) retain
{
    return self;
}

-(id) retain
{
    return _objc_rootRetain(self);
}
複製程式碼

NSObject.mm _objc_rootRetain(id obj)

id
_objc_rootRetain(id obj)
{
    assert(obj);

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

可見,無論是NSObject還是Object的 retain,歸根結底,呼叫的都是 objc_objectrootRetain()

2.2 歸根結底 -- NSObject物件的rootRetain()

objc4/objc4-723/runtime/objc-object.h objc_object::rootRetain()

ALWAYS_INLINE id 
objc_object::rootRetain()
{
    return rootRetain(false, false);
}
複製程式碼

objc4/objc4-723/runtime/objc-object.h objc_object::rootRetain(bool tryRetain, bool handleOverflow)

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)) {
            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;
        }
        uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            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.
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            newisa.extra_rc = RC_HALF;
            newisa.has_sidetable_rc = true;
        }
    } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));

    if (slowpath(transcribeToSideTable)) {
        // 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;
}
複製程式碼

其中,手動retain對引用計數的影響關鍵在這麼一句話:

newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++
複製程式碼

對isa的 extra_rc 變數進行+1,前面說到isa會存很多東西。

3. isa與Tagged Pointer

3.1 NSObject的唯一成員變數 -- isa

NSObject.h NSObject的isa

OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
OBJC_ROOT_CLASS
OBJC_EXPORT
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
複製程式碼

其中,Class isa繼續檢視Class的定義:

objc-private.h Class

typedef struct objc_class *Class;
typedef struct objc_object *id;
複製程式碼

其中,objc_object類內部結構:

蘋果iOS系統原始碼思考:物件的引用計數儲存在哪裡?--從runtime原始碼得到的啟示

其中,私有的成員資料isa為isa_t型別的聯合體:

objc-private.h isa_t

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;

#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
    struct {
        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
    struct {
        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


#if SUPPORT_INDEXED_ISA

# if  __ARM_ARCH_7K__ >= 2

#   define ISA_INDEX_IS_NPI      1
#   define ISA_INDEX_MASK        0x0001FFFC
#   define ISA_INDEX_SHIFT       2
#   define ISA_INDEX_BITS        15
#   define ISA_INDEX_COUNT       (1 << ISA_INDEX_BITS)
#   define ISA_INDEX_MAGIC_MASK  0x001E0001
#   define ISA_INDEX_MAGIC_VALUE 0x001C0001
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t indexcls          : 15;
        uintptr_t magic             : 4;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 7;
#       define RC_ONE   (1ULL<<25)
#       define RC_HALF  (1ULL<<6)
    };

# else
#   error unknown architecture for indexed isa
# endif

// SUPPORT_INDEXED_ISA
#endif

};
複製程式碼

其中,cls 變數會指向物件所屬的類的結構,在 64 位裝置上會佔用 8byte。

另外,bits 變數儲存著isa的唯一標誌(可以根據bits獲取isa),是一個型別為 uintptr_t 的資料, uintptr_t的定義:

typedef unsigned long		uintptr_t;
複製程式碼
知識回顧

不熟悉C++的朋友可能很難看出來bits會是如何初始化的,其實,這是一種與建構函式並列的初始化辦法 -- 初始化列表。關於初始化列表的定義,擷取百度百科的一段話:

初始化列表的用法

所以,再回過來看bitsbitsisa_t(uintptr_t value)中的value為初始化的值:

`bits`

例如isa初始化的API objc_object::initIsa(Class cls)`中,有這樣一句:

isa_t newisa(0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
//...
複製程式碼

而這個bits值可以用來獲取isa(注意區分左右兩邊的bits分別是兩個東西):

isa_t bits = LoadExclusive(&isa.bits);
複製程式碼

其中,LoadExclusive根據平臺的不同,實現體並不一樣,這是__arm64__平臺的實現體:

#if __arm64__

static ALWAYS_INLINE
uintptr_t 
LoadExclusive(uintptr_t *src)
{
    uintptr_t result;
    asm("ldxr %x0, [%x1]" 
        : "=r" (result) 
        : "r" (src), "m" (*src));
    return result;
}
複製程式碼

對這個isa (這裡是左邊的bits,它是個isa,而非右邊的uintptr_t) 的呼叫,比如獲取引用計數的原始碼中就有幾處:

inline uintptr_t 
objc_object::rootRetainCount()
{
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    if (bits.nonpointer) {
        uintptr_t rc = 1 + bits.extra_rc;
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}
複製程式碼

呼叫的有: bits.extra_rc bits.nonpointer bits.has_sidetable_rc

3.2 isa_t聯合體裡面struct的資料含義

nonpointer

該變數佔用 1bit 記憶體空間,可以有兩個值:0 和 1,分別代表不同的 isa_t 的型別:

  • 0 表示 isa_t 沒有開啟指標優化,不使用 isa_t 中定義的結構體。訪問 objc_object 的 isa 會直接返回 isa_t 結構中的 cls 變數,cls 變數會指向物件所屬的類的結構,在 64 位裝置上會佔用 8byte。

  • 1 表示 isa_t 開啟了指標優化,不能直接訪問 objc_object 的 isa 成員變數 (因為 isa 已經不是一個合法的記憶體指標了,而是一個 Tagged Pointer ),從其名字 nonpointer 也可獲知這個 isa 已經不是一個指標了。但是 isa 中包含了類資訊、物件的引用計數等資訊,在 64 位裝置上充分利用了記憶體空間。

shiftcls

儲存類指標的值。開啟指標優化的情況下,在 arm64 架構中有 33 位用來儲存類指標。

has_assoc

該變數與物件的關聯引用有關,當物件有關聯引用時,釋放物件時需要做額外的邏輯。關聯引用就是我們通常用 objc_setAssociatedObject 方法設定給物件的,這裡對於關聯引用不做過多分析,如果後續有時間寫關聯引用實現時再深入分析關聯引用有關的程式碼。

has_cxx_dtor

表示該物件是否有 C++ 或者 Objc 的析構器,如果有解構函式,則需要做析構邏輯,如果沒有,則可以更快的釋放物件。

magic

用於判斷物件是否已經完成了初始化,在 arm64 中 0x16 是偵錯程式判斷當前物件是真的物件還是沒有初始化的空間(在 x86_64 中該值為 0x3b)。

weakly_referenced

標誌物件是否被指向或者曾經指向一個 ARC 的弱變數,沒有弱引用的物件可以更快釋放。

deallocating

標誌物件是否正在釋放記憶體。

extra_rc

表示該物件的引用計數值,實際上是引用計數值減 1,例如,如果物件的引用計數為 10,那麼 extra_rc 為 9。如果引用計數大於 10,則需要使用到下面的 has_sidetable_rc。

has_sidetable_rc

當物件引用計數大於 10 時,則has_sidetable_rc 的值為 1,那麼引用計數會儲存在一個叫 SideTable 的類的屬性中,這是一個雜湊表。

ISA_MAGIC_MASK

通過掩碼方式獲取 magic 值。

ISA_MASK

通過掩碼方式獲取 isa 的類指標值。

RC_ONERC_HALF

用於引用計數的相關計算。

3.3 isa_t聯合體裡面的巨集

SUPPORT_PACKED_ISA

表示平臺是否支援在 isa 指標中插入除 Class 之外的資訊。

  • 如果支援就會將 Class 資訊放入 isa_t 定義的 struct 內,並附上一些其他資訊,例如上面的 nonpointer 等等;
  • 如果不支援,那麼不會使用 isa_t 內定義的 struct,這時 isa_t 只使用 cls(Class 指標)。

小結在 iOS 以及 MacOSX 裝置上,SUPPORT_PACKED_ISA 定義為 1

__arm64____x86_64__

表示 CPU 架構,例如電腦一般是 __x86_64__ 架構,手機一般是 arm 結構,這裡 64 代表是 64 位 CPU。上面只列出了 __arm64__ 架構的定義。

小結iOS 裝置上 __arm64__ 是 1

SUPPORT_INDEXED_ISA

SUPPORT_INDEXED_ISA 表示 isa_t 中存放的 Class 資訊是 Class 的地址,還是一個索引(根據該索引可在類資訊表中查詢該類結構地址)。可以看出,多了一個 uintptr_t indexcls : 15;

小結iOS 裝置上 SUPPORT_INDEXED_ISA 是 0

3.4 是否Tagged Pointer的判斷

objc-object.h objc_object::isTaggedPointer()

inline bool 
objc_object::isTaggedPointer() 
{
    return _objc_isTaggedPointer(this);
}
複製程式碼

objc-internal.h _objc_isTaggedPointer(const void * _Nullable ptr)

static inline bool 
_objc_isTaggedPointer(const void * _Nullable ptr) 
{
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
複製程式碼

3.5 與isa型別有關的巨集

SUPPORT_NONPOINTER_ISA

用於標記是否支援優化的 isa 指標,其字面含義意思是 isa 的內容不再是類的指標了,而是包含了更多資訊,比如引用計數,析構狀態,被其他 weak 變數引用情況。下面看看SUPPORT_NONPOINTER_ISA及其相關巨集的定義:

objc-config.h SUPPORT_TAGGED_POINTERS

// Define SUPPORT_TAGGED_POINTERS=1 to enable tagged pointer objects
// Be sure to edit tagged pointer SPI in objc-internal.h as well.
#if !(__OBJC2__  &&  __LP64__)
#   define SUPPORT_TAGGED_POINTERS 0
#else
#   define SUPPORT_TAGGED_POINTERS 1
#endif

// Define SUPPORT_MSB_TAGGED_POINTERS to use the MSB 
// as the tagged pointer marker instead of the LSB.
// Be sure to edit tagged pointer SPI in objc-internal.h as well.
#if !SUPPORT_TAGGED_POINTERS  ||  !TARGET_OS_IPHONE
#   define SUPPORT_MSB_TAGGED_POINTERS 0
#else
#   define SUPPORT_MSB_TAGGED_POINTERS 1
#endif

// 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
#   define SUPPORT_INDEXED_ISA 1
#else
#   define SUPPORT_INDEXED_ISA 0
#endif

// Define SUPPORT_PACKED_ISA=1 on platforms that store the class in the isa 
// field as a maskable pointer with other data around it.
#if (!__LP64__  ||  TARGET_OS_WIN32  ||  TARGET_OS_SIMULATOR)
#   define SUPPORT_PACKED_ISA 0
#else
#   define SUPPORT_PACKED_ISA 1
#endif

// Define SUPPORT_NONPOINTER_ISA=1 on any platform that may store something
// in the isa field that is not a raw pointer.
#if !SUPPORT_INDEXED_ISA  &&  !SUPPORT_PACKED_ISA
#   define SUPPORT_NONPOINTER_ISA 0
#else
#   define SUPPORT_NONPOINTER_ISA 1
#endif
複製程式碼

3.6 怎麼判斷是否支援優化的isa指標?-- 看裝置、自己設定。

  • 已知iOS系統的SUPPORT_PACKED_ISA為1,SUPPORT_INDEXED_ISA為0,根據4.5節中原始碼的定義可知,iOS系統的SUPPORT_NONPOINTER_ISA為1。

  • 在環境變數中設定OBJC_DISABLE_NONPOINTER_ISA

即,iOS系統 支援 優化的isa指標

在 64 位環境下,優化的 isa 指標並不是就一定會儲存引用計數,畢竟用 19bit (iOS 系統)儲存引用計數不一定夠。需要注意的是這 19 位儲存的是引用計數的值減一。

3.7 怎麼判斷是否Tagged Pointer的物件?-- 看物件、自己設定

  • 可以啟用Tagged Pointer的類物件有:NSDate、NSNumber、NSString。Tagged Pointer專門用來儲存小的物件。

  • 在環境變數中設定OBJC_DISABLE_TAGGED_POINTERS=YES強制不啟用Tagged Pointer。

3.8 引用計數的儲存形式 -- 雜湊表

下面對sidetable_retain進行分析。

4. 雜湊表

4.1 增加引用計數 -- sidetable_retain()

第2節的增加引用假設,以及後面第8節的獲取引用計數會用到下面的API:

NSObject.mm objc_object::sidetable_retain()

id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];
    
    table.lock();
    size_t& refcntStorage = table.refcnts[this];
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    table.unlock();

    return (id)this;
}
複製程式碼

4.2 增加引用計數 -- sidetable_tryRetain()

NSObject.mm objc_object::sidetable_tryRetain()

bool
objc_object::sidetable_tryRetain()
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];

    // NO SPINLOCK HERE
    // _objc_rootTryRetain() is called exclusively by _objc_loadWeak(), 
    // which already acquired the lock on our behalf.

    // fixme can't do this efficiently with os_lock_handoff_s
    // if (table.slock == 0) {
    //     _objc_fatal("Do not call -_tryRetain.");
    // }

    bool result = true;
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) {
        table.refcnts[this] = SIDE_TABLE_RC_ONE;
    } else if (it->second & SIDE_TABLE_DEALLOCATING) {
        result = false;
    } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
        it->second += SIDE_TABLE_RC_ONE;
    }
    
    return result;
}
複製程式碼

4.3 獲取雜湊表 -- SideTable()

NSObject.mm SideTable

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;

    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }

    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }

    void lock() { slock.lock(); }
    void unlock() { slock.unlock(); }
    void forceReset() { slock.forceReset(); }

    // Address-ordered lock discipline for a pair of side tables.

    template<HaveOld, HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<HaveOld, HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
複製程式碼

其中,RefcountMap以及HaveOldHaveNew的定義為:

// RefcountMap disguises its pointers because we 
// don't want the table to act as a root for `leaks`.
typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;

// Template parameters.
enum HaveOld { DontHaveOld = false, DoHaveOld = true };
enum HaveNew { DontHaveNew = false, DoHaveNew = true };
複製程式碼

llvm-DenseMap.h DenseMap/DenseMapBase

位置

DenseMapBase

DenseMapBase

DenseMap

DenseMap

5. 設定變數導致的引用計數變化 -- objc_retain操作

5.1 情況1 -- strong

runtime.h 設定strong變數

/** 
 * Sets the value of an instance variable in an object.
 * 
 * @param obj The object containing the instance variable whose value you want to set.
 * @param ivar The Ivar describing the instance variable whose value you want to set.
 * @param value The new value for the instance variable.
 * 
 * @note Instance variables with known memory management (such as ARC strong and weak)
 *  use that memory management. Instance variables with unknown memory management 
 *  are assigned as if they were strong.
 * @note \c object_setIvar is faster than \c object_setInstanceVariable if the Ivar
 *  for the instance variable is already known.
 */
OBJC_EXPORT void
object_setIvarWithStrongDefault(id _Nullable obj, Ivar _Nonnull ivar,
                                id _Nullable value) 
    OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0);
複製程式碼

objc-class.mm object_setIvarWithStrongDefault

void object_setIvarWithStrongDefault(id obj, Ivar ivar, id value)
{
    return _object_setIvar(obj, ivar, value, true /*strong default*/);
}
複製程式碼

objc-class.mm _object_setIvar

static ALWAYS_INLINE 
void _object_setIvar(id obj, Ivar ivar, id value, bool assumeStrong)
{
    if (!obj  ||  !ivar  ||  obj->isTaggedPointer()) return;

    ptrdiff_t offset;
    objc_ivar_memory_management_t memoryManagement;
    _class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement);

    if (memoryManagement == objc_ivar_memoryUnknown) {
        if (assumeStrong) memoryManagement = objc_ivar_memoryStrong;
        else memoryManagement = objc_ivar_memoryUnretained;
    }

    id *location = (id *)((char *)obj + offset);

    switch (memoryManagement) {
    case objc_ivar_memoryWeak:       objc_storeWeak(location, value); break;
    case objc_ivar_memoryStrong:     objc_storeStrong(location, value); break;
    case objc_ivar_memoryUnretained: *location = value; break;
    case objc_ivar_memoryUnknown:    _objc_fatal("impossible");
    }
}
複製程式碼

NSObject.mm objc_storeStrong

void
objc_storeStrong(id *location, id obj)
{
    id prev = *location;
    if (obj == prev) {
        return;
    }
    objc_retain(obj);
    *location = obj;
    objc_release(prev);
}
複製程式碼

5.2 情況2 -- weak

objc-class.mm 設定weak變數

object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
複製程式碼

objc-class.mm object_setIvar

void object_setIvar(id obj, Ivar ivar, id value)
{
    return _object_setIvar(obj, ivar, value, false /*not strong default*/);
}
複製程式碼
  • 可見,這裡同樣呼叫了 _object_setIvar,程式碼情況1,是同一個API。其中,不同於objc_storeStrong,走的是objc_storeWeak,下面分析一下:

NSObject.mm 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<DoHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object *)newObj),它的程式碼有點長,核心的關鍵是更新了weak雜湊表:->weak_table。讀者可以從下面搜尋一下這個關鍵詞的位置。

// 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.
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;
    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) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    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()) 
        {
            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;

            goto retry;
        }
    }

    // Clean up old value, if any.
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

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

        // Set is-weakly-referenced bit in refcount table.
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj;
}
複製程式碼

5.3 objc_storeStrong導致的retain

上面第5.1節中有一個objc_storeStrong,這裡繼續分析它的原理。

NSObject.mm objc_storeStrong(id *location, id obj)

void
objc_storeStrong(id *location, id obj)
{
    id prev = *location;
    if (obj == prev) {
        return;
    }
    objc_retain(obj);
    *location = obj;
    objc_release(prev);
}
複製程式碼

NSObject.mm objc_retain(id obj)

/***********************************************************************
* Optimized retain/release/autorelease entrypoints
**********************************************************************/


#if __OBJC2__

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


__attribute__((aligned(16)))
void 
objc_release(id obj)
{
    if (!obj) return;
    if (obj->isTaggedPointer()) return;
    return obj->release();
}


__attribute__((aligned(16)))
id
objc_autorelease(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->autorelease();
}


// OBJC2
#else
// not OBJC2


id objc_retain(id obj) { return [obj retain]; }
void objc_release(id obj) { [obj release]; }
id objc_autorelease(id obj) { return [obj autorelease]; }


#endif
複製程式碼

可知: 1)如果TaggedPointer,則返回本身。 2)如果非TaggedPointer,則由物件的retain()返回。

objc-object.h objc_object::retain()

// Equivalent to calling [this retain], with shortcuts if there is no override
inline id 
objc_object::retain()
{
    assert(!isTaggedPointer());

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

    return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}
複製程式碼

objc-object.h objc_object::rootRetain()

// Base retain implementation, ignoring overrides.
// This does not check isa.fast_rr; if there is an RR override then 
// it was already called and it chose to call [super retain].
//
// tryRetain=true is the -_tryRetain path.
// handleOverflow=false is the frameless fast path.
// handleOverflow=true is the framed slow path including overflow to side table
// The code is structured this way to prevent duplication.

ALWAYS_INLINE id 
objc_object::rootRetain()
{
    return rootRetain(false, false);
}
複製程式碼

這裡的rootRetain(false, false);正是上文第2.2節中介紹的,不再贅述。

6. 新建物件(分配記憶體與初始化)導致的引用計數變化 -- alloc 和 init 操作

首先,新建一個物件的典型寫法:

NSObject *obj = [NSObject alloc] init];
複製程式碼

6.1 分配記憶體 -- 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*/);
}
複製程式碼
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate 
// shortcutting optimizations.
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())) {
            // No ctors, raw isa, etc. Go straight to the metal.
            bool dtor = cls->hasCxxDtor();
            id obj = (id)calloc(1, cls->bits.fastInstanceSize());
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            obj->initInstanceIsa(cls, dtor);
            return obj;
        }
        else {
            // Has ctor or raw isa or something. Use the slower path.
            id obj = class_createInstance(cls, 0);
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            return obj;
        }
    }
#endif

    // No shortcuts available.
    if (allocWithZone) return [cls allocWithZone:nil];
    return [cls alloc];
}
複製程式碼
分支1 -- obj->initInstanceIsa(cls, dtor);
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 = cls;
    } else {
        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;
    }
}
複製程式碼

上述程式碼中,newisa.bits = ISA_MAGIC_VALUE; 是為了對 isa 結構賦值一個初始值,通過對 isa_t 的結構分析,我們可以知道此次賦值只是對 nonpointer 和 magic 部分進行了賦值。

newisa.shiftcls = (uintptr_t)cls >> 3; 是將類的地址儲存在物件的 isa 結構中。這裡右移三位的主要原因是用於將 Class 指標中無用的後三位清除減小記憶體的消耗,因為類的指標要按照位元組(8 bits)對齊記憶體,其指標後三位都是沒有意義的 0。關於類指標對齊的詳細解析可參考:從 NSObject 的初始化了解 isa

分支2 -- id obj = class_createInstance(cls, 0);
id 
class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}
複製程式碼
/***********************************************************************
* class_createInstance
* fixme
* Locking: none
**********************************************************************/

static __attribute__((always_inline)) 
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    if (!cls) return nil;

    assert(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();

    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) {
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if (!obj) return nil;

        // Use raw pointer isa on the assumption that they might be 
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    return obj;
}
複製程式碼

其中,有個 obj->initIsa(cls);,初始化isa的操作:

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    assert(!isTaggedPointer()); 
    
    if (!nonpointer) {
        isa.cls = cls;
    } else {
        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;
    }
}
複製程式碼

可見,alloc的時候會初始化isa,並通過newisa(0)的初始化列表辦法生成一個isa,並根據是否支援indexed isa分別設定.bits的值。

6.2 初始化 -- 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;
}
複製程式碼

7. 獲取引用計數

NSObject.mm retainCount

- (NSUInteger)retainCount {
    return ((id)self)->rootRetainCount();
}
複製程式碼

objc-object.h objc_object::rootRetainCount()

inline uintptr_t 
objc_object::rootRetainCount()
{
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    if (bits.nonpointer) {
        uintptr_t rc = 1 + bits.extra_rc;
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}
複製程式碼

可見,獲取引用計數的關鍵在這麼一句話:

uintptr_t rc = 1 + bits.extra_rc;
複製程式碼

bits.extra_rc表示引用計數減1。當然,這隻針對情況1,即bits.nonpointer為1(開啟了指標優化),且bits.has_sidetable_rc為0(表示不儲存在雜湊表Side Table中,而儲存在extra_rc中)。

  • 情況0 -- TaggedPointer

直接返回isa值本身。

  • 情況1 -- 非TaggedPointer,且開啟了指標優化,且儲存在extra_rc

objc-os.h LoadExclusive(uintptr_t *src)

static ALWAYS_INLINE
uintptr_t 
LoadExclusive(uintptr_t *src)
{
    return *src;
}
複製程式碼
  • 情況2 -- 非TaggedPointer,且沒有開啟指標優化,且儲存在雜湊表中

NSObject.mm objc_object::sidetable_getExtraRC_nolock()

size_t 
objc_object::sidetable_getExtraRC_nolock()
{
    assert(isa.nonpointer);
    SideTable& table = SideTables()[this];
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) return 0;
    else return it->second >> SIDE_TABLE_RC_SHIFT;
}
複製程式碼

可見,其邏輯就是先從 SideTable 的靜態方法獲取當前例項對應的 SideTable 物件,其 refcnts 屬性就是之前說的儲存引用計數的雜湊表,這裡將其型別簡寫為 RefcountMap:

typedef objc::DenseMap RefcountMap;
複製程式碼

然後在引用計數表中用迭代器查詢當前例項對應的鍵值對,獲取引用計數值,並在此基礎上 +1 並將結果返回。這也就是為什麼之前說引用計數表儲存的值為實際引用計數減一。

需要注意的是為什麼這裡把鍵值對的值做了向右移位操作(it->second >> SIDE_TABLE_RC_SHIFT):

// 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)
複製程式碼

可以看出值的第一個 bit 表示該物件是否有過 weak 物件,如果沒有,在析構釋放記憶體時可以更快;第二個 bit 表示該物件是否正在析構。從第三個 bit 開始才是儲存引用計數數值的地方。所以這裡要做向右移兩位的操作,而對引用計數的 +1 和 -1 可以使用 SIDE_TABLE_RC_ONE,還可以用 SIDE_TABLE_RC_PINNED 來判斷是否引用計數值有可能溢位。

  • 情況3 -- 預設值

NSObject.mm objc_object::sidetable_retainCount()

uintptr_t
objc_object::sidetable_retainCount()
{
    SideTable& table = SideTables()[this];

    size_t refcnt_result = 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;
    }
    table.unlock();
    return refcnt_result;
}
複製程式碼

8. 結論

  1. 如果有些物件支援使用 TaggedPointer
  • 蘋果會直接將物件的指標值作為引用計數返回。
  1. 如果另外一些物件不支援使用 TaggedPointer
  • 如果當前裝置是 64 位環境並且使用 Objective-C 2.0,那麼會使用物件的 isa 指標一部分空間bits.extra_rc)來儲存它的引用計數;
  • 否則 Runtime 會使用一張 雜湊表SideTables())來管理引用計數。

9. 擴充閱讀

weak表

https://www.jianshu.com/p/13c4fb1cedea

相關文章