類結(jié)構(gòu)、isa指針結(jié)構(gòu)、引用計數(shù)睡雇、內(nèi)存管理等總結(jié)

零 索引

  1. 內(nèi)存管理
  2. 類結(jié)構(gòu)窺探
  3. 類結(jié)構(gòu)分解-isa結(jié)構(gòu)、superclass饮醇、cache_t它抱、class_data_bits_t
  4. 引用計數(shù)
  5. weak實現(xiàn)
  6. nonpointer和taggedpointer
  7. 自動釋放池的結(jié)構(gòu)和工作原理、autorelease與引用計數(shù)
  8. 內(nèi)存分配 class_getInstanceSize以及內(nèi)存對齊
  9. copy and mutablecopy

一 內(nèi)存管理

  1. TaggedPointer(針對類似于 NSNumber 的小對象類型)

  2. NONPOINTER_ISA(64位系統(tǒng)下)

第一位的 0 或 1 代表是純地址型 isa 指針朴艰,還是 NONPOINTER_ISA 指針观蓄。
第二位,代表是否有關(guān)聯(lián)對象
第三位代表是否有 C++ 代碼祠墅。
接下來33位代表指向的內(nèi)存地址
接下來有 弱引用 的標記
接下來有是否 delloc 的標記....等等
  1. 散列表(引用計數(shù)表侮穿、weak表)
SideTables 表在 非嵌入式的64位系統(tǒng)中,有 64張 SideTable 表
每一張 SideTable 主要是由三部分組成毁嗦。自旋鎖亲茅、引用計數(shù)表、弱引用表狗准。
全局的 引用計數(shù) 之所以不存在同一張表中克锣,是為了避免資源競爭,解決效率的問題腔长。
引用計數(shù)表 中引入了 分離鎖的概念袭祟,將一張表分拆成多個部分,對他們分別加鎖捞附,可以實現(xiàn)并發(fā)操作巾乳,提升執(zhí)行效率

4:CF框架對象和OC相互轉(zhuǎn)換

__bridge
CF和OC對象轉(zhuǎn)化時只涉及對象類型不涉及對象所有權(quán)的轉(zhuǎn)化
__bridge_retained
與__bridge_transfer 相反,常用在將OC對象轉(zhuǎn)化成CF對象鸟召,且OC對象的所有權(quán)也交給CF對象來管理想鹰,即OC對象轉(zhuǎn)化成CF對象時,涉及到對象類型和對象所有權(quán)的轉(zhuǎn)化药版,作用同CFBridgingRetain()
_bridge_transfer
常用在CF對象轉(zhuǎn)化成OC對象時,將CF對象的所有權(quán)交給OC對象喻犁,此時ARC就能自動管理該內(nèi)存,作用同CFBridgingRelease()

二 類結(jié)構(gòu)窺探

類的結(jié)構(gòu)是什么槽片?當(dāng)問到類的本質(zhì)是什么的時候何缓,我們應(yīng)該都知道是結(jié)構(gòu)體。下面我們就通過編譯源碼來看一下还栓,類的本質(zhì)碌廓。

我們創(chuàng)建一個類:

LGPerson *person = [LGPerson alloc]
Class pClass     = object_getClass(person);
image.png

然后我們4gx打印pClass


image.png

接下來我們進行clang調(diào)試 我們OC代碼被編譯了這個樣子,也是runtime的mesg


image.png

我們?nèi)炙阉鱈GPerson 可以看到LGPerson的具體是什么了剩盒,strut 結(jié)構(gòu)體


image.png

我們想知道類的結(jié)構(gòu)嗎谷婆,所以我們需要繼續(xù)查找 class 就是我們所說的類,這個源碼可以很清楚的明白辽聊,class的真正類型是 objc_class

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

可見纪挎,Class是指向C的結(jié)構(gòu)體objc_class的指針,我們再看一下objc_class的定義

在Objc2.0之前,objc_class源碼如下:

struct objc_class {
Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

 #if !__OBJC2__
Class _Nullable super_class                              OBJC2_UNAVAILABLE;
const char * _Nonnull name                               OBJC2_UNAVAILABLE;
long version                                             OBJC2_UNAVAILABLE;
long info                                                OBJC2_UNAVAILABLE;
long instance_size                                       OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

在這里可以看到跟匆,在一個類中异袄,有超類的指針,類名玛臂,版本的信息烤蜕。
ivars是objc_ivar_list成員變量列表的指針;methodLists是指向objc_method_list指針的指針迹冤。methodLists是指向方法列表的指針讽营。這里如果動態(tài)修改methodLists的值來添加成員方法,這也是Category實現(xiàn)的原理泡徙,同樣解釋了Category不能添加屬性的原因橱鹏。

然后在2006年蘋果發(fā)布Objc 2.0之后,objc_class的定義就變成下面這個樣子了锋勺。

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

@interface Object { 
    Class isa; 
}

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

struct objc_object {
private:
    isa_t isa;
}

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
}

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;
}

 struct objc_class : objc_object {
// Class ISA; // 8
Class superclass; // 8
cache_t cache;    // 16 不是8         // 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);
}
image.png

看這個結(jié)構(gòu)內(nèi)部含有一個class isa蚀瘸,注釋了,說明這是個隱藏的類庶橱,所以isa 肯定是繼承父類的.驗證一下贮勃,jump 下就可以你看到.這個內(nèi)部第一個是isa ,第二個就是superclass苏章,第三個是cache寂嘉,第四個是bit,這樣我們就可以和4gx 打印的吻合了枫绅!我們po 出來的NSObject 就是父類

 /// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

屬性和方法是存在哪里泉孩?

之前已經(jīng)確定了 0x001d800100002389 為isa指針,0x0000000100b37140為superclass并淋,0x00000001003da260這里為cache_t寓搬,最后為bit,我們先讀取bit里面有什么县耽,需要使用地址偏移
isa 占8個字節(jié)句喷,superclass占8個字節(jié)镣典,chache_t 內(nèi)部代碼

  struct cache_t {
  struct bucket_t *_buckets; // 8
  mask_t _mask;  // 4
  mask_t _occupied; // 4

所以chache_t 占16個字節(jié),看下偏移結(jié)果

image.png

我們想看我們定義的屬性在哪里,我們可以看見一個properties唾琼,那么久 p 下
image.png

發(fā)現(xiàn)了我們的屬性兄春,name
image.png

但是驚人的事情發(fā)生了,其實屬性不在properlist里面锡溯,這個是個意外赶舆,其實是放在 ro 里面的!
image.png

image.png

其實這個就是我們設(shè)置屬性的Nickname,實例變量是放在ivas里的 p 下
image.png

實例變量和屬性是有區(qū)別的祭饭,存儲位置不同 name我們可以看到ivars 里面的count 為2 芜茵,其實我們添加的屬性,編譯進來了甜癞,_nickName夕晓;
image.png

image.png

是不是想到了runtime copyivarlist 獲取的屬性為帶下劃線的
屬性存在當(dāng)前的類里面,并且就在bits里面

方法在哪悠咱?正好在p 屬性的時候蒸辆,可以看見methodlist,方法是否在這個里面析既?驗證一下


image.png

確實是我們定義方法躬贡,但是數(shù)量是4,因為屬性眼坏,系統(tǒng)會默認添加set拂玻,get方法,但是數(shù)量還是差一個宰译,我們p 一下


image.png

可以看出多出的那一個為系統(tǒng)的c++方法檐蚜, 但是我們還添加了一個類方法,只在這里看到了對象方法沿侈,類方法沒有找到呢闯第,這里是找不到了!我們嘗試用代碼獲取方法列表

void testIMP_classToMetaclass(Class pClass){

const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);

IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));

IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));
IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));

NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
NSLog(@"%s",__func__);
}

打印結(jié)果:
0x100002228-0x0-0x0-0x1000021c0

所以 對象方法在當(dāng)前類返回了地址缀拭,類方法在元類返回了地址咳短,所以類方法存在元類里面

三 類結(jié)構(gòu)分解-isa結(jié)構(gòu)、superclass蛛淋、cache_t咙好、class_data_bits_t

從一中,我們可以看到褐荷,Objective-C 對象都是 C 語言結(jié)構(gòu)體實現(xiàn)的勾效,在objc2.0中,所有的對象都會包含一個isa_t類型的結(jié)構(gòu)體。

objc_object被源碼typedef成了id類型葵第,這也就是我們平時遇到的id類型绘迁。這個結(jié)構(gòu)體中就只包含了一個isa_t類型的結(jié)構(gòu)體。這個結(jié)構(gòu)體在下面會詳細分析卒密。

objc_class繼承于objc_object。所以在objc_class中也會包含isa_t類型的結(jié)構(gòu)體isa棠赛。至此哮奇,可以得出結(jié)論:Objective-C 中類也是一個對象。在objc_class中睛约,除了isa之外鼎俘,還有3個成員變量,一個是父類的指針辩涝,一個是方法緩存贸伐,最后一個這個類的實例方法鏈表。

object類和NSObject類里面分別都包含一個objc_class類型的isa怔揩。

上圖的左半邊類的關(guān)系描述完了捉邢,接著先從isa來說起

當(dāng)一個對象的實例方法被調(diào)用的時候,會通過isa找到相應(yīng)的類商膊,然后在該類的class_data_bits_t中去查找方法伏伐。class_data_bits_t是指向了類對象的數(shù)據(jù)區(qū)域。在該數(shù)據(jù)區(qū)域內(nèi)查找相應(yīng)方法的對應(yīng)實現(xiàn)晕拆。

但是在我們調(diào)用類方法的時候藐翎,類對象的isa里面是什么呢?這里為了和對象查找方法的機制一致实幕,遂引入了元類(meta-class)的概念吝镣。

關(guān)于元類,更多具體可以研究這篇文章What is a meta-class in Objective-C?

在引入元類之后昆庇,類對象和對象查找方法的機制就完全統(tǒng)一了末贾。

對象的實例方法調(diào)用時,通過對象的 isa 在類中獲取方法的實現(xiàn)凰锡。
類對象的類方法調(diào)用時未舟,通過類的 isa 在元類中獲取方法的實現(xiàn)。

meta-class之所以重要掂为,是因為它存儲著一個類的所有類方法裕膀。每個類都會有一個單獨的meta-class,因為每個類的類方法基本不可能完全相同勇哗。

對應(yīng)關(guān)系的圖如下圖昼扛,下圖很好的描述了對象,類,元類之間的關(guān)系:


image.png

圖中實線是 super_class指針抄谐,虛線是isa指針渺鹦。

  1. Root class (class)其實就是NSObject,NSObject是沒有超類的蛹含,所以Root class(class)的superclass指向nil毅厚。
  2. 每個Class都有一個isa指針指向唯一的Meta class
  3. Root class(meta)的superclass指向Root class(class),也就是NSObject浦箱,形成一個回路吸耿。
  4. 每個Meta class的isa指針都指向Root class (meta)。

我們其實應(yīng)該明白酷窥,類對象和元類對象是唯一的咽安,對象是可以在運行時創(chuàng)建無數(shù)個的。而在main方法執(zhí)行之前蓬推,從 dyld到runtime這期間妆棒,類對象和元類對象在這期間被創(chuàng)建。具體可看sunnyxx這篇iOS 程序 main 函數(shù)之前發(fā)生了什么

(1)isa_t結(jié)構(gòu)體的具體實現(xiàn)

接下來我們就該研究研究isa的具體實現(xiàn)了沸伏。objc_object里面的isa是isa_t類型糕珊。通過查看源碼,我們可以知道isa_t是一個union聯(lián)合體馋评。

