NSTaggedPointer和NONPOINTER_ISA

內(nèi)存管理之Tagged pointer

iOS開(kāi)發(fā)者對(duì)引用計(jì)數(shù)這個(gè)名詞肯定不陌生,引用計(jì)數(shù)是蘋(píng)果為了方便開(kāi)發(fā)者管理內(nèi)存而引入的一個(gè)概念,當(dāng)引用計(jì)數(shù)為0時(shí),對(duì)象就會(huì)被釋放。但是,真的是所有對(duì)象都是這樣嗎?

內(nèi)存分配

iOS將虛擬內(nèi)存按照地址由低到高劃分為如下五個(gè)區(qū):

[圖片上傳失敗...(image-b07219-1625042343370)]

在程序運(yùn)行時(shí),代碼區(qū),常量區(qū)以及全局靜態(tài)區(qū)的大小是固定的,會(huì)變化的只有棧和堆的大小。而棧的內(nèi)存是有操作系統(tǒng)自動(dòng)釋放的,我們平常說(shuō)所的iOS內(nèi)存引用計(jì)數(shù),其實(shí)是就堆上的對(duì)象來(lái)說(shuō)的。

如何引入tagged pointer

自2013年蘋(píng)果推出iphone5s之后,iOS的尋址空間擴(kuò)大到了64位。我們可以用63位來(lái)表示一個(gè)數(shù)字(一位做符號(hào)位)。那么這個(gè)數(shù)字的范圍是2^63 ,很明顯我們一般不會(huì)用到這么大的數(shù)字,那么在我們定義一個(gè)數(shù)字時(shí)NSNumber *num = @100,實(shí)際上內(nèi)存中浪費(fèi)了很多的內(nèi)存空間。

當(dāng)然蘋(píng)果肯定也認(rèn)識(shí)到了這個(gè)問(wèn)題,于是就引入了tagged pointer,tagged pointer是一種特殊的“指針”,其特殊在于,其實(shí)它存儲(chǔ)的并不是地址,而是真實(shí)的數(shù)據(jù)和一些附加的信息。

我們可以在WWDC2013的《Session 404 Advanced in Objective-C》視頻中,看到蘋(píng)果對(duì)于Tagged Pointer特點(diǎn)的介紹:

image
  • Tagged Pointer專(zhuān)門(mén)用來(lái)存儲(chǔ)小的對(duì)象,例如NSNumber, NSDate, NSString。
  • Tagged Pointer指針的值不再是地址了,而是真正的值。所以,實(shí)際上它不再是一個(gè)對(duì)象了,它只是一個(gè)披著對(duì)象皮的普通變量而已。所以,它的內(nèi)存并不存儲(chǔ)在堆中,也不需要malloc和free。
  • 在內(nèi)存讀取上有著3倍的效率,創(chuàng)建時(shí)比以前快106倍。

NSTaggedPointer

我們先看下下面這段代碼:

NSMutableString *mutableStr = [NSMutableString string];
    NSString *immutable = nil;
    #define _OBJC_TAG_MASK (1UL<<63)
    char c = 'a';
    do {
        [mutableStr appendFormat:@"%c", c++];
        immutable = [mutableStr copy];
        NSLog(@"%p %@ %@", immutable, immutable, immutable.class);
    }while(((uintptr_t)immutable & _OBJC_TAG_MASK) == _OBJC_TAG_MASK);

運(yùn)行結(jié)果:

