NSTaggedPointer和NONPOINTER_ISA

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

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

內(nèi)存分配

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

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

在程序運(yùn)行時,代碼區(qū)荆陆,常量區(qū)以及全局靜態(tài)區(qū)的大小是固定的篮愉,會變化的只有棧和堆的大小腐芍。而棧的內(nèi)存是有操作系統(tǒng)自動釋放的,我們平常說所的iOS內(nèi)存引用計數(shù)试躏,其實(shí)是就堆上的對象來說的猪勇。

如何引入tagged pointer

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

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

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

image
  • Tagged Pointer專門用來存儲小的對象员舵,例如NSNumber, NSDate, NSString膀跌。
  • Tagged Pointer指針的值不再是地址了,而是真正的值固灵。所以捅伤,實(shí)際上它不再是一個對象了,它只是一個披著對象皮的普通變量而已巫玻。所以丛忆,它的內(nèi)存并不存儲在堆中,也不需要malloc和free仍秤。
  • 在內(nèi)存讀取上有著3倍的效率熄诡,創(chuàng)建時比以前快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)字符串的長度為10個以內(nèi)時凰浮,字符串的類型都是NSTaggedPointerString類型,當(dāng)超過10個時苇本,字符串的類型才是__NSCFString

打印結(jié)果分析:

NSTaggedPointer標(biāo)志位

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

上面這個方法我們看到袜茧,判斷一個對象類型是否為NSTaggedPointerString類型實(shí)際上是講對象的地址與_OBJC_TAG_MASK進(jìn)行按位與操作,結(jié)果在跟_OBJC_TAG_MASK進(jìn)行對比,我們在看下_OBJC_TAG_MASK的定義:

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

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

那么我們在看下上面打印出的地址俺夕,所有NSTaggedPointerString地址都是0xd開頭裳凸,d轉(zhuǎn)換為二進(jìn)制1110,根據(jù)上面的結(jié)論劝贸,我們看到首位為1表示為NSTaggedPointerString類型姨谷。在這里得到驗(yàn)證。

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

對象類型

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

objc4-723之前

在objc4-723之前,我們可以通過與判斷TaggedPointer標(biāo)志位一樣根據(jù)地址來判斷践叠,而類型的標(biāo)志位就是對象地址的61-63位,比如對象地址為0xa開頭言缤,那么轉(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即字符串類型管挟。因?yàn)槟壳耙呀?jīng)無法驗(yàn)證這種情況了 所以我們不做其他類型驗(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中獲取類指針,而下標(biāo)的計算方法發(fā)是根據(jù)外部傳遞的索引tag弄捕。比如字符串 tag = 2僻孝。當(dāng)然這并不是簡單的從數(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]);

控制臺輸出為:

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

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

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

NSCFNumber

下面我們在看下NSNumber類型

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

我們在看下打印結(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)對于NSNumber斋荞,我們打印出來的數(shù)據(jù)類型均為__NSCFNumber,但是我們發(fā)現(xiàn)對于MAXFLOAT打印出的地址顯然與其他幾項(xiàng)不符荞雏,上面幾個NSNumber的地址以0xf開頭,根據(jù)字符串地址的經(jīng)驗(yàn)我們可以看出f = 1111,首位標(biāo)記位為1平酿,表示這個數(shù)據(jù)類型屬于TaggedPointer凤优。而MAXFLOAT不是。

獲取TaggedPointer的值

objc4-723之前

字符串:

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

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

數(shù)字:

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

對于數(shù)字來說從地址中也是直接讀出存儲的值蜈彼,如上圖筑辨。

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

控制臺輸出:

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

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

isa 指針(NONPOINTER_ISA)

上面我們說了,對于一個對象的存儲还绘,蘋果做了優(yōu)化楚昭,那么對于ISA指針呢?

對象的isa指針蚕甥,用來表明對象所屬的類類型哪替。

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

同時結(jié)合下圖,我們可以更清晰的了解isa指針的作用以及類對象的概念菇怀。

image

從圖中可以看出,我們所謂的isa指針晌块,最后實(shí)際上落腳于isa_t的聯(lián)合類型爱沟。那么何為聯(lián)合類型呢? 聯(lián)合類型是C語言中的一種類型匆背,是一種n選1的關(guān)系,聯(lián)合的作用在于呼伸,用更少的空間,表示了更多的可能的類型,雖然這些類型是不能夠共存的括享。比如isa_t 中包含有cls搂根,bitsstruct三個變量铃辖,它們的內(nèi)存空間是重疊的剩愧。在實(shí)際使用時,僅能夠使用它們中的一種娇斩,你把它當(dāng)做cls仁卷,就不能當(dāng)bits訪問,你把它當(dāng)bits犬第,就不能用cls來訪問锦积。

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

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