struct objc_object {
private:
    isa_t isa;
public:
    // 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
    void initIsa(Class cls /*indexed=false*/);
    void initInstanceIsa(Class cls, bool hasCxxDtor);
private:
    void initIsa(Class newCls, bool indexed, bool hasCxxDtor);
}

那就從initIsa方法開始研究放接。下面以arm64為例。

inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    initIsa(cls, true, hasCxxDtor);
}

inline void
objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor)
{
    if (!indexed) {
        isa.cls = cls;
    } else {
        isa.bits = ISA_MAGIC_VALUE;
        isa.has_cxx_dtor = hasCxxDtor;
        isa.shiftcls = (uintptr_t)cls >> 3;
    }
}

initIsa第二個參數(shù)傳入了一個true留特,所以initIsa就會執(zhí)行else里面的語句纠脾。

union isa_t 
{
    Class cls;
    uintptr_t bits;
    # if __arm64__ // arm64架構(gòu)
#   define ISA_MASK        0x0000000ffffffff8ULL //用來取出33位內(nèi)存地址使用(&)操作
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t nonpointer        : 1; //0:代表普通指針,1:表示優(yōu)化過的蜕青,可以存儲更多信息苟蹈。
        uintptr_t has_assoc         : 1; //是否設(shè)置過關(guān)聯(lián)對象。如果沒設(shè)置過右核,釋放會更快
        uintptr_t has_cxx_dtor      : 1; //是否有C++的析構(gòu)函數(shù)
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 內(nèi)存地址值
        uintptr_t magic             : 6; //用于在調(diào)試時分辨對象是否未完成初始化
        uintptr_t weakly_referenced : 1; //是否有被弱引用指向過
        uintptr_t deallocating      : 1; //是否正在釋放
        uintptr_t has_sidetable_rc  : 1; //引用計數(shù)器是否過大無法存儲在ISA中慧脱。如果為1,那么引用計數(shù)會存儲在一個叫做SideTable的類的屬性中
        uintptr_t extra_rc          : 19; //里面存儲的值是引用計數(shù)器減1

#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };

# elif __x86_64__ // arm86架構(gòu),模擬器是arm86
#   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
image.png

ISA_MAGIC_VALUE = 0x000001a000000001ULL轉(zhuǎn)換成二進制是11010000000000000000000000000000000000001贺喝,結(jié)構(gòu)如下圖:


image.png

關(guān)于參數(shù)的說明:

第一位index菱鸥,代表是否開啟isa指針優(yōu)化。index = 1躏鱼,代表開啟isa指針優(yōu)化氮采。

在2013年9月,蘋果推出了iPhone5s染苛,與此同時鹊漠,iPhone5s配備了首個采用64位架構(gòu)的A7雙核處理器,為了節(jié)省內(nèi)存和提高執(zhí)行效率,蘋果提出了Tagged Pointer的概念躯概。對于64位程序登钥,引入Tagged Pointer后,相關(guān)邏輯能減少一半的內(nèi)存占用娶靡,以及3倍的訪問速度提升牧牢,100倍的創(chuàng)建、銷毀速度提升姿锭。

在WWDC2013的《Session 404 Advanced in Objective-C》視頻中结执,蘋果介紹了 Tagged Pointer。 Tagged Pointer的存在主要是為了節(jié)省內(nèi)存艾凯。我們知道,對象的指針大小一般是與機器字長有關(guān)懂傀,在32位系統(tǒng)中,一個指針的大小是32位(4字節(jié)),而在64位系統(tǒng)中笛粘,一個指針的大小將是64位(8字節(jié))外臂。

假設(shè)我們要存儲一個NSNumber對象,其值是一個整數(shù)犀斋。正常情況下贝乎,如果這個整數(shù)只是一個NSInteger的普通變量,那么它所占用的內(nèi)存是與CPU的位數(shù)有關(guān)叽粹,在32位CPU下占4個字節(jié)览效,在64位CPU下是占8個字節(jié)的。而指針類型的大小通常也是與CPU位數(shù)相關(guān)虫几,一個指針所占用的內(nèi)存在32位CPU下為4個字節(jié)锤灿,在64位CPU下也是8個字節(jié)。如果沒有Tagged Pointer對象辆脸,從32位機器遷移到64位機器中后但校,雖然邏輯沒有任何變化,但這種NSNumber啡氢、NSDate一類的對象所占用的內(nèi)存會翻倍状囱。如下圖所示:

image.png

蘋果提出了Tagged Pointer對象。由于NSNumber倘是、NSDate一類的變量本身的值需要占用的內(nèi)存大小常常不需要8個字節(jié)亭枷,拿整數(shù)來說,4個字節(jié)所能表示的有符號整數(shù)就可以達到20多億(注:2^31=2147483648辨绊,另外1位作為符號位)奶栖,對于絕大多數(shù)情況都是可以處理的。所以,引入了Tagged Pointer對象之后宣鄙,64位CPU下NSNumber的內(nèi)存圖變成了以下這樣:
image.png

關(guān)于Tagged Pointer技術(shù)詳細的袍镀,可以看上面鏈接那個文章。

  • has_assoc
    對象含有或者曾經(jīng)含有關(guān)聯(lián)引用冻晤,沒有關(guān)聯(lián)引用的可以更快地釋放內(nèi)存

  • has_cxx_dtor
    表示該對象是否有 C++ 或者 Objc 的析構(gòu)器

  • shiftcls
    類的指針苇羡。arm64架構(gòu)中有33位可以存儲類指針。
    源碼中isa.shiftcls = (uintptr_t)cls >> 3;
    將當(dāng)前地址右移三位的主要原因是用于將 Class 指針中無用的后三位清除減小內(nèi)存的消耗鼻弧,因為類的指針要按照字節(jié)(8 bits)對齊內(nèi)存设江,其指針后三位都是沒有意義的 0。具體可以看從 NSObject 的初始化了解 isa這篇文章里面的shiftcls分析攘轩。

  • magic
    判斷對象是否初始化完成叉存,在arm64中0x16是調(diào)試器判斷當(dāng)前對象是真的對象還是沒有初始化的空間。

  • weakly_referenced
    對象被指向或者曾經(jīng)指向一個 ARC 的弱變量度帮,沒有弱引用的對象可以更快釋放

  • deallocating
    對象是否正在釋放內(nèi)存

  • has_sidetable_rc
    判斷該對象的引用計數(shù)是否過大歼捏,如果過大則需要其他散列表來進行存儲。

  • extra_rc
    存放該對象的引用計數(shù)值減一后的結(jié)果笨篷。對象的引用計數(shù)超過 1瞳秽,會存在這個這個里面,如果引用計數(shù)為 10率翅,extra_rc的值就為 9练俐。

ISA_MAGIC_MASK 和 ISA_MASK 分別是通過掩碼的方式獲取MAGIC值 和 isa類指針。


inline Class 
objc_object::ISA() 
{
    assert(!isTaggedPointer()); 
    return (Class)(isa.bits & ISA_MASK);
}

關(guān)于x86_64的架構(gòu)冕臭,具體可以看從 NSObject 的初始化了解 isa文章里面的詳細分析腺晾。

(2)cache_t的具體實現(xiàn)

struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
}

typedef unsigned int uint32_t;
typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits

typedef unsigned long  uintptr_t;
typedef uintptr_t cache_key_t;

struct bucket_t {
private:
    cache_key_t _key;
    IMP _imp;
}
image.png

據(jù)源碼,我們可以知道cache_t中存儲了一個bucket_t的結(jié)構(gòu)體浴韭,和兩個unsigned int的變量丘喻。

mask:分配用來緩存bucket的總數(shù)。
occupied:表明目前實際占用的緩存bucket的個數(shù)念颈。

bucket_t的結(jié)構(gòu)體中存儲了一個unsigned long和一個IMP泉粉。IMP是一個函數(shù)指針,指向了一個方法的具體實現(xiàn)榴芳。

cache_t中的bucket_t *_buckets其實就是一個散列表嗡靡,用來存儲Method的鏈表。

Cache的作用主要是為了優(yōu)化方法調(diào)用的性能窟感。當(dāng)對象receiver調(diào)用方法message時讨彼,首先根據(jù)對象receiver的isa指針查找到它對應(yīng)的類,然后在類的methodLists中搜索方法柿祈,如果沒有找到哈误,就使用super_class指針到父類中的methodLists查找哩至,一旦找到就調(diào)用方法。如果沒有找到蜜自,有可能消息轉(zhuǎn)發(fā)菩貌,也可能忽略它。但這樣查找方式效率太低重荠,因為往往一個類大概只有20%的方法經(jīng)常被調(diào)用箭阶,占總調(diào)用次數(shù)的80%。所以使用Cache來緩存經(jīng)常調(diào)用的方法戈鲁,當(dāng)調(diào)用方法時仇参,優(yōu)先在Cache查找,如果沒有找到婆殿,再到methodLists查找

(3)class_data_bits_t的具體實現(xiàn)

源碼實現(xiàn)如下:

struct class_data_bits_t {

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

struct class_rw_t {
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;
}

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};
image.png

在 objc_class結(jié)構(gòu)體中的注釋寫到 class_data_bits_t相當(dāng)于 class_rw_t指針加上 rr/alloc 的標志诈乒。

class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags

它為我們提供了便捷方法用于返回其中的 class_rw_t *指針:

class_rw_t *data() {
    return bits.data();
}

Objc的類的屬性、方法婆芦、以及遵循的協(xié)議在obj 2.0的版本之后都放在class_rw_t中抓谴。class_ro_t是一個指向常量的指針,存儲來編譯器決定了的屬性寞缝、方法和遵守協(xié)議。rw-readwrite仰泻,ro-readonly

在編譯期類的結(jié)構(gòu)中的 class_data_bits_t *data指向的是一個 class_ro_t *指針:


image.png

在運行時調(diào)用 realizeClass方法荆陆,會做以下3件事情:

  1. 從 class_data_bits_t調(diào)用 data方法,將結(jié)果從 class_rw_t強制轉(zhuǎn)換為 class_ro_t指針
  2. 初始化一個 class_rw_t結(jié)構(gòu)體
  3. 設(shè)置結(jié)構(gòu)體 ro的值以及 flag

最后調(diào)用methodizeClass方法集侯,把類里面的屬性被啼,協(xié)議,方法都加載進來棠枉。

struct method_t {
    SEL name;
    const char *types;
    IMP imp;

    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };
};

方法method的定義如上浓体。里面包含3個成員變量。SEL是方法的名字name辈讶。types是Type Encoding類型編碼命浴,類型可參考Type Encoding,在此不細說贱除。

IMP是一個函數(shù)指針生闲,指向的是函數(shù)的具體實現(xiàn)。在runtime中消息傳遞和轉(zhuǎn)發(fā)的目的就是為了找到IMP月幌,并執(zhí)行函數(shù)碍讯。

整個運行時過程可以描述如下:

image.png

更加詳細的分析,請看@Draveness 的這篇文章深入解析 ObjC 中方法的結(jié)構(gòu)
到此扯躺,總結(jié)一下objc_class 1.0和2.0的差別捉兴。
image.png

image.png

四 引用計數(shù)

有些對象如果支持使用 TaggedPointer蝎困,蘋果會直接將其指針值作為引用計數(shù)返回;如果當(dāng)前設(shè)備是 64 位環(huán)境并且使用 Objective-C 2.0倍啥,那么“一些”對象會使用其 isa 指針的一部分空間來存儲它的引用計數(shù)禾乘;否則 Runtime 會使用一張散列表來管理引用計數(shù)。