2020-08-08 14:15:54.480862+0800 TaggedPointerDemo[55468:2078125] 0xdc5050684e86e57c a NSTaggedPointerString
2020-08-08 14:15:54.481719+0800 TaggedPointerDemo[55468:2078125] 0xdc5050684e80c57f ab NSTaggedPointerString
2020-08-08 14:15:54.482480+0800 TaggedPointerDemo[55468:2078125] 0xdc50506848b0c57e abc NSTaggedPointerString
2020-08-08 14:15:54.483342+0800 TaggedPointerDemo[55468:2078125] 0xdc50506e08b0c579 abcd NSTaggedPointerString
2020-08-08 14:15:54.483950+0800 TaggedPointerDemo[55468:2078125] 0xdc50563e08b0c578 abcde NSTaggedPointerString
2020-08-08 14:15:54.484246+0800 TaggedPointerDemo[55468:2078125] 0xdc56363e08b0c57b abcdef NSTaggedPointerString
2020-08-08 14:15:54.484800+0800 TaggedPointerDemo[55468:2078125] 0xda26363e08b0c57a abcdefg NSTaggedPointerString
2020-08-08 14:15:54.485200+0800 TaggedPointerDemo[55468:2078125] 0xdc527050ee978a35 abcdefgh NSTaggedPointerString
2020-08-08 14:15:54.485644+0800 TaggedPointerDemo[55468:2078125] 0xdcd85e404adcb774 abcdefghi NSTaggedPointerString
2020-08-08 14:15:54.486003+0800 TaggedPointerDemo[55468:2078125] 0x28334c2c0 abcdefghij __NSCFString

上圖我們可以看到,當(dāng)字符串的長(zhǎng)度為10個(gè)以?xún)?nèi)時(shí),字符串的類(lèi)型都是NSTaggedPointerString類(lèi)型,當(dāng)超過(guò)10個(gè)時(shí),字符串的類(lèi)型才是__NSCFString

打印結(jié)果分析:

NSTaggedPointer標(biāo)志位

static inline bool 
_objc_isTaggedPointer(const void * _Nullable ptr)
{
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

上面這個(gè)方法我們看到,判斷一個(gè)對(duì)象類(lèi)型是否為NSTaggedPointerString類(lèi)型實(shí)際上是講對(duì)象的地址與_OBJC_TAG_MASK進(jìn)行按位與操作,結(jié)果在跟_OBJC_TAG_MASK進(jìn)行對(duì)比,我們?cè)诳聪?code>_OBJC_TAG_MASK的定義:

#if OBJC_MSB_TAGGED_POINTERS
#   define _OBJC_TAG_MASK (1UL<<63)
#else
#   define _OBJC_TAG_MASK 1UL
#endif

我們都知道一個(gè)對(duì)象地址為64位二進(jìn)制,它表明如果64位數(shù)據(jù)中,最高位是1的話(huà),則表明當(dāng)前是一個(gè)tagged pointer類(lèi)型。

那么我們?cè)诳聪律厦娲蛴〕龅牡刂?,所?code>NSTaggedPointerString地址都是0xd開(kāi)頭,d轉(zhuǎn)換為二進(jìn)制1110,根據(jù)上面的結(jié)論,我們看到首位為1表示為NSTaggedPointerString類(lèi)型。在這里得到驗(yàn)證。

注意:TaggedPointer類(lèi)型在iOS和MacOS中標(biāo)志位是不同的iOS為最高位而MacOS為最低位

對(duì)象類(lèi)型

正常情況下一個(gè)對(duì)象的類(lèi)型,是通過(guò)這個(gè)對(duì)象的ISA指針來(lái)判斷的,那么對(duì)于NSTaggedPointer類(lèi)型我們?nèi)绾瓮ㄟ^(guò)地址判斷對(duì)應(yīng)數(shù)據(jù)是什么類(lèi)型的呢?

objc4-723之前

在objc4-723之前,我們可以通過(guò)與判斷TaggedPointer標(biāo)志位一樣根據(jù)地址來(lái)判斷,而類(lèi)型的標(biāo)志位就是對(duì)象地址的61-63位,比如對(duì)象地址為0xa開(kāi)頭,那么轉(zhuǎn)換成二進(jìn)制位1010,那么去掉最高位標(biāo)志位后,剩余為010,即10進(jìn)制中的2。

接著我們看下runtime源碼objc-internal.h中有關(guān)于標(biāo)志位的定義如下:

#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-bit payloads
    OBJC_TAG_NSAtom            = 0, 
    OBJC_TAG_1                 = 1, 
    OBJC_TAG_NSString          = 2, 
    OBJC_TAG_NSNumber          = 3, 
    OBJC_TAG_NSIndexPath       = 4, 
    OBJC_TAG_NSManagedObjectID = 5, 
    OBJC_TAG_NSDate            = 6,

    // 60-bit reserved
    OBJC_TAG_RESERVED_7        = 7, 

    // 52-bit payloads
    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, 
    OBJC_TAG_Last52BitPayload  = 263, 

    OBJC_TAG_RESERVED_264      = 264
};
#if __has_feature(objc_fixed_enum)  &&  !defined(__cplusplus)
typedef enum objc_tag_index_t objc_tag_index_t;
#endif

那么我們知道2表示的OBJC_TAG_NSString即字符串類(lèi)型。因?yàn)槟壳耙呀?jīng)無(wú)法驗(yàn)證這種情況了 所以我們不做其他類(lèi)型驗(yàn)證。

objc4-750之后

// Returns a pointer to the class's storage in the tagged class arrays.
// Assumes the tag is a valid basic tag.
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;
    // Array index in objc_tag_classes includes the tagged bit itself
#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中獲取類(lèi)指針,而下標(biāo)的計(jì)算方法發(fā)是根據(jù)外部傳遞的索引tag。比如字符串 tag = 2。當(dāng)然這并不是簡(jiǎn)單的從數(shù)組中獲取某條數(shù)據(jù)。

uint16_t NSString_Tag = 2;
uint16_t NSNumber_Tag = 3;
// 3 = 0011
// _OBJC_TAG_INDEX_MASK = 0x7 = 0111
        uintptr_t string_tagObfuscator = ((objc_debug_taggedpointer_obfuscator
                                           >> _OBJC_TAG_INDEX_SHIFT)
                                          & _OBJC_TAG_INDEX_MASK);

        uintptr_t number_tagObfuscator = ((objc_debug_taggedpointer_obfuscator
                                           >> _OBJC_TAG_INDEX_SHIFT)
                                          & _OBJC_TAG_INDEX_MASK);

// 異或操作 相同返回0 不同返回1
// 2 ^ 3 = 0010 ^ 0011 = 0001
// 3^ 3 = 0011 ^ 0011 = 0000
        uintptr_t string_obfuscatedTag = NSString_Tag ^ string_tagObfuscator;
        uintptr_t number_obfuscatedTag = NSNumber_Tag ^ number_tagObfuscator;

// 按位或
// 1000 | 0001 = 1001 = 9
// 1000 | 0000 = 1000 = 8
        NSLog(@"%@", objc_tag_classes[0x8 | string_obfuscatedTag]);
        NSLog(@"%@", objc_tag_classes[0x8 | number_obfuscatedTag]);

控制臺(tái)輸出為:

TaggedPointer[89420:3027642] NSTaggedPointerString
TaggedPointer[89420:3027642] __NSCFNumber

當(dāng)我們多次運(yùn)行時(shí),我們發(fā)現(xiàn)實(shí)際上每次獲取到的string_tagObfuscatornumber_obfuscatedTag都不一樣,但是每次從objc_tag_classes中取出的類(lèi)型均是一致的,因此實(shí)際上每次運(yùn)行objc_tag_classes中的內(nèi)容也是不斷變化的。

如果你想進(jìn)一步的了解可以參考Objective-C中偽指針Tagged Pointer

NSCFNumber

下面我們?cè)诳聪翹SNumber類(lèi)型

NSNumber *number1 = @(0x1);
    NSNumber *number2 = @(0x20);
    NSNumber *number3 = @(0x3F);
    NSNumber *numberFFFF = @(0xFFFFFFFFFFEFE);
    NSNumber *maxNum = @(MAXFLOAT);
    NSLog(@"number1 pointer is %p class is %@", number1, number1.class);
    NSLog(@"number2 pointer is %p class is %@", number2, number2.class);
    NSLog(@"number3 pointer is %p class is %@", number3, number3.class);
    NSLog(@"numberffff pointer is %p class is %@", numberFFFF, numberFFFF.class);
    NSLog(@"maxNum pointer is %p class is %@", maxNum, maxNum.class);