因此冠场,絕大多數(shù)情況下家浇,蘋果采用了優(yōu)化的isa策略,即碴裙,isa_t類型并不等同而Class cls, 而是struct钢悲。

struct

下面我們先來看下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)

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

標(biāo)志位說明

| 成員 | bit位 | 說明 | | --- | --- | --- | | nonpointer | 1bit | 標(biāo)志位。1(奇數(shù))表示開啟了isa優(yōu)化舔株,0(偶數(shù))表示沒有啟用isa優(yōu)化莺琳。所以,我們可以通過判斷isa是否為奇數(shù)來判斷對象是否啟用了isa優(yōu)化 | | has_assoc | 1bit | 標(biāo)志位载慈。表明對象是否有關(guān)聯(lián)對象惭等。沒有關(guān)聯(lián)對象的對象釋放的更快。 | | has_cxx_dtor | 1bit | 標(biāo)志位办铡。表明對象是否有C++或ARC析構(gòu)函數(shù)辞做。沒有析構(gòu)函數(shù)的對象釋放的更快| | shiftcls | 33bit | 類指針的非零位。 | | magic | 6bit | 固定為0x1a寡具,用于在調(diào)試時區(qū)分對象是否已經(jīng)初始化秤茅。 | | weakly_referenced | 1bit | 標(biāo)志位。用于表示該對象是否被別的對象弱引用童叠。沒有被弱引用的對象釋放的更快框喳。 | | deallocating | 1bit | 標(biāo)志位。用于表示該對象是否正在被釋放。 | | has_sidetable_rc | 1bit | 標(biāo)志位五垮。用于標(biāo)識是否當(dāng)前的引用計數(shù)過大乍惊,無法在isa中存儲,而需要借用sidetable來存儲放仗。(這種情況大多不會發(fā)生) | | extra_rc | 19bit | 對象的引用計數(shù)減1润绎。比如,一個object對象的引用計數(shù)為7匙监,則此時extra_rc的值為6凡橱。 |

從上表我們發(fā)現(xiàn),extra_rchas_sidetable_rc是和引用計數(shù)相關(guān)的標(biāo)志位亭姥,當(dāng)extra_rc 不夠用時稼钩,還會借助sidetable來存儲計數(shù)值,這時达罗,has_sidetable_rc會被標(biāo)志為1坝撑。

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

引用計數(shù)

我們先來看下面這段代碼

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

控制臺輸出結(jié)果

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

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

image

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

下面我們接著測試

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

控制臺輸出為

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

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

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

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

weakly_referenced

我們這次添加一個弱引用來驗(yàn)證

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

控制臺輸出為

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

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

image

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

has_assoc

然后我們在添加一個關(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);

控制臺輸出為:

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

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

總結(jié)

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市誉察,隨后出現(xiàn)的幾起案子与涡,更是在濱河造成了極大的恐慌,老刑警劉巖持偏,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件驼卖,死亡現(xiàn)場離奇詭異,居然都是意外死亡鸿秆,警方通過查閱死者的電腦和手機(jī)款慨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谬莹,“玉大人,你說我怎么就攤上這事「矫保” “怎么了埠戳?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蕉扮。 經(jīng)常有香客問我整胃,道長,這世上最難降的妖魔是什么喳钟? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任屁使,我火速辦了婚禮,結(jié)果婚禮上奔则,老公的妹妹穿的比我還像新娘蛮寂。我一直安慰自己,他們只是感情好易茬,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布酬蹋。 她就那樣靜靜地躺著,像睡著了一般抽莱。 火紅的嫁衣襯著肌膚如雪范抓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天食铐,我揣著相機(jī)與錄音匕垫,去河邊找鬼。 笑死虐呻,一個胖子當(dāng)著我的面吹牛象泵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播铃慷,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼单芜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了犁柜?” 一聲冷哼從身側(cè)響起洲鸠,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎馋缅,沒想到半個月后扒腕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡萤悴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年瘾腰,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片覆履。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡蹋盆,死狀恐怖费薄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情栖雾,我是刑警寧澤楞抡,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站析藕,受9級特大地震影響召廷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜账胧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一竞慢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧治泥,春花似錦筹煮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吮播,卻和暖如春变屁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背意狠。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工粟关, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人环戈。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓闷板,卻偏偏與公主長得像,于是被迫代替她去往敵國和親院塞。 傳聞我的和親對象是個殘疾皇子遮晚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

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