散列表來存儲引用計數(shù)具體是用 DenseMap 類來實現(xiàn)逗栽,這個類中包含好多映射實例到其引用計數(shù)的鍵值對盖袭,并支持用 DenseMapIterator 迭代器快速查找遍歷這些鍵值對。接著說鍵值對的格式:鍵的類型為 DisguisedPtr<objc_object>彼宠,DisguisedPtr 類是對 objc_object * 指針及其一些操作進行的封裝鳄虱,目的就是為了讓它給人看起來不會有內(nèi)存泄露的樣子(真是心機裱),其內(nèi)容可以理解為對象的內(nèi)存地址凭峡;值的類型為 __darwin_size_t拙已,在 darwin 內(nèi)核一般等同于 unsigned long。其實這里保存的值也是等于引用計數(shù)減一摧冀。使用散列表保存引用計數(shù)的設(shè)計很好倍踪,即使出現(xiàn)故障導(dǎo)致對象的內(nèi)存塊損壞,只要引用計數(shù)表沒有被破壞索昂,依然可以順藤摸瓜找到內(nèi)存塊的位置建车。

之前說引用計數(shù)表是個散列表,這里簡要說下散列的方法椒惨。有個專門處理鍵的 DenseMapInfo 結(jié)構(gòu)體缤至,它針對 DisguisedPtr 做了些優(yōu)化匹配鍵值速度的方法:

struct DenseMapInfo<DisguisedPtr<T>> {
  static inline DisguisedPtr<T> getEmptyKey() {
    return DisguisedPtr<T>((T*)(uintptr_t)-1);
  }
  static inline DisguisedPtr<T> getTombstoneKey() {
    return DisguisedPtr<T>((T*)(uintptr_t)-2);
  }
  static unsigned getHashValue(const T *PtrVal) {
      return ptr_hash((uintptr_t)PtrVal);
  }
  static bool isEqual(const DisguisedPtr<T> &LHS, const DisguisedPtr<T> &RHS) {
      return LHS == RHS; 
  }
};

當(dāng)然這里的哈希算法會根據(jù)是否為 64 位平臺來進行優(yōu)化,算法具體細節(jié)就不深究了康谆,我總覺得蘋果在這里的 hardcode 是隨便寫的:

#if __LP64__
static inline uint32_t ptr_hash(uint64_t key)
{
    key ^= key >> 4;
    key *= 0x8a970be7488fda55;
    key ^= __builtin_bswap64(key);
    return (uint32_t)key;
}
#else
static inline uint32_t ptr_hash(uint32_t key)
{
    key ^= key >> 4;
    key *= 0x5052acdb;
    key ^= __builtin_bswap32(key);
    return key;
}
#endif

再介紹下 SideTable 這個類领斥,它用于管理引用計數(shù)表和 weak 表,并使用 spinlock_lock 自旋鎖來防止操作表結(jié)構(gòu)時可能的競態(tài)條件沃暗。它用一個 64*128 大小的 uint8_t 靜態(tài)數(shù)組作為 buffer 來保存所有的 SideTable 實例月洛。并提供三個公有屬性:

spinlock_t slock;//保證原子操作的自選鎖
RefcountMap refcnts;//保存引用計數(shù)的散列表
weak_table_t weak_table;//保存 weak 引用的全局散列表

還提供了一個工廠方法,用于根據(jù)對象的地址在 buffer 中尋找對應(yīng)的 SideTable 實例:

static SideTable *tableForPointer(const void *p)

weak 表的作用是在對象執(zhí)行 dealloc 的時候?qū)⑺兄赶蛟搶ο蟮?weak 指針的值設(shè)為 nil孽锥,避免懸空指針嚼黔。這是 weak 表的結(jié)構(gòu):

struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};

蘋果使用一個全局的 weak 表來保存所有的 weak 引用。并將對象作為鍵惜辑,weak_entry_t 作為值隔崎。weak_entry_t 中保存了所有指向該對象的 weak 指針。

4.2 ARC

是由clang(LLVM編譯器)+運行時庫共同實現(xiàn)的韵丑。

4.3 ARC的所有權(quán)修飾符號

4.3.1
  • __strong id類型和對象類型的默認修飾符(copy爵卒、strong、retain)
  • __weak 弱引用(weak)
  • __unsafe_unretained 既不是強引用也不是弱引用(assign撵彻、unsafe_unretained)
  • __autoreleasing ARC中@autoreleasepool中使用 類似于之前的release

Tips:
1:實際上在使用__weak修飾的對象钓株,由于無法取得相應(yīng)的對象的所有權(quán)实牡,必定會注冊到對應(yīng)的autoreleasepool中,防止在作用域內(nèi)被釋放轴合,導(dǎo)致crash创坞。非顯式使用了__autoreleasing。
2:id的指針或?qū)ο蟮闹羔?也會隱式的使用了__autoreleasing

4.3.2
  • __bridge,
  • __bridge_retained
  • __bridge_transfer

__bridge可以用于OC對象和CF對象互轉(zhuǎn)受葛,例如

NSObject *obj = [[NSObject alloc] init];    //retain count 1
CFTypeRef cfObj1 = (__bridge CFTypeRef)obj; //retain count 1
NSObject *obj1 = (__bridge id)cfObj1;       //retain count 2

在這種轉(zhuǎn)換方式下题涨,如果是OC對象轉(zhuǎn)換成CF對象,引用計數(shù)不變总滩。如果是CF對象轉(zhuǎn)換成OC對象纲堵,因為OC對象的默認修飾符是__strong,引用計數(shù)會+1闰渔,即以下兩種寫法是一樣的席函。

NSObject *obj1 = (__bridge id)cfObj1;
NSObject __strong *obj1 = (__bridge id)cfObj1;

__bridge_retained用于OC對象轉(zhuǎn)換為CF對象,例如

NSObject *obj = [[NSObject alloc] init];                //retain count 1
CFTypeRef cfObj1 = (__bridge_retained CFTypeRef)obj;    //retain count 2

//等價寫法
NSObject *obj = [[NSObject alloc] init];                //retain count 1
CFTypeRef cfObj1 = (CFTypeRef)CFBridgingRetain(obj);    //retain count 2

這種情況下冈涧,obj的引用計數(shù)會+1茂附,obj的釋放不會影響到cfObj1的使用

__bridge_transfer用于CF對象轉(zhuǎn)換為OC對象,例如

NSObject *obj = [[NSObject alloc] init];                //retain count 1
CFTypeRef cfObj1 = (__bridge_retained CFTypeRef)obj;    //retain count 2
NSObject *obj1 = (__bridge_transfer id)cfObj1;          //retain count 2

//等價寫法
NSObject *obj = [[NSObject alloc] init];                  //retain count 1
CFTypeRef cfObj1 = (__bridge_retained CFTypeRef)obj;      //retain count 2
NSObject *obj1 = (NSObject *)CFBridgingRelease(cfObj1);   //retain count 2

五 weak實現(xiàn)

在iOS開發(fā)過程中督弓,會經(jīng)常使用到一個修飾詞weak营曼,使用場景大家都比較清晰,避免出現(xiàn)對象之間的強強引用而造成對象不能被正常釋放最終導(dǎo)致內(nèi)存泄露的問題愚隧。weak 關(guān)鍵字的作用是弱引用溶推,所引用對象的計數(shù)器不會加1,并在引用對象被釋放的時候自動被設(shè)置為 nil奸攻。

1、weak 初探

下面的一段代碼是我們在開發(fā)中常見的weak的使用

Person *object = [Person alloc];
id __weak objc = object;
復(fù)制代碼

如果在此打斷點跟蹤匯編信息虱痕,可以發(fā)現(xiàn)底層庫調(diào)了objc_initWeak函數(shù) [圖片上傳失敗...(image-99a1be-1615882971235)]

那么我們來看一下objc_initWeak 方法的實現(xiàn)代碼是怎么樣的呢睹耐?

1、objc_initWeak方法

如下是objc_initWeak 方法的底層源碼

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

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}
復(fù)制代碼

該方法的兩個參數(shù)locationnewObj部翘。

  • location__weak指針的地址硝训,存儲指針的地址,這樣便可以在最后將其指向的對象置為nil新思。
  • newObj :所引用的對象窖梁。即例子中的obj 。

從上面的代碼可以看出objc_initWeak方法只是一個深層次函數(shù)調(diào)用的入口夹囚,在該方法內(nèi)部調(diào)用了storeWeak 方法纵刘。下面我們來看下storeWeak 方法的實現(xiàn)代碼。

2荸哟、storeWeak方法

如下是storeWeak方法的實現(xiàn)代碼假哎。

// Template parameters.
enum HaveOld { DontHaveOld = false, DoHaveOld = true };
enum HaveNew { DontHaveNew = false, DoHaveNew = true };
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) { // 如果weak ptr之前弱引用過一個obj瞬捕,則將這個obj所對應(yīng)的SideTable取出,賦值給oldTable
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil; // 如果weak ptr之前沒有弱引用過一個obj舵抹,則oldTable = nil
    }
    if (haveNew) { // 如果weak ptr要weak引用一個新的obj肪虎,則將該obj對應(yīng)的SideTable取出,賦值給newTable
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil; // 如果weak ptr不需要引用一個新obj惧蛹,則newTable = nil
    }

    // 加鎖操作扇救,防止多線程中競爭沖突
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    // location 應(yīng)該與 oldObj 保持一致,如果不同香嗓,說明當(dāng)前的 location 已經(jīng)處理過 oldObj 可是又被其他線程所修改
    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }

    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no
    // weakly-referenced object has an un-+initialized isa.
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&
            !((objc_class *)cls)->isInitialized())  // 如果cls還沒有初始化迅腔,先初始化,再嘗試設(shè)置weak
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));

            // If this class is finished with +initialize then we're good.
            // If this class is still running +initialize on this thread
            // (i.e. +initialize called storeWeak on an instance of itself)
            // then we may proceed but it will appear initializing and
            // not yet initialized to the check above.
            // Instead set previouslyInitializedClass to recognize it on retry.
            previouslyInitializedClass = cls; // 這里記錄一下previouslyInitializedClass陶缺, 防止改if分支再次進入

            goto retry; // 重新獲取一遍newObj钾挟,這時的newObj應(yīng)該已經(jīng)初始化過了
        }
    }

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

    // Assign new value, if any.
    if (haveNew) { // 如果weak_ptr需要弱引用新的對象newObj
        // (1) 調(diào)用weak_register_no_lock方法掺出,將weak ptr的地址記錄到newObj對應(yīng)的weak_entry_t中
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
                                  crashIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // (2) 更新newObj的isa的weakly_referenced bit標志位
        // Set is-weakly-referenced bit in refcount table.
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        // (3)*location 賦值,也就是將weak ptr直接指向了newObj苫费√老牵可以看到,這里并沒有將newObj的引用計數(shù)+1
        *location = (id)newObj; // 將weak ptr指向object
    }
    else {
        // No new value. The storage is not changed.
    }

    // 解鎖百框,其他線程可以訪問oldTable, newTable了
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj; // 返回newObj闲礼,此時的newObj與剛傳入時相比,weakly-referenced bit位置1
}
復(fù)制代碼

storeWeak 方法的實現(xiàn)代碼雖然有些長铐维,但是并不難以理解柬泽。下面我們來分析下該方法的實現(xiàn)。

  1. storeWeak方法實際上是接收了5個參數(shù)嫁蛇,分別是haveOld锨并、haveNew和crashIfDeallocating ,這三個參數(shù)都是以模板的方式傳入的睬棚,是三個bool類型的參數(shù)第煮。 分別表示weak指針之前是否指向了一個弱引用,weak指針是否需要指向一個新的引用抑党,若果被弱引用的對象正在析構(gòu)包警,此時再弱引用該對象是否應(yīng)該crash。
  2. 該方法維護了oldTablenewTable分別表示舊的引用弱表和新的弱引用表底靠,它們都是SideTable的hash表害晦。
  3. 如果weak指針之前指向了一個弱引用,則會調(diào)用weak_unregister_no_lock 方法將舊的weak指針地址移除暑中。
  4. 如果weak指針需要指向一個新的引用篱瞎,則會調(diào)用weak_register_no_lock 方法將新的weak指針地址添加到弱引用表中苟呐。
  5. 調(diào)用setWeaklyReferenced_nolock 方法修改weak新引用的對象的bit標志位

