你不知道的TaggedPointer

一、環(huán)境介紹

  1. mac版本Mac Mojave 10.14
  2. objc版本objc runtime 750

二、為什么要使用TaggedPointer?

以前我們初始化一個對象(64位為例),開發(fā)的代碼如下

NSNumber *number2 = [NSNumber numberWithInteger:2];

此時的內(nèi)存圖如下

15469379882496.jpg

可以看到我就想存一個2用掉了24個字節(jié),由于我們的NSNumberNSDate對象的值一般不需要8個字節(jié),4個字節(jié)的長度2^31=2147483648可以表達的數(shù)量已經(jīng)達到了20多億了,為了不造成內(nèi)存的浪費,想到將指針的值(8個字節(jié))進行拆分,一部分表示數(shù)據(jù),一部分用來表示是一個特殊的指針,他不執(zhí)行任何對象,這就是TaggedPointer技術(shù),這樣指針 = Data + Tag,那么我們的存一個數(shù)字只需要8個字節(jié)就夠了。

三、一個簡單的例子

3.1 版本新特性

NSNumber *number1 = @1;
NSNumber *number2 = @2;
NSNumber *number3 = @3;
NSNumber *numberFFFF = @(0xFFFF);

NSLog(@"number1 pointer is %p", number1);
NSLog(@"number2 pointer is %p", number2);
NSLog(@"number3 pointer is %p", number3);
NSLog(@"numberffff pointer is %p", numberFFFF);

輸出結(jié)果卻是這個樣子的

number1 pointer is 0x19ec25e574ba1459
number2 pointer is 0x19ec25e574ba1759
number3 pointer is 0x19ec25e574ba1659
numberffff pointer is 0x19ec25e57445ea59

這個地址有點特殊,研究了一下,發(fā)現(xiàn)原來是在10_14以后蘋果對TaggedPointer進行了混淆,文件objc-runtime-new.m寫到

static void
initializeTaggedPointerObfuscator(void)
{
    if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) ||
        DisableTaggedPointerObfuscation) {
        objc_debug_taggedpointer_obfuscator = 0;
    } else {
        arc4random_buf(&objc_debug_taggedpointer_obfuscator,
                       sizeof(objc_debug_taggedpointer_obfuscator));
        objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
    }
}

混淆的代碼也很簡單,類似這種加入加密前的數(shù)據(jù)是a,加密后的數(shù)據(jù)為b,
那么:

加密b = a ^ objc_debug_taggedpointer_obfuscator,
解密: a = b ^ objc_debug_taggedpointer_obfuscator.

這里利用了異或的特性,源碼如下:

static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
{
    return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);
}

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

所以要想知道0x19ec25e574ba1459是什么意思,還是要知道objc_debug_taggedpointer_obfuscator值,這是個隨機值,要想獲取這個值:

方法一:通過斷點來獲取

15471013678173.jpg

通過lldb指令讀取

(lldb) p/x objc_debug_taggedpointer_obfuscator
(uintptr_t) $0 = 0x19ec25e574ba157e

方法二: 看來runtime源碼知道objc_debug_taggedpointer_obfuscator是個全局變量,只要在我們用的地方申明一下即可

extern uintptr_t objc_debug_taggedpointer_obfuscator;

通過NSLog打印就可以了

NSLog(@"%lx",objc_debug_taggedpointer_obfuscator);

為了方便查看,簡單寫了一個方法,用來解開混淆

uintptr_t _objc_decodeTaggedPointer_(id  ptr) {
    NSString *p = [NSString stringWithFormat:@"%ld",ptr];
    return [p longLongValue] ^ objc_debug_taggedpointer_obfuscator;
}

3.2 真實的地址

NSNumber *number1 = @1;
NSNumber *number2 = @2;
NSNumber *number3 = @3;
NSNumber *numberFFFF = @(0xFFFF);

NSLog(@"number1 pointer is %p---真實地址:==0x%lx", number1,_objc_decodeTaggedPointer_(number1));
NSLog(@"number2 pointer is %p---真實地址:==0x%lx", number2,_objc_decodeTaggedPointer_(number2));
NSLog(@"number3 pointer is %p---真實地址:==0x%lx", number3,_objc_decodeTaggedPointer_(number3));
NSLog(@"numberffff pointer is %p---真實地址:==0x%lx", numberFFFF,_objc_decodeTaggedPointer_(numberFFFF));