我們?cè)诳聪麓蛴〗Y(jié)果:

TaggedPointerDemo[59218:2167895] number1 pointer is 0xf7cb914ffb51479a class is __NSCFNumber
TaggedPointerDemo[59218:2167895] number2 pointer is 0xf7cb914ffb51458a class is __NSCFNumber
TaggedPointerDemo[59218:2167895] number3 pointer is 0xf7cb914ffb51447a class is __NSCFNumber
TaggedPointerDemo[59218:2167895] numberffff pointer is 0xf7346eb004aea86b class is __NSCFNumber
TaggedPointerDemo[59218:2167895] maxNum pointer is 0x28172a0c0 class is __NSCFNumber

我們發(fā)現(xiàn)對(duì)于NSNumber,我們打印出來(lái)的數(shù)據(jù)類(lèi)型均為__NSCFNumber,但是我們發(fā)現(xiàn)對(duì)于MAXFLOAT打印出的地址顯然與其他幾項(xiàng)不符,上面幾個(gè)NSNumber的地址以0xf開(kāi)頭,根據(jù)字符串地址的經(jīng)驗(yàn)我們可以看出f = 1111,首位標(biāo)記位為1,表示這個(gè)數(shù)據(jù)類(lèi)型屬于TaggedPointer。而MAXFLOAT不是。

獲取TaggedPointer的值

objc4-723之前

字符串:

[圖片上傳失敗...(image-e6401a-1625042343369)]

從上圖的地址中我們就可以看出,從低位到高位分別表示的就是字符串的值(在A(yíng)SCII碼表中的值)

數(shù)字:

[圖片上傳失敗...(image-1f04a-1625042343369)]

對(duì)于數(shù)字來(lái)說(shuō)從地址中也是直接讀出存儲(chǔ)的值,如上圖。

objc4-750之后

static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
    return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}

static inline uintptr_t
_objc_getTaggedPointerValue(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 (value << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT;
    } else {
        return (value << _OBJC_TAG_PAYLOAD_LSHIFT) >> _OBJC_TAG_PAYLOAD_RSHIFT;
    }
}

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;
    }
}

示例代碼:

NSString *str1 = [NSString stringWithFormat:@"1"];
        NSString *str11 = [NSString stringWithFormat:@"11"];
        NSString *str2 = [NSString stringWithFormat:@"2"];
        NSString *str22 = [NSString stringWithFormat:@"22"];

        // 0x31 1 0x32 1
        uintptr_t value1 = objc_getTaggedPointerValue((__bridge void *)str1);
        uintptr_t value2 = objc_getTaggedPointerValue((__bridge void *)str2);
        uintptr_t value11 = objc_getTaggedPointerValue((__bridge void *)str11);
        uintptr_t value22 = objc_getTaggedPointerValue((__bridge void *)str22);
        // 以16進(jìn)制形式輸出
        NSLog(@"%lx", value1);
        NSLog(@"%lx", value11);
        NSLog(@"%lx", value2);
        NSLog(@"%lx", value22);

控制臺(tái)輸出:

TaggedPointer[89535:3033433] 311
TaggedPointer[89535:3033433] 31312
TaggedPointer[89535:3033433] 321
TaggedPointer[89535:3033433] 32322

即 "1" = 0x31 1,最后一位表示長(zhǎng)度,在A(yíng)SCII碼表中31表示的就是字符1。而且從字符串“11”的結(jié)果我們也可以驗(yàn)證上面的說(shuō)法。

isa 指針(NONPOINTER_ISA)

上面我們說(shuō)了,對(duì)于一個(gè)對(duì)象的存儲(chǔ),蘋(píng)果做了優(yōu)化,那么對(duì)于ISA指針呢?