那么這個方法中的重點也就是weak_unregister_no_lockweak_register_no_lock 這兩個方法。而這兩個方法都是操作的SideTable 這樣一個結(jié)構(gòu)的變量俐筋,那么我們需要先來了解下SideTable 牵素。

3、SideTable

先來看下SideTable的定義澄者。

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;
}
復(fù)制代碼

SideTable的定義很清晰笆呆,有三個成員:

  • spinlock_t slock : 自旋鎖,用于上鎖/解鎖 SideTable粱挡。
  • RefcountMap refcnts :用來存儲OC對象的引用計數(shù)的 hash表(僅在未開啟isa優(yōu)化或在isa優(yōu)化情況下isa_t的引用計數(shù)溢出時才會用到)赠幕。
  • weak_table_t weak_table : 存儲對象弱引用指針的hash表。是OC中weak功能實現(xiàn)的核心數(shù)據(jù)結(jié)構(gòu)询筏。

3.1榕堰、weak_table_t

先來看下weak_table_t 的底層代碼。

struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};
復(fù)制代碼
  • weak_entries: hash數(shù)組嫌套,用來存儲弱引用對象的相關(guān)信息weak_entry_t
  • num_entries: hash數(shù)組中的元素個數(shù)
  • mask:hash數(shù)組長度-1逆屡,會參與hash計算。(注意踱讨,這里是hash數(shù)組的長度魏蔗,而不是元素個數(shù)。比如痹筛,數(shù)組長度可能是64莺治,而元素個數(shù)僅存了2個)
  • max_hash_displacement:可能會發(fā)生的hash沖突的最大次數(shù),用于判斷是否出現(xiàn)了邏輯錯誤(hash表中的沖突次數(shù)絕不會超過改值)

weak_table_t是一個典型的hash結(jié)構(gòu)帚稠。weak_entries是一個動態(tài)數(shù)組谣旁,用來存儲weak_entry_t類型的元素,這些元素實際上就是OC對象的弱引用信息滋早。

3.2榄审、weak_entry_t

weak_entry_t的結(jié)構(gòu)也是一個hash結(jié)構(gòu),其存儲的元素是弱引用對象指針的指針馆衔, 通過操作指針的指針,就可以使得weak 引用的指針在對象析構(gòu)后怨绣,指向nil。

#define WEAK_INLINE_COUNT 4
#define REFERRERS_OUT_OF_LINE 2

struct weak_entry_t {
    DisguisedPtr<objc_object> referent; // 被弱引用的對象

    // 引用該對象的對象列表,聯(lián)合律想。 引用個數(shù)小于4在岂,用inline_referrers數(shù)組。 用個數(shù)大于4赢笨,用動態(tài)數(shù)組weak_referrer_t *referrers
    union {
        struct {
            weak_referrer_t *referrers;                      // 弱引用該對象的對象指針地址的hash數(shù)組
            uintptr_t        out_of_line_ness : 2;           // 是否使用動態(tài)hash數(shù)組標記位
            uintptr_t        num_refs : PTR_MINUS_2;         // hash數(shù)組中的元素個數(shù)
            uintptr_t        mask;                           // hash數(shù)組長度-1未蝌,會參與hash計算驮吱。(注意,這里是hash數(shù)組的長度萧吠,而不是元素個數(shù)左冬。比如,數(shù)組長度可能是64纸型,而元素個數(shù)僅存了2個)素個數(shù))拇砰。
            uintptr_t        max_hash_displacement;          // 可能會發(fā)生的hash沖突的最大次數(shù),用于判斷是否出現(xiàn)了邏輯錯誤(hash表中的沖突次數(shù)絕不會超過改值)
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };

    bool out_of_line() {
        return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
    }

    weak_entry_t& operator=(const weak_entry_t& other) {
        memcpy(this, &other, sizeof(other));
        return *this;
    }

    weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
        : referent(newReferent) // 構(gòu)造方法狰腌,里面初始化了靜態(tài)數(shù)組
    {
        inline_referrers[0] = newReferrer;
        for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
            inline_referrers[i] = nil;
        }
    }
};

可以看到在weak_entry_t 的結(jié)構(gòu)定義中有聯(lián)合體除破,在聯(lián)合體的內(nèi)部有定長數(shù)組inline_referrers[WEAK_INLINE_COUNT]和動態(tài)數(shù)組weak_referrer_t *referrers兩種方式來存儲弱引用對象的指針地址。通過out_of_line()這樣一個函數(shù)方法來判斷采用哪種存儲方式琼腔。當(dāng)弱引用該對象的指針數(shù)目小于等于WEAK_INLINE_COUNT時瑰枫,使用定長數(shù)組。當(dāng)超過WEAK_INLINE_COUNT時丹莲,會將定長數(shù)組中的元素轉(zhuǎn)移到動態(tài)數(shù)組中光坝,并之后都是用動態(tài)數(shù)組存儲。

到這里我們已經(jīng)清楚了弱引用表的結(jié)構(gòu)是一個hash結(jié)構(gòu)的表圾笨,Key是所指對象的地址教馆,Value是weak指針的地址(這個地址的值是所指對象的地址)數(shù)組。那么接下來看看這個弱引用表是怎么維護這些數(shù)據(jù)的擂达。

4土铺、weak_register_no_lock方法添加弱引用

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;

    // 如果referent為nil 或 referent 采用了TaggedPointer計數(shù)方式,直接返回板鬓,不做任何操作
    if (!referent  ||  referent->isTaggedPointer()) return referent_id;

    // 確保被引用的對象可用(沒有在析構(gòu)悲敷,同時應(yīng)該支持weak引用)
    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);
    }
    // 正在析構(gòu)的對象,不能夠被弱引用
    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_table中找到referent對應(yīng)的weak_entry,并將referrer加入到weak_entry中
    weak_entry_t *entry;
    if ((entry = weak_entry_for_referent(weak_table, referent))) { // 如果能找到weak_entry,則講referrer插入到weak_entry中
        append_referrer(entry, referrer);   // 將referrer插入到weak_entry_t的引用數(shù)組中
    } 
    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;
}

這個方法需要傳入四個參數(shù)后德,它們代表的意義如下:

  • weak_tableweak_table_t 結(jié)構(gòu)類型的全局的弱引用表。
  • referent_id:weak指針抄腔。
  • *referrer_id:weak指針地址瓢湃。
  • crashIfDeallocating :若果被弱引用的對象正在析構(gòu),此時再弱引用該對象是否應(yīng)該crash赫蛇。

從上面的代碼我么可以知道該方法主要的做了如下幾個方便的工作绵患。

  1. 如果referent為nil 或 referent 采用了TaggedPointer計數(shù)方式,直接返回悟耘,不做任何操作落蝙。
  2. 如果對象正在析構(gòu),則拋出異常。
  3. 如果對象不能被weak引用筏勒,直接返回nil移迫。
  4. 如果對象沒有再析構(gòu)且可以被weak引用,則調(diào)用weak_entry_for_referent 方法根據(jù)弱引用對象的地址從弱引用表中找到對應(yīng)的weak_entry管行,如果能夠找到則調(diào)用append_referrer 方法向其中插入weak指針地址厨埋。否則新建一個weak_entry。

4.1病瞳、weak_entry_for_referent取元素

static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
    assert(referent);

    weak_entry_t *weak_entries = weak_table->weak_entries;

    if (!weak_entries) return nil;

    size_t begin = hash_pointer(referent) & weak_table->mask;  // 這里通過 & weak_table->mask的位操作揽咕,來確保index不會越界
    size_t index = begin;
    size_t hash_displacement = 0;
    while (weak_table->weak_entries[index].referent != referent) {
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_table->weak_entries); // 觸發(fā)bad weak table crash
        hash_displacement++;
        if (hash_displacement > weak_table->max_hash_displacement) { // 當(dāng)hash沖突超過了可能的max hash 沖突時,說明元素沒有在hash表中套菜,返回nil 
            return nil;
        }
    }

    return &weak_table->weak_entries[index];
}

4.2亲善、append_referrer添加元素

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    if (! entry->out_of_line()) { // 如果weak_entry 尚未使用動態(tài)數(shù)組,走這里
        // Try to insert inline.
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == nil) {
                entry->inline_referrers[i] = new_referrer;
                return;
            }
        }

        // 如果inline_referrers的位置已經(jīng)存滿了逗柴,則要轉(zhuǎn)型為referrers蛹头,做動態(tài)數(shù)組。
        // Couldn't insert inline. Allocate out of line.
        weak_referrer_t *new_referrers = (weak_referrer_t *)
            calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
        // This constructed table is invalid, but grow_refs_and_insert
        // will fix it and rehash it.
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            new_referrers[i] = entry->inline_referrers[I];
        }
        entry->referrers = new_referrers;
        entry->num_refs = WEAK_INLINE_COUNT;
        entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
        entry->mask = WEAK_INLINE_COUNT-1;
        entry->max_hash_displacement = 0;
    }

    // 對于動態(tài)數(shù)組的附加處理:
    assert(entry->out_of_line()); // 斷言: 此時一定使用的動態(tài)數(shù)組

    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) { // 如果動態(tài)數(shù)組中元素個數(shù)大于或等于數(shù)組位置總空間的3/4戏溺,則擴展數(shù)組空間為當(dāng)前長度的一倍
        return grow_refs_and_insert(entry, new_referrer); // 擴容渣蜗,并插入
    }

    // 如果不需要擴容,直接插入到weak_entry中
    // 注意旷祸,weak_entry是一個哈希表耕拷,key:w_hash_pointer(new_referrer) value: new_referrer

    // 細心的人可能注意到了,這里weak_entry_t 的hash算法和 weak_table_t的hash算法是一樣的托享,同時擴容/減容的算法也是一樣的
    size_t begin = w_hash_pointer(new_referrer) & (entry->mask); // '& (entry->mask)' 確保了 begin的位置只能大于或等于 數(shù)組的長度
    size_t index = begin;  // 初始的hash index
    size_t hash_displacement = 0;  // 用于記錄hash沖突的次數(shù)骚烧,也就是hash再位移的次數(shù)
    while (entry->referrers[index] != nil) {
        hash_displacement++;
        index = (index+1) & entry->mask;  // index + 1, 移到下一個位置,再試一次能否插入闰围。(這里要考慮到entry->mask取值赃绊,一定是:0x111, 0x1111, 0x11111, ... ,因為數(shù)組每次都是*2增長羡榴,即8碧查, 16, 32校仑,對應(yīng)動態(tài)數(shù)組空間長度-1的mask忠售,也就是前面的取值。)
        if (index == begin) bad_weak_table(entry); // index == begin 意味著數(shù)組繞了一圈都沒有找到合適位置迄沫,這時候一定是出了什么問題稻扬。
    }
    if (hash_displacement > entry->max_hash_displacement) { // 記錄最大的hash沖突次數(shù), max_hash_displacement意味著: 我們嘗試至多max_hash_displacement次,肯定能夠找到object對應(yīng)的hash位置
        entry->max_hash_displacement = hash_displacement;
    }
    // 將ref存入hash數(shù)組邢滑,同時腐螟,更新元素個數(shù)num_refs
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    entry->num_refs++;
}