輸出

number1 pointer is 0xfda27e12be89be71---真實地址:==0x127
number2 pointer is 0xfda27e12be89bd71---真實地址:==0x227
number3 pointer is 0xfda27e12be89bc71---真實地址:==0x327
numberffff pointer is 0xfda27e12be764071---真實地址:==0xffff27

會發(fā)現(xiàn),不管運行多少次,都是以27結(jié)尾,我們有理由相信,蘋果貢獻了1個字節(jié)(8個bit)來標(biāo)識這是個特殊的指針,最后1個字節(jié)用來標(biāo)識,這個類指針,判斷是否是TaggedPointer不同平臺判斷的方式不一樣,但對我們理解根本不影響

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

  1. mac平臺最后一個為1;
  2. iPhone和模擬器,為最高位是1。

那么剩下的7個字節(jié)是不是都用來存放數(shù)據(jù)呢?

3.3 TaggedPointer存儲的數(shù)字的最大值

NSNumber *numberF13   = @(0xFFFFFFFFFFFFF);
NSNumber *numberF13_1 = @(0x1FFFFFFFFFFFFF);
NSNumber *numberF13_3 = @(0x3FFFFFFFFFFFFF);
NSNumber *numberF13_7 = @(0x7FFFFFFFFFFFFF);
NSNumber *numberF14   = @(0xFFFFFFFFFFFFFF);

NSLog(@"numberF13 pointer is %p---真實地址:==0x%lx", numberF13,_objc_decodeTaggedPointer_(numberF13));
NSLog(@"numberF13_1 pointer is %p---真實地址:==0x%lx", numberF13_1,_objc_decodeTaggedPointer_(numberF13_1));
NSLog(@"numberF13_3 pointer is %p---真實地址:==0x%lx", numberF13_3,_objc_decodeTaggedPointer_(numberF13_3));
NSLog(@"numberF13_7 pointer is %p---真實地址:==0x%lx", numberF13_7,_objc_decodeTaggedPointer_(numberF13_7));
   
NSLog(@"numberF14 pointer is %p---真實地址:==0x%lx", numberF14,_objc_decodeTaggedPointer_(numberF14));

輸出如下

number1 pointer is 0x20f9850034a2e631---真實地址:==0x127
number2 pointer is 0x20f9850034a2e531---真實地址:==0x227
number3 pointer is 0x20f9850034a2e431---真實地址:==0x327
numberffff pointer is 0x20f98500345d1831---真實地址:==0xffff27
numberF13 pointer is 0x2f067affcb5d1821---真實地址:==0xfffffffffffff37
numberF13_1 pointer is 0x3f067affcb5d1821---真實地址:==0x1fffffffffffff37
numberF13_3 pointer is 0x1f067affcb5d1821---真實地址:==0x3fffffffffffff37
numberF13_7 pointer is 0x5f067affcb5d1821---真實地址:==0x7fffffffffffff37

numberF14 pointer is 0x102500210

從輸出可以看出,到numberF14地址已經(jīng)是真正的oc對象的地址了,說明有效存儲位置有56位,所以TaggedPointer所能表達的數(shù)字范圍為[0 2^65)

四、思考:你會如何實現(xiàn)NSString的TaggedPointer?

我們現(xiàn)在想做的事情就是如何利用指針來存儲我們的字符數(shù)據(jù),而指針的大小就是8個字節(jié),一共64位,如何利用這個64位呢?由NSNumber的靈感,可以使用低1位來表示是TaggedPointer類型,其他三位來表示具體哪個類的,對于字符串,需要存儲它的長度,再讓出4位,還剩下56位,從而問題轉(zhuǎn)為如何利用這個56位。