對(duì)象的isa指針,用來(lái)表明對(duì)象所屬的類(lèi)類(lèi)型。

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

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

同時(shí)結(jié)合下圖,我們可以更清晰的了解isa指針的作用以及類(lèi)對(duì)象的概念。

image

從圖中可以看出,我們所謂的isa指針,最后實(shí)際上落腳于isa_t的聯(lián)合類(lèi)型。那么何為聯(lián)合類(lèi)型呢? 聯(lián)合類(lèi)型是C語(yǔ)言中的一種類(lèi)型,是一種n選1的關(guān)系,聯(lián)合的作用在于,用更少的空間,表示了更多的可能的類(lèi)型,雖然這些類(lèi)型是不能夠共存的。比如isa_t 中包含有cls,bits, struct三個(gè)變量,它們的內(nèi)存空間是重疊的。在實(shí)際使用時(shí),僅能夠使用它們中的一種,你把它當(dāng)做cls,就不能當(dāng)bits訪(fǎng)問(wèn),你把它當(dāng)bits,就不能用cls來(lái)訪(fǎng)問(wèn)。

對(duì)于isa_t聯(lián)合類(lèi)型,主要包含了兩個(gè)構(gòu)造函數(shù)isa_t(),isa_t(uintptr_t value)和三個(gè)變量cls,bits,struct,而uintptr_t的定義為typedef unsigned long

當(dāng)isa_t作為Class cls使用時(shí),這符合了我們之前一貫的認(rèn)知:isa是一個(gè)指向?qū)ο笏鶎貱lass類(lèi)型的指針。然而,僅讓一個(gè)64位的指針表示一個(gè)類(lèi)型,顯然不劃算。

因此,絕大多數(shù)情況下,蘋(píng)果采用了優(yōu)化的isa策略,即,isa_t類(lèi)型并不等同而Class cls, 而是struct

struct

下面我們先來(lái)看下struct的結(jié)構(gòu)體

// ISA_BITFIELD定義如下
# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)

注意:成員后面的:表明了該成員占用幾個(gè)bit 而每個(gè)成員的意義如下表

標(biāo)志位說(shuō)明

| 成員 | bit位 | 說(shuō)明 | | --- | --- | --- | | nonpointer | 1bit | 標(biāo)志位。1(奇數(shù))表示開(kāi)啟了isa優(yōu)化,0(偶數(shù))表示沒(méi)有啟用isa優(yōu)化。所以,我們可以通過(guò)判斷isa是否為奇數(shù)來(lái)判斷對(duì)象是否啟用了isa優(yōu)化 | | has_assoc | 1bit | 標(biāo)志位。表明對(duì)象是否有關(guān)聯(lián)對(duì)象。沒(méi)有關(guān)聯(lián)對(duì)象的對(duì)象釋放的更快。 | | has_cxx_dtor | 1bit | 標(biāo)志位。表明對(duì)象是否有C++或ARC析構(gòu)函數(shù)。沒(méi)有析構(gòu)函數(shù)的對(duì)象釋放的更快| | shiftcls | 33bit | 類(lèi)指針的非零位。 | | magic | 6bit | 固定為0x1a,用于在調(diào)試時(shí)區(qū)分對(duì)象是否已經(jīng)初始化。 | | weakly_referenced | 1bit | 標(biāo)志位。用于表示該對(duì)象是否被別的對(duì)象弱引用。沒(méi)有被弱引用的對(duì)象釋放的更快。 | | deallocating | 1bit | 標(biāo)志位。用于表示該對(duì)象是否正在被釋放。 | | has_sidetable_rc | 1bit | 標(biāo)志位。用于標(biāo)識(shí)是否當(dāng)前的引用計(jì)數(shù)過(guò)大,無(wú)法在isa中存儲(chǔ),而需要借用sidetable來(lái)存儲(chǔ)。(這種情況大多不會(huì)發(fā)生) | | extra_rc | 19bit | 對(duì)象的引用計(jì)數(shù)減1。比如,一個(gè)object對(duì)象的引用計(jì)數(shù)為7,則此時(shí)extra_rc的值為6。 |