這段代碼首先確定是使用定長數(shù)組還是動態(tài)數(shù)組,如果是使用定長數(shù)組困后,則直接將weak指針地址添加到數(shù)組即可乐纸,如果定長數(shù)組已經(jīng)用盡,則需要將定長數(shù)組中的元素轉(zhuǎn)存到動態(tài)數(shù)組中摇予。

5汽绢、weak_unregister_no_lock移除引用

如果weak指針之前指向了一個弱引用,則會調(diào)用weak_unregister_no_lock方法將舊的weak指針地址移除侧戴。

void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                        id *referrer_id)
{
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;

    weak_entry_t *entry;

    if (!referent) return;

    if ((entry = weak_entry_for_referent(weak_table, referent))) { // 查找到referent所對應(yīng)的weak_entry_t
        remove_referrer(entry, referrer);  // 在referent所對應(yīng)的weak_entry_t的hash數(shù)組中宁昭,移除referrer

        // 移除元素之后, 要檢查一下weak_entry_t的hash數(shù)組是否已經(jīng)空了
        bool empty = true;
        if (entry->out_of_line()  &&  entry->num_refs != 0) {
            empty = false;
        }
        else {
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i]) {
                    empty = false; 
                    break;
                }
            }
        }

        if (empty) { // 如果weak_entry_t的hash數(shù)組已經(jīng)空了酗宋,則需要將weak_entry_t從weak_table中移除
            weak_entry_remove(weak_table, entry);
        }
    }
  1. 首先积仗,它會在weak_table中找出referent對應(yīng)的weak_entry_t
  2. 在weak_entry_t中移除referrer
  3. 移除元素后,判斷此時weak_entry_t中是否還有元素 (empty==true蜕猫?)
  4. 如果此時weak_entry_t已經(jīng)沒有元素了寂曹,則需要將weak_entry_t從weak_table中移除

到這里為止就是對于一個對象做weak引用時底層做的事情,用weak引用對象后引用計數(shù)并不會加1回右,當(dāng)對象釋放時隆圆,所有weak引用它的指針又是如何自動設(shè)置為nil的呢?

6翔烁、dealloc

當(dāng)對象的引用計數(shù)為0時渺氧,底層會調(diào)用_objc_rootDealloc方法對對象進行釋放,而在_objc_rootDealloc方法里面會調(diào)用rootDealloc方法蹬屹。如下是rootDealloc方法的代碼實現(xiàn)侣背。

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

    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}
  1. 首先判斷對象是否是Tagged Pointer,如果是則直接返回哩治。
  2. 如果對象是采用了優(yōu)化的isa計數(shù)方式秃踩,且同時滿足對象沒有被weak引用!isa.weakly_referenced、沒有關(guān)聯(lián)對象!isa.has_assoc 业筏、沒有自定義的C++析構(gòu)方法!isa.has_cxx_dtor憔杨、沒有用到SideTable來引用計數(shù)!isa.has_sidetable_rc則直接快速釋放。
  3. 如果不能滿足2中的條件蒜胖,則會調(diào)用object_dispose 方法消别。

6.1、object_dispose

object_dispose 方法很簡單台谢,主要是內(nèi)部調(diào)用了objc_destructInstance方法寻狂。

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

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

    return obj;
}
復(fù)制代碼

上面這一段代碼很清晰,如果有自定義的C++析構(gòu)方法朋沮,則調(diào)用C++析構(gòu)函數(shù)蛇券。如果有關(guān)聯(lián)對象,則移除關(guān)聯(lián)對象并將其自身從Association Manager的map中移除。調(diào)用clearDeallocating 方法清除對象的相關(guān)引用纠亚。

6.2塘慕、clearDeallocating

inline void 
objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // Slow path for non-pointer isa with weak refs and/or side table data.
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}

clearDeallocating中有兩個分支,先判斷對象是否采用了優(yōu)化isa引用計數(shù)蒂胞,如果沒有的話則需要清理對象存儲在SideTable中的引用計數(shù)數(shù)據(jù)图呢。如果對象采用了優(yōu)化isa引用計數(shù),則判斷是否有使用SideTable的輔助引用計數(shù)(isa.has_sidetable_rc)或者有weak引用(isa.weakly_referenced)骗随,符合這兩種情況中一種的蛤织,調(diào)用clearDeallocating_slow 方法。

6.3鸿染、clearDeallocating_slow

NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
    assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    SideTable& table = SideTables()[this]; // 在全局的SideTables中指蚜,以this指針為key,找到對應(yīng)的SideTable
    table.lock();
    if (isa.weakly_referenced) { // 如果obj被弱引用
        weak_clear_no_lock(&table.weak_table, (id)this); // 在SideTable的weak_table中對this進行清理工作
    }
    if (isa.has_sidetable_rc) { // 如果采用了SideTable做引用計數(shù)
        table.refcnts.erase(this); // 在SideTable的引用計數(shù)中移除this
    }
    table.unlock();
}

在這里我們關(guān)心的是weak_clear_no_lock 方法涨椒。這里調(diào)用了weak_clear_no_lock來做weak_table的清理工作姚炕。

6.4、weak_clear_no_lock

void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;

    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent); // 找到referent在weak_table中對應(yīng)的weak_entry_t
    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;

    // 找出weak引用referent的weak 指針地址數(shù)組以及數(shù)組長度
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }

    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i]; // 取出每個weak ptr的地址
        if (referrer) {
            if (*referrer == referent) { // 如果weak ptr確實weak引用了referent丢烘,則將weak ptr設(shè)置為nil柱宦,這也就是為什么weak 指針會自動設(shè)置為nil的原因
                *referrer = nil;
            }
            else if (*referrer) { // 如果所存儲的weak ptr沒有weak 引用referent,這可能是由于runtime代碼的邏輯錯誤引起的播瞳,報錯
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }

    weak_entry_remove(weak_table, entry); // 由于referent要被釋放了掸刊,因此referent的weak_entry_t也要移除出weak_table
}

7、總結(jié)

  • 1赢乓、weak的原理在于底層維護了一張weak_table_t結(jié)構(gòu)的hash表忧侧,key是所指對象的地址,value是weak指針的地址數(shù)組牌芋。
  • 2蚓炬、weak 關(guān)鍵字的作用是弱引用,所引用對象的計數(shù)器不會加1躺屁,并在引用對象被釋放的時候自動被設(shè)置為 nil肯夏。
  • 3、對象釋放時犀暑,調(diào)用clearDeallocating函數(shù)根據(jù)對象地址獲取所有weak指針地址的數(shù)組驯击,然后遍歷這個數(shù)組把其中的數(shù)據(jù)設(shè)為nil,最后把這個entry從weak表中刪除耐亏,最后清理對象的記錄徊都。
  • 4、文章中介紹了SideTable广辰、weak_table_t暇矫、weak_entry_t這樣三個結(jié)構(gòu)主之,它們之間的關(guān)系如下圖所示。
    image.png

六 tagged pointer

1李根、Tagged Pointer 介紹

1.1杀餐、什么是 Tagged Pointer

Tagged Pointer是一個特別的指針朱巨,它分為兩部分:

  • 一部分直接保存數(shù)據(jù) ;
  • 另一部分作為特殊標記枉长,表示這是一個特別的指針冀续,不指向任何一個地址;

因此必峰,我們說Tagged Pointer是一個偽指針洪唐!

1.2、 Tagged Pointer出現(xiàn)的背景

對于一個NSNumber對象吼蚁,如果存儲NSInteger的普通變量凭需,那么它所占用的內(nèi)存是與 CPU 的位數(shù)有關(guān),在 32 位CPU下占4個字節(jié)肝匆。而指針類型的大小通常也是與 CPU 位數(shù)相關(guān)粒蜈,一個指針所占用的內(nèi)存在32位CPU下為4個字節(jié)。

在2013年9月旗国,蘋果推出了iPhone 5s枯怖,該款機型首次采用64位架構(gòu)的A7雙核處理器。此時能曾,對于一個NSNumber對象度硝,如果存儲NSInteger的普通變量,那么它將占 8 個字節(jié)寿冕;一個指針也將占用 8 個字節(jié)蕊程。

對于一個普通的程序而言,從 32 位機器遷移到 64 位機器中后驼唱,雖然邏輯沒有任何變化藻茂,但對于 NSNumberNSDate玫恳、NSString等類型的實例所占用的內(nèi)存會翻倍捌治、浪費了稀有的內(nèi)存資源!纽窟!同時維護程序中的對象需要也分配內(nèi)存肖油,維護引用計數(shù),管理生命周期臂港,使用對象給程序的運行增加了負擔(dān)I埂视搏!

為了節(jié)省內(nèi)存和提高執(zhí)行效率,蘋果提出了 Tagged Pointer的概念县袱。蘋果將一個對象的指針拆成兩部分浑娜,一部分直接保存數(shù)據(jù),另一部分作為特殊標記式散,表示這是一個特別的指針筋遭,不指向任何一個地址。

1.3暴拄、蘋果對 Tagged Pointer的介紹

蘋果對于 Tagged Pointer特點的做出了介紹:

  • Tagged Pointer被設(shè)計的目的是用來存儲較小的對象漓滔,例如NSNumberNSDate乖篷、NSString 等响驴;
  • Tagged Pointer的值不再表示地址,而是真正的值撕蔼;
  • 在內(nèi)存讀取上有著3倍的效率豁鲤,創(chuàng)建時比以前快106倍 ;
1.4鲸沮、Tagged Pointer的使用

在一個程序中運行下述代碼琳骡,獲取輸出日志:

NSNumber *number =  @(0);
NSNumber *number1 = @(1);
NSNumber *number2 = @(2);
NSNumber *number3 = @(9999999999999999999);
NSString *string = [[@"a" mutableCopy] copy];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];

NSLog(@"number ---- %@, %p", [number class], number);
NSLog(@"number1 --- %@, %p", [number1 class], number1);
NSLog(@"number2 --- %@, %p", [number2 class], number2);
NSLog(@"number3 --- %@, %p", [number3 class], number3);
NSLog(@"NSString -- %@, %p", [string class], string);
NSLog(@"indexPath - %@, %p", indexPath.class,indexPath);

/********************* 輸出日志 *********************
number ---- __NSCFNumber, 0xb000000000000002
number1 --- __NSCFNumber, 0xb000000000000012
number2 --- __NSCFNumber, 0xb000000000000022
number3 --- __NSCFNumber, 0x600003b791c0
NSString -- NSTaggedPointerString, 0xa000000000000611
indexPath - NSIndexPath, 0xc000000000000016
 */

分析日志:

  • 1、NSNumber存儲的數(shù)據(jù)不大時讼溺,NSNumber *指針是偽指針Tagged Pointer日熬;
  • 2、NSNumber存儲的數(shù)據(jù)很大時肾胯,NSNumber *指針一般指針竖席,指向NSNumber實例的地址,如 number3敬肚;
  • 3毕荐、NSTaggedPointerString 經(jīng)常遇見,它就是Tagged Pointer對象艳馒;

對于Tagged Pointer憎亚,是系統(tǒng)實現(xiàn)的,無需開發(fā)者操心弄慰!但是作為開發(fā)者第美,也要知道NSTaggedPointerString等是什么東西!

1.5陆爽、Tagged Pointer的思考

通過上文的概念 + 應(yīng)用什往,相信大家對 Tagged Pointer 有了一定的了解;那么大家有沒有一些疑問呢慌闭?

  • 系統(tǒng)是如何將 NSNumber别威、NSDate躯舔、NSString等類型的數(shù)據(jù)存儲在 Tagged Pointer 上?
  • 既然Tagged Pointer是一個偽指針省古,不再指向?qū)嵗龑ο笾嘧敲?code>isa指針就不能再調(diào)用!不通過isa指針獲取它所屬的類豺妓,系統(tǒng)是如何知道它存儲的數(shù)據(jù)結(jié)構(gòu)的惜互?
  • 數(shù)據(jù)展示在Tagged Pointer,可以被人通過Tagged Pointer獲取琳拭,明顯不再安全训堆,那么蘋果是如何加密Tagged Pointer上的數(shù)據(jù)的?