計算機中存儲的就是01,對于字符串的編碼有ASCII非ASCII

  1. ASCII是利用一個字節(jié)的大小表示字符的,一共是128個(最高位都為0);
  2. 后面為了統(tǒng)一編碼出現(xiàn)了Unicode編碼,Unicode是規(guī)定了符號的二進制代碼,沒有規(guī)定如何存儲,具體如何存儲的,后來就出現(xiàn)了,UTF-16(字符用兩個字節(jié)或四個字節(jié)表示)、UTF-32(字符用四個字節(jié)表示)和UTF-8(最常用的,兼容了ASCII

對于非ASCII

  1. 如果是UTF-32編碼的,要想包含所有Unicode,需要4個字節(jié),那么最多也只能保存1個字符,沒有任何意義;
  2. 如果是 UTF-16編碼的,要想包含所有Unicode,也需要4個字節(jié),最少也需要2個字節(jié),按最少的算,那么56位,也只能放316為的字符,還是很少;
  3. 如果是UTF-8,如果撇開ASCII的話,那么也是最多需要4個字節(jié),最少2個字節(jié),56位還是最多放3個字節(jié)。

對于非ASCII我們貌似沒有找到一個好的方案來存儲,那么我們要實現(xiàn)TaggedPointer的話,是不是可以不考慮非ASCII的情況,畢竟在實際場景,我們用到ASCII的場景的幾率還是比非ASCII大的多,對于非ASCII的還是交給開辟控件的方式。

對于ASCII:

如果我們不考慮非ASCII的話,那么有以下方案可以用來存儲數(shù)據(jù):

  1. 方案一: 使用8位存儲一個字符,這也是默認計算機存儲ASCII的方式,由于占用一個字節(jié),那么這種方式56位可以放7個字節(jié);
  2. 方案二: 使用7位存儲一個字符,ASCII其實真正存儲數(shù)據(jù)的是7位,如果是用7位表示一個字符的話,那么最多可以放8個字節(jié),比方案一多出一個字節(jié);
  3. 方案三: 使用6位存儲,有人可能想6位怎么可能,存儲ASCII最少也得7位啊,6怎么存儲,是的,直接存是不行的,但是我們可以不直接存字符,而是提供一個表格,存索引。ASCII一共有128個,但是我們常用的根本就沒有那么多,那么我們可以不可以選出一些常用的來作為我們的可選值 ? 6位的話,最多可以存儲2^ 6 = 64個不同的字符,所以肯定是不能滿查找ASCII集合,但是,我們可以找來常見的64個字符比如[a-zA-z0-9./_-],這里就有66個了,再從這個66個里面取出2個不常用的就可以了,這樣的話我們就可以存儲9個字節(jié)了;
  4. 方案四: 使用5位存儲,這種的話我們的查找范圍就縮小為了2^5 = 32個,也就是我們要在方案三的基礎(chǔ)上在找出更加常用的32個字符,這種方案可以存儲11個字符;
  5. 方案五: 使用4位存儲,那范圍就是2^4 = 16個,這種感覺行也行,但是范圍太小了
  6. 更少的想想不大可能了

下面看下蘋果是如何實現(xiàn)的

五、對于NSString蘋果是如何使用TaggedPointer的?

5.1 現(xiàn)象

添加測試如下測試代碼

NSMutableString *imutable = [NSMutableString string];
NSString *immutable;
char c = 'a';
do {
 [imutable appendFormat: @"%c", c++];
 immutable = [imutable copy];
 NSLog(@"源地址:%p 真實地址:0x%lx %@ %@", immutable,_objc_decodeTaggedPointer_(immutable), immutable, object_getClass(immutable));
} while(((uintptr_t)immutable & 1) == 1);

輸出,這里我省去了源地址,因為這里打印了類的類型更直觀寫

真實地址:0x6115                 a               NSTaggedPointerString
真實地址:0x626125               ab              NSTaggedPointerString
真實地址:0x63626135             abc             NSTaggedPointerString
真實地址:0x6463626145           abcd            NSTaggedPointerString
真實地址:0x656463626155         abcde           NSTaggedPointerString
真實地址:0x66656463626165       abcdef          NSTaggedPointerString
真實地址:0x6766656463626175     abcdefg         NSTaggedPointerString
真實地址:0x22038a01169585       abcdefgh        NSTaggedPointerString
真實地址:0x880e28045a54195      abcdefghi       NSTaggedPointerString
真實地址:0xf9eb5f3ca3c376e0     abcdefghij      __NSCFString

前面提到過最后一個字節(jié)低4位標(biāo)志是TaggedPointer信息,高4位存放字符串的長度,所以最后一個數(shù)字5是標(biāo)志位,倒數(shù)一個數(shù)字就是字符串的長度。

從上面的輸出可以看出:

  1. 當(dāng)字符串的長度<=7的時候,蘋果是直接存儲的字符ASCII值,aASCII值是61,b62...。
  2. 當(dāng)字符串長度大于7的時候具體如何做的,我們通過逆向CoreFoundation.framework來查看

5.2 hopper -> length

先來看下length方法,看看是不是和我們猜測的一樣

15471101516030.jpg

翻譯一下就是


rdi = self ^ *_objc_debug_taggedpointer_obfuscator; // 解密得到真實地址

if ((di & 14 ) == 14) { 也就是//0b1110 我們的字符串的是5(0x0101),所以走else了
       rax = (di >> 11) & 0xf;
} else {
       rax =(di >>  4 ) & 0xf;
}  


再簡化一下就是

======

rax = (di >>  4 ) & 0xf

已經(jīng)很顯然了,就是拿低1字節(jié)的高4位的值,證明了我們的猜想。

5.3 hopper -> characterAtIndex

蘋果是如何將字符轉(zhuǎn)成NSTaggedPointerString的,不是很好查,但是我們可以反向思考,通過取數(shù)據(jù)來反推如何存的,

15471113839214.jpg

下面開始簡化該偽代碼,如果你覺得不想看,可以直接跳到第四次簡化開始看。

___stack_chk_guard是為了安全加的,不考慮,前面分析過((((r8 ^ rdi) & 0xe) == 0xe ? 0x1 : 0x0) << 0x3 | 0x4)在這里等價于0x4arg2就是傳進來的index

5.3.1 第一次簡化

unsigned short -[NSTaggedPointerString characterAtIndex:](void * self, void * _cmd, unsigned long long arg2) {
    r12 = index;
    rbx = self >>  0x4 & 0xf;
    r8 = self >> 0x4 >> 0x4;
    if (rbx >= 0x8) {
            rdx = rbx;
            if (rbx < 0xa) {
                    do {
                            *(int8_t *)(rbp + rdx + 0xffffffffffffffc7) = *(int8_t *)((r8 & 0x3f) + _sixBitToCharLookup);
                            rdx = rdx - 0x1;
                            r8 = r8 >> 0x6;
                    } while (rdx != 0x0);
            }
            else {
                    do {
                            *(int8_t *)(rbp + rdx + 0xffffffffffffffc7) = *(int8_t *)((r8 & 0x1f) + _sixBitToCharLookup);
                            rdx = rdx - 0x1;
                            r8 = r8 >> 0x5;
                    } while (rdx != 0x0);
            }
    }

    rax = *(int8_t *)(rbp + r12 + 0xffffffffffffffc8) & 0xff;
    
    return rax;
}

繼續(xù)分析這段代碼

  1. self >> 0x4 & 0xf;其實就是字符串的length
  2. self >> 0x4 >> 0x4;其實就是字符串的開始位置
  3. 0xffffffffffffffc7其實是-0x39 = -57的補碼,0xffffffffffffffc7-0x38 = -56的補碼

5.3.2 第二次簡化

unsigned short -[NSTaggedPointerString characterAtIndex:](void * self, void * _cmd, unsigned long long arg2) {
    rbx = length;
    r8 = self >> 0x8;
    if (rbx >= 0x8) {
            if (length < 0xa) {
                    do {
                            *(int8_t *)(rbp - 57 + rdx) = *(int8_t *)((r8 & 0x3f) + _sixBitToCharLookup);
                            rdx = rdx - 0x1;
                            r8 = r8 >> 0x6;
                    } while (rdx != 0x0);
            }
            else {
                    do {
                            *(int8_t *)(rbp - 57 + rdx) = *(int8_t *)((r8 & 0x1f) + _sixBitToCharLookup);
                            rdx = rdx - 0x1;
                            r8 = r8 >> 0x5;
                    } while (rdx != 0x0);
            }
    }

    rax = *(int8_t *)(rbp - 56 + index) & 0xff;
    
    return rax;
}


  1. bp其實就是棧指針,這里使用bp說明是通過bp來操控??臻g的,然后每次循環(huán)dx都減1,然后r8左移6位或者5位,這個一般都是數(shù)組操作了,如果是5位的話最多存11個字節(jié),所以這里使用一個長度11的數(shù)組buffer[11],dx其實就會游離指針了我們用變量cursor表示

5.3.3 第三次簡化

unsigned short -[NSTaggedPointerString characterAtIndex:](void * self, void * _cmd, unsigned long long arg2) {

    int8_t  buffer[11];
    r8 = self >> 0x8;
    
    if (length >= 0x8) {
            base = rbp - 57;
            cursor = length;
            if (length < 0xa) {
                    do {
                            buffer[base + cursor ] = *(int8_t *)((r8 & 0x3f) + _sixBitToCharLookup)
                            cursor = cursor - 0x1;
                            r8 = r8 >> 0x6;
                    } while (rdx != 0x0);
            }
            else {
                    do {
                            buffer[base + cursor ] = *(int8_t *)((r8 & 0x1f) + _sixBitToCharLookup);
                            cursor = cursor - 0x1;
                            r8 = r8 >> 0x5;
                    } while (rdx != 0x0);
            }
    }

    rax = *(int8_t *)(rbp - 56 + index) & 0xff;
    
    return rax;
}


_sixBitToCharLookup到底是什么呢,其實就是字符串

15471761831012.jpg

也就是eilotrm.apdnsIc ufkMShjTRxgC4013bDNvwyUL2O856P-B79AFKEWV_zGJ/HYX

其實程序還少了一段代碼,hopper翻譯偽代碼的時候漏掉了

0000000000060d87         cmp        rbx, 0x8
0000000000060d8b         jb         loc_60dd1 // 當(dāng)bs < 0x8時
...
loc_60dd1:
0000000000060dd1         mov        qword [rbp+var_38], r8   

var_38就是-56

15471166175011.jpg

其實就是將r8的值放到[bp-56]的內(nèi)存處,由于是小端存儲,其實就是講self>> 8的內(nèi)容存放到對應(yīng)的內(nèi)存地址,類似于下面的代碼,但是是占8個字節(jié)的

 *(uint64_t *)buffer = self >> 8;

5.3.4 第四次簡化

unsigned short -[NSTaggedPointerString characterAtIndex:](void * self, void * _cmd, unsigned long long arg2) {

    int8_t  buffer[11];
    r8 = self >> 0x8;
    
    if (length >= 0x8) {
       base = rbp - 57;
       cursor = length;
       _sixBitToCharLookup = 'eilotrm.apdnsIc ufkMShjTRxgC4013bDNvwyUL2O856P-B79AFKEWV_zGJ/HYX';
       if (length < 0xa) {
               do {
                   buffer[base + cursor ] = _sixBitToCharLookup[r8 & 0x3f]
                   cursor = cursor - 0x1;
                   r8 = r8 >> 0x6;
               } while (rdx != 0x0);
       } else {
               do {
                   buffer[base + cursor ] = _sixBitToCharLookup[r8 & 0x1f];
                   cursor = cursor - 0x1;
                   r8 = r8 >> 0x5;
               } while (rdx != 0x0);
       }
    } else {
        *(uint64_t *)buffer = self >> 8;
    }

    rax = *(int8_t *)(rbp - 56 + index) & 0xff;
    
    return rax;
}


這就顯而易見了,對于字符串蘋果的處理如下:

  1. 對于小于8個字符的,使用的是8位存儲;
  2. [8,10)的是通過6位存儲的;
  3. [10,11]的是通過5位存儲的。

根據(jù)這個結(jié)論我們再來看下5.1的現(xiàn)象,對于上面的判斷條件分別選一個代表

5.3.4.1 小于8位代表0x66656463626165 -> abcdef

可以看出是直接存儲的;

5.3.4.2 [8,10)代表:0x22038a01169585 -> abcdefgh

去掉后面的95剩下0x22038a011695,6位排列如下

001000 100000 001110 001010 000000 010001 011010 010101,每一個就對應(yīng)這個字符串eilotrm.apdnsIc ufkMShjTRxgC4013bDNvwyUL2O856P-B79AFKEWV_zGJ/HYX的索引值,為了方便查找做了一個對照表

15469350941106.jpg

所以

001000 100000 001110 001010 000000 010001 011010 010101

分別對應(yīng)

a b c d e f g h

5.3.4.3 [10,11]位代表abcdefghij

但是這個類是__NSCFString并不是我們的NSTaggedPointerString,按道理說5位的話是可以存放10個字節(jié)的啊,這是什么原因呢?

原來:不管是5位還是6位都是查詢的同一個字符串eilotrm.apdnsIc ufkMShjTRxgC4013bDNvwyUL2O856P-B79AFKEWV_zGJ/HYX,也就是上圖索引表的顏色區(qū)分,5位里面沒有包含b字符,但是我們的abcdefghijb字符,所以不行,修改demo如下看看


NSString *str = [NSString stringWithFormat:@"acdefghijk"];
NSString *str2 = [NSString stringWithFormat:@"acdefghijkm"];
NSString *str3 = [NSString stringWithFormat:@"acdefghijkmn"];
    
NSLog(@"真實地址:0x%lx %@ %@", str,_objc_decodeTaggedPointer_(str), str3, object_getClass(str));
NSLog(@"真實地址:0x%lx %@ %@", str2,_objc_decodeTaggedPointer_(str2), str3, object_getClass(str2));
NSLog(@"真實地址:0x%lx %@ %@", str3,_objc_decodeTaggedPointer_(str3), str3, object_getClass(str3));

輸出

真實地址:0x10e5023aa86d2a5 acdefghijk NSTaggedPointerString
真實地址:0x21ca047550da46b5 acdefghijkm NSTaggedPointerString
真實地址:0xc64838cff22b0b46 acdefghijkmn __NSCFString

可以看到能夠支持11個字節(jié)了,0x10e5023aa86d2a5去掉0x10e5023aa86d2,按5位排列下看看

01000 01110 01010 00000 10001 11010 10101 00001 10110 10010

也就是 a c d e f g h i j k

所以我們可以得出能夠存[10,11]位字符是以所存字符在eilotrm.apdnsIc ufkMShjTRxgC4013內(nèi)為前提的。

最后再來看下蘋果對于非ASCII是怎么處理的,以漢字(Unicode)編碼為\u65b9,占3個字節(jié),按道理也是可以放進指針里面的,我們看看蘋果有沒有這樣做

NSString *notAscii_1 = [NSString stringWithFormat:@"方"];

NSLog(@"源地址:%p  %@ %@", notAscii_1,notAscii_1, object_getClass(notAscii_1));
        

輸出

源地址:0x101907df0  方 __NSCFString

發(fā)現(xiàn)蘋果并沒有放進指針內(nèi),而是真實的oc對象。

至此,我們之前的猜測一一驗證了。

下面總結(jié)一下TaggedPointer的特點

六、什么樣的字符會放進TaggedPointer?

總結(jié)了以下表格,注意這個只適用ASCII的情況,對于非ASCII都是使用的oc對象。

15471895085356.jpg

傳入的字符任意一個不在所在行的范圍,存的地方就會發(fā)生變化。

七、一個和TaggedPointer相關(guān)的面試題

下面代碼會發(fā)生什么問題?

@property (nonatomic, copy) NSString *target;
//.... dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);

// 方式一
for (int i = 0; i < 1000000 ; i++) {
    dispatch_async(queue, ^{
        self.target = [NSString stringWithFormat:@"ksddkjalkjd%d",I];
    });
}



//.... dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);
// 方式二
for (int i = 0; i < 1000000 ; i++) {
    dispatch_async(queue, ^{
        self.target = [NSString stringWithFormat:@"ksddkjalkj"];
    });
}


先說下結(jié)果吧 ,方式一會閃退,方式二正常運行。

分析這個道題,targetset方法實現(xiàn)

- (void)setTarget:(NSString *)target {
    if(_target != target) {
        [_target release];
        target = [target retain];
    }
}

方式一是真正的oc對象,由于是多線程會出現(xiàn)[_target release];被調(diào)用多次,從而閃退;
方式二不是oc對象,而是TaggedPointer,在releaseretain的時候都會判斷是不是TaggedPointer


objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
    if (isTaggedPointer()) return false;

    bool sideTableLocked = false;
    ...
}


ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    if (isTaggedPointer()) return (id)this;

}

其他的方式可以加鎖解決,就不說了。

感謝

  1. http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
  2. https://mikeash.com/pyblog/friday-qa-2015-07-31-tagged-pointer-strings.html
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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