從上表我們發(fā)現(xiàn),extra_rchas_sidetable_rc是和引用計(jì)數(shù)相關(guān)的標(biāo)志位,當(dāng)extra_rc 不夠用時(shí),還會(huì)借助sidetable來(lái)存儲(chǔ)計(jì)數(shù)值,這時(shí),has_sidetable_rc會(huì)被標(biāo)志為1。

接下來(lái)我們來(lái)驗(yàn)證下,這些標(biāo)志位是否真的如表中介紹那樣。

引用計(jì)數(shù)

我們先來(lái)看下面這段代碼

- (void)testisa {
    NSObject *obj = [[NSObject alloc] init];
    NSLog(@"1\. obj isa_t = %p", *(void **)(__bridge void*)obj);
}

控制臺(tái)輸出結(jié)果

TaggedPointerDemo[59983:2185591] 1\. obj isa_t = 0x1a1f335beb1

我們將地址0x1a1f335beb1轉(zhuǎn)換過(guò)后:

image

我們看到這時(shí)候 對(duì)象是nonpointer開(kāi)啟了isa優(yōu)化,且當(dāng)前的引用計(jì)數(shù)器為 extra_rc = 0 + 1 = 1;

下面我們接著測(cè)試

NSObject *obj = [[NSObject alloc] init];
    NSLog(@"1\. obj isa_t = %p", *(void **)(__bridge void*)obj);
    _obj1 = obj;
    NSObject *tmpObj = obj;
    NSLog(@"2\. obj isa_t = %p", *(void **)(__bridge void*)obj);

控制臺(tái)輸出為

TaggedPointerDemo[63235:2266690] 1\. obj isa_t = 0x1a1f335beb1
TaggedPointerDemo[63235:2266690] 2\. obj isa_t = 0x41a1f335beb1

我們將地址0x41a1f335beb1轉(zhuǎn)換過(guò)后:

[圖片上傳失敗...(image-262798-1625042343368)]

我們看到這時(shí)候,我們將obj強(qiáng)引用之后,又實(shí)用了一個(gè)局部變量對(duì)其進(jìn)行引用,所以這時(shí)的引用計(jì)數(shù)應(yīng)該為2,當(dāng)然從圖中我們也可以驗(yàn)證這一點(diǎn)。

weakly_referenced

我們這次添加一個(gè)弱引用來(lái)驗(yàn)證

_weakRefObj = _obj1;
NSLog(@"3\. obj isa_t = %p", *(void **)(__bridge void*)_obj1);

控制臺(tái)輸出為

TaggedPointerDemo[63235:2266690] 3\. obj isa_t = 0x45a1f335beb1

這時(shí)候我們僅僅通過(guò)地址進(jìn)行判斷 當(dāng)添加了_obj2 = _obj1后,地址變?yōu)?code>0x61a1f335beb1與之前地址0x41a1f335beb1對(duì)比

image

上圖我們可以看到weakly_referenced標(biāo)志位被置為1.表示這個(gè)對(duì)象有被弱引用。

has_assoc

然后我們?cè)谔砑右粋€(gè)關(guān)聯(lián)屬性

NSObject *attachObj = [[NSObject alloc] init];
objc_setAssociatedObject(_obj1, "attachKey", attachObj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
NSLog(@"4\. obj isa_t = %p", *(void **)(__bridge void*)_obj1);

控制臺(tái)輸出為:

TaggedPointerDemo[63235:2266690] 4\. obj isa_t = 0x45a1f335beb3
image

從上圖中我們看到has_assoc標(biāo)志位被置為1.

總結(jié)

截止到這里,我們通過(guò)觀(guān)察NSTaggedPointer,相關(guān)標(biāo)志位我們基本了解了NSTaggedPointer是如何存儲(chǔ)數(shù)據(jù)以及標(biāo)志位的作用。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容