2臀栈、Tagged Pointer的底層探究

蘋果設(shè)計的Tagged Pointer技術(shù),是在 Runtime 庫 中實現(xiàn)的挠乳。

2.1权薯、Tagged Pointer技術(shù)的啟用與禁用

蘋果預(yù)留了環(huán)境變量 OBJC_DISABLE_TAGGED_POINTERS,通過設(shè)置該變量的布爾值睡扬,可以將Tagged Pointer技術(shù)的啟用與關(guān)閉的決定權(quán)交給開發(fā)者盟蚣!
如果禁用Tagged Pointer,只需設(shè)置環(huán)境變量 OBJC_DISABLE_TAGGED_POINTERSYES 即可卖怜!

2.1.1屎开、Tagged Pointer的禁用函數(shù)

Runtime 庫objc-runtime-new.mm文件中有一個禁用Tagged Pointer的函數(shù):

static void disableTaggedPointers(){
    objc_debug_taggedpointer_mask = 0;
    objc_debug_taggedpointer_slot_shift = 0;
    objc_debug_taggedpointer_slot_mask = 0;
    objc_debug_taggedpointer_payload_lshift = 0;
    objc_debug_taggedpointer_payload_rshift = 0;

    objc_debug_taggedpointer_ext_mask = 0;
    objc_debug_taggedpointer_ext_slot_shift = 0;
    objc_debug_taggedpointer_ext_slot_mask = 0;
    objc_debug_taggedpointer_ext_payload_lshift = 0;
    objc_debug_taggedpointer_ext_payload_rshift = 0;
}

在該函數(shù)內(nèi)部,將一些列變量全部設(shè)置為 0 马靠!至于這些變量都有什么用處奄抽,我們后文用到了再解釋!

2.1.2甩鳄、何時調(diào)用disableTaggedPointers()函數(shù)?

何時調(diào)用disableTaggedPointers()函數(shù)禁用Tagged Pointer呢逞度?

還是在 objc-runtime-new.mm文件中,在大名鼎鼎的_read_images() 函數(shù)中有一處關(guān)鍵代碼:

//注意:由于_read_images() 函數(shù)中完成了大量的初始化操作妙啃,我們在此處省略大量無關(guān)代碼档泽,
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses){
        //禁用NSNumber等的 Tagged Pointer 指針優(yōu)化
        if (DisableTaggedPointers) {
            disableTaggedPointers();
        }

        //初始化 TaggedPointer 混淆器:用于保護 Tagged Pointer 上的數(shù)據(jù)
        initializeTaggedPointerObfuscator();
}

可以看到,首先判斷Tagged Pointer是否被禁用揖赴,如果被禁用馆匿,才調(diào)用disableTaggedPointers()函數(shù)將Tagged Pointer相關(guān)變量全部設(shè)置為 0。

OPTION( DisableTaggedPointers,  OBJC_DISABLE_TAGGED_POINTERS,  "disable tagged pointer optimization of NSNumber et al.") 

判斷Tagged Pointer時燥滑,使用的是預(yù)定義的宏 OPTION渐北!本質(zhì)是獲取環(huán)境變量 OBJC_DISABLE_TAGGED_POINTERS的值,設(shè)置該變量為 YES铭拧,則Tagged Pointer被禁用腔稀。

2.1.3盆昙、判斷是否禁用的函數(shù)
static inline bool  _objc_taggedPointersEnabled(void){
    extern uintptr_t objc_debug_taggedpointer_mask;
    return (objc_debug_taggedpointer_mask != 0);
}

  • DisableTaggedPointers 通過環(huán)境變量 OBJC_DISABLE_TAGGED_POINTERS來判斷是否使用Tagged Pointer
  • 函數(shù) _objc_taggedPointersEnabled() 通過全局變量objc_debug_taggedpointer_mask判斷是否使用Tagged Pointer焊虏。
2.1.4淡喜、能否禁用?

可能有的讀者去測試OBJC_DISABLE_TAGGED_POINTERSYES時诵闭,發(fā)現(xiàn)程序報錯:

objc[3658]: tagged pointers are disabled
Message from debugger: Terminated due to signal 9

既然蘋果給了OBJC_DISABLE_TAGGED_POINTERS這個環(huán)境變量讓我們設(shè)置炼团,為何程序還無法啟動呢?分析下圖錯誤堆棧:

image

在調(diào)用棧中發(fā)現(xiàn)了Runtime 庫 殺手 _objc_fatal()函數(shù),該函數(shù)一言不合就殺死進程!可以看到巍杈,是_objc_registerTaggedPointerClass() 函數(shù)請求_objc_fatal()殺死程序的赴涵!

那么_objc_registerTaggedPointerClass() 函數(shù)是什么呢?它憑什么一言不合就殺死程序毫胜?我們下節(jié)分析!

2.2、注冊成為Tagged Pointer

為什么NSNumber贸宏、NSDateNSString等類型可以自動轉(zhuǎn)為Tagged Pointer磕洪?而UIViewController就不能自動轉(zhuǎn)化呢吭练?這就要說到上節(jié)遺留的_objc_registerTaggedPointerClass() 函數(shù)了!析显!
加載程序時鲫咽,從 dyld 庫_dyld_start()函數(shù)開始,經(jīng)歷了多般步驟谷异,開始調(diào)用_objc_registerTaggedPointerClass() 函數(shù)分尸!

在講解_objc_registerTaggedPointerClass() 函數(shù)之前先說說題外話:classSlotForBasicTagIndex() 函數(shù)與classSlotForTagIndex() 函數(shù)!

2.2.1歹嘹、classSlotForBasicTagIndex() 函數(shù)
static Class *classSlotForBasicTagIndex(objc_tag_index_t tag){
    uintptr_t tagObfuscator = ((objc_debug_taggedpointer_obfuscator >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK);
    uintptr_t obfuscatedTag = tag ^ tagObfuscator;
    // objc_tag_classes 數(shù)組的索引包含標記的位本身

#if SUPPORT_MSB_TAGGED_POINTERS//高位優(yōu)先
    return &objc_tag_classes[0x8 | obfuscatedTag];
#else
    return &objc_tag_classes[(obfuscatedTag << 1) | 1];
#endif
}

classSlotForBasicTagIndex() 函數(shù)的主要功能就是根據(jù)指定索引 tag 從數(shù)組 objc_tag_classes中獲取類指針寓落;該函數(shù)要求索引tag是個有效的索引!

注:可能大家會對objc_debug_taggedpointer_obfuscator產(chǎn)生疑惑荞下,這個東西是什么?是用來干嘛的伶选?別急,在此處不影響該函數(shù)的功能尖昏,后文用到了我們再講仰税!

a、這里有兩個重要的全局變量數(shù)組:
#if SUPPORT_TAGGED_POINTERS //支持 Tagged Pointer

extern "C" {
    extern Class objc_debug_taggedpointer_classes[_OBJC_TAG_SLOT_COUNT*2];
    extern Class objc_debug_taggedpointer_ext_classes[_OBJC_TAG_EXT_SLOT_COUNT];
}
#define objc_tag_classes objc_debug_taggedpointer_classes
#define objc_tag_ext_classes objc_debug_taggedpointer_ext_classes
#endif

  • 數(shù)組objc_tag_classes:存儲蘋果定義的幾個基礎(chǔ)類抽诉;
  • 數(shù)組objc_tag_ext_classes:存儲蘋果預(yù)留的擴展類陨簇;
b、數(shù)組objc_debug_taggedpointer_classesobjc_debug_taggedpointer_ext_classes的初始化

筆者在 Runtime 庫objc-msg-armXX.s文件發(fā)現(xiàn)了相關(guān)代碼:

#if SUPPORT_TAGGED_POINTERS
    .data
    .align 3
    .globl _objc_debug_taggedpointer_classes
_objc_debug_taggedpointer_classes:
    .fill 16, 8, 0
    .globl _objc_debug_taggedpointer_ext_classes
_objc_debug_taggedpointer_ext_classes:
    .fill 256, 8, 0
#endif

注:在別的地方?jīng)]有關(guān)于這兩個數(shù)組的代碼迹淌,筆者懷疑這倆個數(shù)組在此處被初始化的河绽!如果有大神認為筆者錯了己单,這倆個數(shù)組的初始化另有地方,還望告知耙饰!

2.2.2纹笼、classSlotForTagIndex() 函數(shù)
static Class *classSlotForTagIndex(objc_tag_index_t tag){
    if (tag >= OBJC_TAG_First60BitPayload && tag <= OBJC_TAG_Last60BitPayload) {
        return classSlotForBasicTagIndex(tag);
    }

    if (tag >= OBJC_TAG_First52BitPayload && tag <= OBJC_TAG_Last52BitPayload) {
        int index = tag - OBJC_TAG_First52BitPayload;
        uintptr_t tagObfuscator = ((objc_debug_taggedpointer_obfuscator >> _OBJC_TAG_EXT_INDEX_SHIFT)& _OBJC_TAG_EXT_INDEX_MASK);
        return &objc_tag_ext_classes[index ^ tagObfuscator];
    }
    return nil;
}

該函數(shù)的主要功能就是根據(jù)指定索引 tag 獲取類指針:

  • 當(dāng)索引tag為基礎(chǔ)類的索引時,去數(shù)組objc_tag_classes中取數(shù)據(jù)苟跪;
  • 當(dāng)索引tag為擴展類的索引時廷痘,去數(shù)組objc_tag_ext_classes中取數(shù)據(jù);
  • 當(dāng)索引tag無效時件已,返回一個 nil笋额;
2.2.3、索引objc_tag_index_t

classSlotForBasicTagIndex() 函數(shù)與classSlotForTagIndex() 函數(shù)的本質(zhì)就是獲取數(shù)組objc_tag_classes與數(shù)組objc_tag_ext_classes中的數(shù)據(jù)篷扩!
那么它的索引 objc_tag_index_t 是何方神圣呢兄猩?

Runtime 庫objc-internal.h文件中找到了 objc_tag_index_t的定義:

//有關(guān)于標志位的枚舉如下:
#if __has_feature(objc_fixed_enum)  ||  __cplusplus >= 201103L
enum objc_tag_index_t : uint16_t
#else
typedef uint16_t objc_tag_index_t;//無符號短整型
enum
#endif
{
    // 60位凈負荷
    OBJC_TAG_NSAtom            = 0, 
    OBJC_TAG_1                 = 1, 
    OBJC_TAG_NSString          = 2, //表示這是一個NSString對象
    OBJC_TAG_NSNumber          = 3, //表示這是一個NSNumber對象
    OBJC_TAG_NSIndexPath       = 4, //表示這是一個NSIndexPath對象
    OBJC_TAG_NSManagedObjectID = 5, 
    OBJC_TAG_NSDate            = 6,//表示這是一個NSDate對象

    OBJC_TAG_RESERVED_7        = 7, //60位凈負荷: 索引 7 被保留

    // 52 位凈負荷
    OBJC_TAG_Photos_1          = 8,
    OBJC_TAG_Photos_2          = 9,
    OBJC_TAG_Photos_3          = 10,
    OBJC_TAG_Photos_4          = 11,
    OBJC_TAG_XPC_1             = 12,
    OBJC_TAG_XPC_2             = 13,
    OBJC_TAG_XPC_3             = 14,
    OBJC_TAG_XPC_4             = 15,

    OBJC_TAG_First60BitPayload = 0, 
    OBJC_TAG_Last60BitPayload  = 6, 
    OBJC_TAG_First52BitPayload = 8, // 52 位凈負荷的開始處
    OBJC_TAG_Last52BitPayload  = 263, // 52 位凈負荷的結(jié)束處

    OBJC_TAG_RESERVED_264      = 264 // 52 位凈負荷: 索引 264 被保留
};
#if __has_feature(objc_fixed_enum)  &&  !defined(__cplusplus)
typedef enum objc_tag_index_t objc_tag_index_t;
#endif

objc_tag_index_t 就是個枚舉變量,存儲在 Tagged Pointer的特殊標記的部位鉴未!

Runtime 就是根據(jù) objc_tag_index_t的枚舉值判斷Tagged Pointer存儲的是 NSString對象或者 NSNumber對象...

2.2.4枢冤、_objc_registerTaggedPointerClass() 函數(shù)

現(xiàn)在知道了classSlotForBasicTagIndex() 函數(shù)與classSlotForTagIndex() 函數(shù)的本質(zhì)就是獲取數(shù)組objc_tag_classes與數(shù)組objc_tag_ext_classes中的數(shù)據(jù)!而索引 objc_tag_index_t 又用來在Tagged Pointer對象標記存儲的類歼狼!

準備工作已經(jīng)做足掏导,現(xiàn)在去分析_objc_registerTaggedPointerClass()函數(shù)享怀! 在 Runtime 庫objc-runtime-new.mm文件中找到該函數(shù)的實現(xiàn):

void _objc_registerTaggedPointerClass(objc_tag_index_t tag, Class cls){
    if (objc_debug_taggedpointer_mask == 0) {
        _objc_fatal("tagged pointers are disabled");
    }

    Class *slot = classSlotForTagIndex(tag);//根據(jù)索引獲取指定的類指針
    if (!slot) {
        _objc_fatal("tag index %u is invalid", (unsigned int)tag);
    }

    Class oldCls = *slot;//取出指針指向的類
    if (cls  &&  oldCls  &&  cls != oldCls) {
        //指定的索引被用于兩個不同的類羽峰,終止程序
        _objc_fatal("tag index %u used for two different classes (was %p %s, now %p %s)", tag,oldCls, oldCls->nameForLogging(),cls, cls->nameForLogging());
    }
    *slot = cls;//將入?yún)?cls 賦值給該類指針指向的地址

    if (tag < OBJC_TAG_First60BitPayload || tag > OBJC_TAG_Last60BitPayload) {
        Class *extSlot = classSlotForBasicTagIndex(OBJC_TAG_RESERVED_7);
        if (*extSlot == nil) {
            extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
            *extSlot = (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer;//表示 TaggedPointer 類
        }
    }
}

分析該函數(shù)的實現(xiàn):

  • 1、首先判斷 objc_debug_taggedpointer_mask是否為 0 添瓷,也就是判斷開發(fā)者是否把 OBJC_DISABLE_TAGGED_POINTERS 設(shè)置為 YES梅屉;如果禁用了 Tagged Pointer,那么不好意思鳞贷,直接調(diào)用 _objc_fatal()函數(shù)終止該程序坯汤,不讓該程序啟動!
    只有啟用 Tagged Pointer搀愧,程序才有執(zhí)行下去的意義惰聂!
  • 2、根據(jù)索引 tag去取出數(shù)組objc_tag_classes或數(shù)組objc_tag_ext_classes中指定的類指針classSlotForTagIndex(tag)
    如果傳遞無效的索引 tag咱筛,獲取一個 nil搓幌,還是要調(diào)用_objc_fatal()終止該程序;
  • 3迅箩、嘗試著去獲取該指針指向的類Class oldCls = *slot:如果要注冊的類和該處的類不是同一個溉愁?不好意思,_objc_fatal()終止程序饲趋!
    只有類指針 slot指向的位置為 NULL拐揭,或者類指針 slot指向的位置就是存儲著我們要注冊的類撤蟆,系統(tǒng)才能安穩(wěn)的運行下去;
  • 4堂污、將入?yún)?code>cls賦值給類指針 slot指向的位置*slot = cls家肯;到此,該函數(shù)的功能經(jīng)過重重考驗就已經(jīng)實現(xiàn)了敷鸦!
  • 5息楔、假如注冊的不是基礎(chǔ)類,而是第一次注冊擴展類扒披,該函數(shù)還有個額外功能:在OBJC_TAG_RESERVED_7出存儲占位類 OBJC_CLASS_$___NSUnrecognizedTaggedPointer
2.2.5值依、打印系統(tǒng)注冊的Tagged Pointer

不妨在_objc_registerTaggedPointerClass()函數(shù)中插入一條 print()語句,打印出系統(tǒng)注冊的Tagged Pointer碟案,如下圖所示:

image

觀察打印日志愿险,可以得知NSNumberNSString等確實在啟動程序時价说,被系統(tǒng)注冊辆亏!

2.2.6、獲取_objc_registerTaggedPointerClass()函數(shù)調(diào)用棧

_objc_registerTaggedPointerClass()函數(shù)打一個斷點鳖目,打印該線程棧信息:

(lldb)  thread backtrace
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100382213 libobjc.A.dylib`::_objc_registerTaggedPointerClass(tag=OBJC_TAG_NSNumber, cls=__NSCFNumber) at objc-runtime-new.mm:6668
    frame #1: 0x00007fff433a0b5b CoreFoundation`__CFNumberGetTypeID_block_invoke + 65
    frame #2: 0x0000000100d087c3 libdispatch.dylib`_dispatch_client_callout + 8
    frame #3: 0x0000000100d0a48b libdispatch.dylib`_dispatch_once_callout + 87
    frame #4: 0x00007fff433a0b17 CoreFoundation`CFNumberGetTypeID + 39
    frame #5: 0x00007fff433a0103 CoreFoundation`__CFInitialize + 715
    frame #6: 0x0000000100020a68 dyld`ImageLoaderMachO::doImageInit(ImageLoader::LinkContext const&) + 316
    frame #7: 0x0000000100020ebb dyld`ImageLoaderMachO::doInitialization(ImageLoader::LinkContext const&) + 29
    frame #8: 0x000000010001c0da dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 358
    frame #9: 0x000000010001c06d dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 249
    frame #10: 0x000000010001c06d dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 249
    frame #11: 0x000000010001c06d dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 249
    frame #12: 0x000000010001b254 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 134
    frame #13: 0x000000010001b2e8 dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 74
    frame #14: 0x000000010000a756 dyld`dyld::initializeMainExecutable() + 169
    frame #15: 0x000000010000f78f dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 6237
    frame #16: 0x00000001000094f6 dyld`dyldbootstrap::start(macho_header const*, int, char const**, long, macho_header const*, unsigned long*) + 1154
    frame #17: 0x0000000100009036 dyld`_dyld_start + 54

具體的調(diào)用信息如上所示扮叨,這里不再過多啰嗦!

至此领迈,筆者已經(jīng)講完了注冊為Tagged Pointer的流程彻磁!

2.3、判斷一個指針是否是 Tagged Pointer

Tagged Pointer之所以特殊狸捅,是因為它有個標記位表明它是特殊指針衷蜓,那么該如何判斷這個特殊的標記呢?

Runtime 庫objc-internal.h文件中找到了 _objc_isTaggedPointer()的實現(xiàn):

static inline bool _objc_isTaggedPointer(const void * _Nullable ptr){
    //將一個指針地址和 _OBJC_TAG_MASK 常量做 & 運算:判斷該指針的最高位或者最低位為 1尘喝,那么這個指針就是 Tagged Pointer磁浇。
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

該函數(shù)將 指針ptr_OBJC_TAG_MASK 做了按位與 運算,這個運算有什么意義呢朽褪?我們先看下_OBJC_TAG_MASK 的定義:

#if OBJC_MSB_TAGGED_POINTERS //MSB 高位優(yōu)先
#   define _OBJC_TAG_MASK (1UL<<63) //Tagged Pointer 指針
#else //LSB 低位優(yōu)先
#   define _OBJC_TAG_MASK 1UL //Tagged Pointer 指針
#endif

對于 iOS 系統(tǒng)而言置吓,遵循 MSB 規(guī)則(高位優(yōu)先)!因此_OBJC_TAG_MASK的值為 0x8000000000000000:一個64 位的二進制缔赠,最左邊一位是 1衍锚,其余位全是 0!

在 64 位系統(tǒng)中橡淑,使用指針很難將有限的 CPU 資源耗盡构拳;因此 64 位還有很大的剩余! 蘋果將64中的最左邊一位(MSB 時)標記是 1 ,或者最右邊一位(LSB 時)標記是 1 置森,以此來表示這個指針是 Tagged Pointer 斗埂。

因此 ptr & _OBJC_TAG_MASK) 按位與 運算后可以判斷它的標志位是否是 1,即是否是 Tagged Pointer 凫海!

2.4呛凶、在偽指針 Tagged Pointer存儲數(shù)據(jù)

講了半天:又是禁用 Tagged Pointer、又是注冊 Tagged Pointer行贪、又是判斷 Tagged Pointer 的 漾稀! 那么到底怎么存儲NSNumberNSDate建瘫、NSString等類型的數(shù)據(jù)呢崭捍?

Runtime 庫objc-internal.h文件中找到了 _objc_makeTaggedPointer() 函數(shù)的實現(xiàn):

static inline void * _Nonnull _objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value){
    if (tag <= OBJC_TAG_Last60BitPayload) {//如果是基礎(chǔ)的索引
        uintptr_t result = (_OBJC_TAG_MASK | ((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) |  ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT));
        return _objc_encodeTaggedPointer(result);
    } else {//如果是擴展的索引
        uintptr_t result =  (_OBJC_TAG_EXT_MASK |  ((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) | ((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT));
        return _objc_encodeTaggedPointer(result);
    }
}

該函數(shù)將 value 位運算,然后再編碼 _objc_encodeTaggedPointer()啰脚,就是指針的值殷蛇!

a、嘗試計算
#if OBJC_MSB_TAGGED_POINTERS //MSB 高位優(yōu)先
#   define _OBJC_TAG_MASK (1UL<<63) //Tagged Pointer 指針
#   define _OBJC_TAG_INDEX_SHIFT 60
#   define _OBJC_TAG_SLOT_SHIFT 60
#   define _OBJC_TAG_PAYLOAD_LSHIFT 4
#   define _OBJC_TAG_PAYLOAD_RSHIFT 4
#   define _OBJC_TAG_EXT_MASK (0xfUL<<60)
#   define _OBJC_TAG_EXT_INDEX_SHIFT 52
#   define _OBJC_TAG_EXT_SLOT_SHIFT 52
#   define _OBJC_TAG_EXT_PAYLOAD_LSHIFT 12
#   define _OBJC_TAG_EXT_PAYLOAD_RSHIFT 12
#else //LSB 低位優(yōu)先
#   define _OBJC_TAG_MASK 1UL //Tagged Pointer 指針
#   define _OBJC_TAG_INDEX_SHIFT 1
#   define _OBJC_TAG_SLOT_SHIFT 0
#   define _OBJC_TAG_PAYLOAD_LSHIFT 0
#   define _OBJC_TAG_PAYLOAD_RSHIFT 4
#   define _OBJC_TAG_EXT_MASK 0xfUL
#   define _OBJC_TAG_EXT_INDEX_SHIFT 4
#   define _OBJC_TAG_EXT_SLOT_SHIFT 4
#   define _OBJC_TAG_EXT_PAYLOAD_LSHIFT 0
#   define _OBJC_TAG_EXT_PAYLOAD_RSHIFT 12
#endif

我們以 1.4 節(jié) 中的NSNumber為例嘗試計算一下:當(dāng)存儲整數(shù)0 時Tagged Pointer的值為0xb000000000000002i吓ā(以MSB 規(guī)則計算)

由于NSNumber為基礎(chǔ)類粒梦,它的索引為 OBJC_TAG_NSNumber=3,所以 tag <= OBJC_TAG_Last60BitPayload

  • 1荸实、_OBJC_TAG_MASK 取值 0x8000000000000000 匀们;
  • 2、tag << _OBJC_TAG_INDEX_SHIFT 轉(zhuǎn)為0x3 << 60准给,位運算結(jié)果是 0x3000000000000000泄朴;該運算的目的是將類的標識符存儲在標志位里;
  • 3圆存、_OBJC_TAG_MASK | ((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) 轉(zhuǎn)為 0x8000000000000000 | 0x3000000000000000 叼旋,位運算結(jié)果是0xb000000000000000仇哆;該結(jié)果就是Tagged Pointer的標記沦辙;
  • 4、(value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT)
  • 5讹剔、先來說說(value << _OBJC_TAG_PAYLOAD_RSHIFT)油讯,將value左移 4 位,為何是 4 位延欠?因為有 4 位標識符陌兑,最右邊一位的1Tagged Pointer的標志,剩余三位是存儲類的識別由捎!
  • 6兔综、再來說說>> _OBJC_TAG_PAYLOAD_LSHIFT),將第 5 步的位運算結(jié)果右移 _OBJC_TAG_PAYLOAD_LSHIFT
    遵循 MSB 時右移 4 位,因為4 位標識符在最左邊软驰,即最左邊 4 位不能用來表示值涧窒!
    遵循 LSB 時右移 0 位,因為4 位標識符在最右邊锭亏,即最右邊 4 位不能用來表示值纠吴!
  • 7、第4步公式轉(zhuǎn)為(0<<4)>>0計算結(jié)果是:0
  • 8慧瘤、最終 result結(jié)果是 0xb0000000000000004饕选!

疑問:程序運行的是 0xb000000000000002锅减,但為何計算的是0xb000000000000000糖儡?該處問題筆者暫時沒有找到答案,如果有大神知道為何不同怔匣,怎么計算休玩,還望告知!劫狠!

b拴疤、編碼與解碼 Tagged Pointer 指針的函數(shù)
/* 編碼 TaggedPointer 指針
 */
static inline void * _Nonnull _objc_encodeTaggedPointer(uintptr_t ptr){
    return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);
}

/* 解碼 TaggedPointer 指針
 * @param ptr 編碼后的 TaggedPointer 指針
 */
static inline uintptr_t _objc_decodeTaggedPointer(const void * _Nullable ptr){
    return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}

注:此處又出現(xiàn)了變量objc_debug_taggedpointer_obfuscator,在該處的取值為 0 独泞,至于作用呐矾,后文再講!

通過一步步計算可知懦砂,Tagged Pointer指針上存儲的數(shù)據(jù)我們完全能夠計算出來蜒犯,此時數(shù)據(jù)暴露在外,及其危險荞膘!蘋果為了數(shù)據(jù)安全問題罚随,設(shè)計了數(shù)據(jù)混淆!

2.5羽资、Tagged Pointer的數(shù)據(jù)混淆

在 2.1.2 節(jié)的_read_images()函數(shù)中還遺留了 initializeTaggedPointerObfuscator()沒有講解淘菩,現(xiàn)在來說說它的作用。
Runtime 庫objc-runtime-new.mm文件中找到該函數(shù)的實現(xiàn):

static void initializeTaggedPointerObfuscator(void){
    if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) || DisableTaggedPointerObfuscation) {
        // 對于鏈接到舊sdk的應(yīng)用程序屠升,如果它們依賴于tagged pointer表示潮改,將混淆器設(shè)置為0,
        objc_debug_taggedpointer_obfuscator = 0;
    } else {
        // 將隨機數(shù)據(jù)放入變量中腹暖,然后移走所有非凈負荷位汇在。
        arc4random_buf(&objc_debug_taggedpointer_obfuscator,
                       sizeof(objc_debug_taggedpointer_obfuscator));
        objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
    }
}

該函數(shù)的主要功能:

  • 如果SDK版本過低,或者禁用混淆脏答,則設(shè)置 objc_debug_taggedpointer_obfuscator 為 0糕殉;
  • 否則為objc_debug_taggedpointer_obfuscator 設(shè)置一個隨機數(shù)亩鬼;

前文遺留的objc_debug_taggedpointer_obfuscator是什么,到此終于有了明確的定義:該變量就是一個隨機生成的數(shù)字阿蝶,通過編碼函數(shù)辛孵、或者解碼函數(shù),用來與 value做位運算赡磅,這樣控制臺就看不出是否是Tagged Pointer 指針魄缚,該指針存儲的數(shù)據(jù)也是安全的!

2.5.1焚廊、Tagged Pointer數(shù)據(jù)混淆功能的開啟與關(guān)閉
OPTION( DisableTaggedPointerObfuscation, OBJC_DISABLE_TAG_OBFUSCATION,    "disable obfuscation of tagged pointers")

通過設(shè)置環(huán)境變量OBJC_DISABLE_TAG_OBFUSCATIONYES冶匹,可以關(guān)閉Tagged Pointer的數(shù)據(jù)混淆,方便我們調(diào)試程序咆瘟!

2.5.2嚼隘、objc_debug_taggedpointer_obfuscator的使用

除了上文的編碼解碼函數(shù)使用了objc_debug_taggedpointer_obfuscator來混淆指針,還有最前文提到的通過指定索引tag獲取類指針時使用objc_debug_taggedpointer_obfuscator袒餐。

2.6飞蛹、獲取Tagged Pointer的數(shù)據(jù)

前文講解了如何存儲Tagged Pointer的數(shù)據(jù),如何加密Tagged Pointer的數(shù)據(jù)灸眼!現(xiàn)在我們來看下如何獲取Tagged Pointer的數(shù)據(jù)卧檐!

Runtime 庫objc-internal.h文件中找到相關(guān)函數(shù)的實現(xiàn):

/* 獲取 Tagged Pointer 指針上存儲的數(shù)據(jù)
 * @note 存儲的數(shù)據(jù)是 zero-extended
 * @note 前提條件:假設(shè)啟用了 tagged pointer 功能
 */
static inline uintptr_t _objc_getTaggedPointerValue(const void * _Nullable ptr){
    // assert(_objc_isTaggedPointer(ptr));
    uintptr_t value = _objc_decodeTaggedPointer(ptr);// 解碼 TaggedPointer 指針
    uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
    if (basicTag == _OBJC_TAG_INDEX_MASK) {
        return (value << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT;
    } else {
        return (value << _OBJC_TAG_PAYLOAD_LSHIFT) >> _OBJC_TAG_PAYLOAD_RSHIFT;
    }
}

/* 獲取 Tagged Pointer 指針上存儲的數(shù)據(jù)
 * @note 存儲的數(shù)據(jù)是 sign-extended
 * @note 前提條件:假設(shè)啟用了 tagged pointer 功能
 */
static inline intptr_t _objc_getTaggedPointerSignedValue(const void * _Nullable ptr){
    // assert(_objc_isTaggedPointer(ptr));
    uintptr_t value = _objc_decodeTaggedPointer(ptr);
    uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
    if (basicTag == _OBJC_TAG_INDEX_MASK) {
        return ((intptr_t)value << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT;
    } else {
        return ((intptr_t)value << _OBJC_TAG_PAYLOAD_LSHIFT) >> _OBJC_TAG_PAYLOAD_RSHIFT;
    }
}

2.7、Tagged Pointer其他函數(shù)

/* 獲取一個 Tagged Pointer 指針的索引
 * @param ptr 指定的指針
 */
static inline objc_tag_index_t _objc_getTaggedPointerTag(const void * _Nullable ptr){
    // assert(_objc_isTaggedPointer(ptr));
    uintptr_t value = _objc_decodeTaggedPointer(ptr);
    uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
    uintptr_t extTag =   (value >> _OBJC_TAG_EXT_INDEX_SHIFT) & _OBJC_TAG_EXT_INDEX_MASK;
    if (basicTag == _OBJC_TAG_INDEX_MASK) {
        return (objc_tag_index_t)(extTag + OBJC_TAG_First52BitPayload);
    } else {
        return (objc_tag_index_t)basicTag;
    }
}

/* 根據(jù)指定的索引獲取 Tagged Pointer 表示的類
 * @param tag 指定的索引
 * @return 如果該索引還沒有使用 或 該索引超出范圍焰宣,則返回nil霉囚。
 */
Class _objc_getClassForTag(objc_tag_index_t tag){
    Class *slot = classSlotForTagIndex(tag);
    if (slot) return *slot;
    else return nil;
}

至此,關(guān)于 偽指針Tagged Pointer匕积,筆者已經(jīng)講完盈罐!

七 自動釋放池的結(jié)構(gòu)和工作原理、autorelease與引用計數(shù)

http://blog.sunnyxx.com/2014/10/15/behind-autorelease/

八 內(nèi)存對齊

http://www.reibang.com/p/3294668e2d8c

九 copy及mutablecopy

首先來說闪唆,copy及mutablecopy/alloc/new都會獲取對象所有權(quán)進行引用計數(shù)的+1盅粪。
兩者的區(qū)別是

深拷貝:對象拷貝 -> 直接拷貝內(nèi)容。
淺拷貝:指針拷貝 -> 將指針中的地址值拷貝一份悄蕾。
  • 不可變對象(NSString票顾、NSArray)的copy都是淺拷貝;
  • 可變對象的(NSMutableString笼吟、NSMutableArray) copy都是深拷貝库物;
  • 無論可變對象還是不可變對象的 mutableCopy都是深拷貝;
  • 自定義對象需要實現(xiàn)NSCopying協(xié)議

參考資料:
1: https://juejin.cn/post/6844904030246797319
2: https://halfrost.com/objc_runtime_isa_class/
3: http://www.reibang.com/p/3176e30c040b

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末霸旗,一起剝皮案震驚了整個濱河市贷帮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌诱告,老刑警劉巖撵枢,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件民晒,死亡現(xiàn)場離奇詭異,居然都是意外死亡锄禽,警方通過查閱死者的電腦和手機潜必,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沃但,“玉大人磁滚,你說我怎么就攤上這事∠恚” “怎么了垂攘?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長淤刃。 經(jīng)常有香客問我晒他,道長,這世上最難降的妖魔是什么逸贾? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任陨仅,我火速辦了婚禮,結(jié)果婚禮上铝侵,老公的妹妹穿的比我還像新娘灼伤。我一直安慰自己,他們只是感情好咪鲜,可當(dāng)我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布饺蔑。 她就那樣靜靜地躺著,像睡著了一般嗜诀。 火紅的嫁衣襯著肌膚如雪猾警。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天隆敢,我揣著相機與錄音发皿,去河邊找鬼。 笑死拂蝎,一個胖子當(dāng)著我的面吹牛穴墅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播温自,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼玄货,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了悼泌?” 一聲冷哼從身側(cè)響起松捉,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎馆里,沒想到半個月后隘世,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體可柿,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年丙者,在試婚紗的時候發(fā)現(xiàn)自己被綠了复斥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡械媒,死狀恐怖目锭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情纷捞,我是刑警寧澤侣集,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站兰绣,受9級特大地震影響世分,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜缀辩,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一臭埋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧臀玄,春花似錦瓢阴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至累贤,卻和暖如春叠穆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背臼膏。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工硼被, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人渗磅。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓嚷硫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親始鱼。 傳聞我的和親對象是個殘疾皇子仔掸,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,689評論 2 354

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