一跳座、環(huán)境介紹
-
mac版本
:Mac Mojave 10.14
-
objc版本
:objc runtime 750
二兵拢、為什么要使用TaggedPointer溅话?
以前我們初始化一個(gè)對(duì)象(64位為例)缎讼,開發(fā)的代碼如下
NSNumber *number2 = [NSNumber numberWithInteger:2];
此時(shí)的內(nèi)存圖如下
可以看到我就想存一個(gè)2
用掉了24
個(gè)字節(jié)澎语,由于我們的NSNumber
和NSDate
對(duì)象的值一般不需要8
個(gè)字節(jié),4
個(gè)字節(jié)的長度2^31=2147483648
可以表達(dá)的數(shù)量已經(jīng)達(dá)到了20
多億了萍膛,為了不造成內(nèi)存的浪費(fèi)融欧,想到將指針的值(8
個(gè)字節(jié))進(jìn)行拆分,一部分表示數(shù)據(jù)卦羡,一部分用來表示是一個(gè)特殊的指針,他不執(zhí)行任何對(duì)象麦到,這就是TaggedPointer
技術(shù)绿饵,這樣指針
= Data
+ Tag
,那么我們的存一個(gè)數(shù)字只需要8
個(gè)字節(jié)就夠了瓶颠。
三拟赊、一個(gè)簡單的例子
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é)果卻是這個(gè)樣子的
number1 pointer is 0x19ec25e574ba1459
number2 pointer is 0x19ec25e574ba1759
number3 pointer is 0x19ec25e574ba1659
numberffff pointer is 0x19ec25e57445ea59
這個(gè)地址有點(diǎn)特殊,研究了一下粹淋,發(fā)現(xiàn)原來是在10_14
以后蘋果對(duì)TaggedPointer
進(jìn)行了混淆吸祟,文件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
值借杰,這是個(gè)隨機(jī)值过吻,要想獲取這個(gè)值:
方法一:通過斷點(diǎn)來獲取
通過lldb指令讀取
(lldb) p/x objc_debug_taggedpointer_obfuscator
(uintptr_t) $0 = 0x19ec25e574ba157e
方法二: 看來runtime
源碼知道objc_debug_taggedpointer_obfuscator
是個(gè)全局變量,只要在我們用的地方申明一下即可
extern uintptr_t objc_debug_taggedpointer_obfuscator;
通過NSLog
打印就可以了
NSLog(@"%lx",objc_debug_taggedpointer_obfuscator);
為了方便查看蔗衡,簡單寫了一個(gè)方法纤虽,用來解開混淆
uintptr_t _objc_decodeTaggedPointer_(id ptr) {
NSString *p = [NSString stringWithFormat:@"%ld",ptr];
return [p longLongValue] ^ objc_debug_taggedpointer_obfuscator;
}
3.2 真實(shí)的地址
NSNumber *number1 = @1;
NSNumber *number2 = @2;
NSNumber *number3 = @3;
NSNumber *numberFFFF = @(0xFFFF);
NSLog(@"number1 pointer is %p---真實(shí)地址:==0x%lx", number1,_objc_decodeTaggedPointer_(number1));
NSLog(@"number2 pointer is %p---真實(shí)地址:==0x%lx", number2,_objc_decodeTaggedPointer_(number2));
NSLog(@"number3 pointer is %p---真實(shí)地址:==0x%lx", number3,_objc_decodeTaggedPointer_(number3));
NSLog(@"numberffff pointer is %p---真實(shí)地址:==0x%lx", numberFFFF,_objc_decodeTaggedPointer_(numberFFFF));
輸出
number1 pointer is 0xfda27e12be89be71---真實(shí)地址:==0x127
number2 pointer is 0xfda27e12be89bd71---真實(shí)地址:==0x227
number3 pointer is 0xfda27e12be89bc71---真實(shí)地址:==0x327
numberffff pointer is 0xfda27e12be764071---真實(shí)地址:==0xffff27
會(huì)發(fā)現(xiàn),不管運(yùn)行多少次绞惦,都是以27
結(jié)尾逼纸,我們有理由相信,蘋果貢獻(xiàn)了1
個(gè)字節(jié)(8個(gè)bit)來標(biāo)識(shí)這是個(gè)特殊的指針济蝉,最后1個(gè)字節(jié)用來標(biāo)識(shí)杰刽,這個(gè)類指針菠发,判斷是否是TaggedPointer
不同平臺(tái)判斷的方式不一樣,但對(duì)我們理解根本不影響
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
-
mac
平臺(tái)最后一個(gè)為1
专缠; -
iPhone
和模擬器雷酪,為最高位是1
。
那么剩下的7個(gè)字節(jié)是不是都用來存放數(shù)據(jù)呢涝婉?
3.3 TaggedPointer存儲(chǔ)的數(shù)字的最大值
NSNumber *numberF13 = @(0xFFFFFFFFFFFFF);
NSNumber *numberF13_1 = @(0x1FFFFFFFFFFFFF);
NSNumber *numberF13_3 = @(0x3FFFFFFFFFFFFF);
NSNumber *numberF13_7 = @(0x7FFFFFFFFFFFFF);
NSNumber *numberF14 = @(0xFFFFFFFFFFFFFF);
NSLog(@"numberF13 pointer is %p---真實(shí)地址:==0x%lx", numberF13,_objc_decodeTaggedPointer_(numberF13));
NSLog(@"numberF13_1 pointer is %p---真實(shí)地址:==0x%lx", numberF13_1,_objc_decodeTaggedPointer_(numberF13_1));
NSLog(@"numberF13_3 pointer is %p---真實(shí)地址:==0x%lx", numberF13_3,_objc_decodeTaggedPointer_(numberF13_3));
NSLog(@"numberF13_7 pointer is %p---真實(shí)地址:==0x%lx", numberF13_7,_objc_decodeTaggedPointer_(numberF13_7));
NSLog(@"numberF14 pointer is %p---真實(shí)地址:==0x%lx", numberF14,_objc_decodeTaggedPointer_(numberF14));
輸出如下
number1 pointer is 0x20f9850034a2e631---真實(shí)地址:==0x127
number2 pointer is 0x20f9850034a2e531---真實(shí)地址:==0x227
number3 pointer is 0x20f9850034a2e431---真實(shí)地址:==0x327
numberffff pointer is 0x20f98500345d1831---真實(shí)地址:==0xffff27
numberF13 pointer is 0x2f067affcb5d1821---真實(shí)地址:==0xfffffffffffff37
numberF13_1 pointer is 0x3f067affcb5d1821---真實(shí)地址:==0x1fffffffffffff37
numberF13_3 pointer is 0x1f067affcb5d1821---真實(shí)地址:==0x3fffffffffffff37
numberF13_7 pointer is 0x5f067affcb5d1821---真實(shí)地址:==0x7fffffffffffff37
numberF14 pointer is 0x102500210
從輸出可以看出哥力,到numberF14
地址已經(jīng)是真正的oc
對(duì)象的地址了,說明有效存儲(chǔ)位置有56
位墩弯,所以TaggedPointer
所能表達(dá)的數(shù)字范圍為[0 2^65)
吩跋。
四、思考:你會(huì)如何實(shí)現(xiàn)NSString
的TaggedPointer渔工?
我們現(xiàn)在想做的事情就是如何利用指針來存儲(chǔ)我們的字符數(shù)據(jù)锌钮,而指針的大小就是8
個(gè)字節(jié),一共64
位引矩,如何利用這個(gè)64
位呢梁丘?由NSNumber
的靈感,可以使用低1
位來表示是TaggedPointer
類型旺韭,其他三位來表示具體哪個(gè)類的氛谜,對(duì)于字符串,需要存儲(chǔ)它的長度区端,再讓出4
位值漫,還剩下56
位,從而問題轉(zhuǎn)為如何利用這個(gè)56
位织盼。
計(jì)算機(jī)中存儲(chǔ)的就是0
和1
杨何,對(duì)于字符串的編碼有ASCII
和非ASCII
:
-
ASCII
是利用一個(gè)字節(jié)的大小表示字符的,一共是128
個(gè)(最高位都為0)沥邻; - 后面為了統(tǒng)一編碼出現(xiàn)了
Unicode
編碼危虱,Unicode
是規(guī)定了符號(hào)的二進(jìn)制代碼,沒有規(guī)定如何存儲(chǔ)谋国,具體如何存儲(chǔ)的槽地,后來就出現(xiàn)了,UTF-16
(字符用兩個(gè)字節(jié)或四個(gè)字節(jié)表示)芦瘾、UTF-32
(字符用四個(gè)字節(jié)表示)和UTF-8
(最常用的捌蚊,兼容了ASCII
)
對(duì)于非ASCII
:
- 如果是
UTF-32
編碼的,要想包含所有Unicode
,需要4
個(gè)字節(jié)近弟,那么最多也只能保存1個(gè)字符缅糟,沒有任何意義; - 如果是
UTF-16
編碼的祷愉,要想包含所有Unicode
窗宦,也需要4
個(gè)字節(jié)赦颇,最少也需要2
個(gè)字節(jié),按最少的算赴涵,那么56
位媒怯,也只能放3
個(gè)16
為的字符,還是很少髓窜; - 如果是
UTF-8
扇苞,如果撇開ASCII
的話,那么也是最多需要4
個(gè)字節(jié)寄纵,最少2
個(gè)字節(jié)鳖敷,56
位還是最多放3
個(gè)字節(jié)。
對(duì)于非ASCII
我們貌似沒有找到一個(gè)好的方案來存儲(chǔ)程拭,那么我們要實(shí)現(xiàn)TaggedPointer
的話定踱,是不是可以不考慮非ASCII
的情況,畢竟在實(shí)際場景恃鞋,我們用到ASCII
的場景的幾率還是比非ASCII
大的多崖媚,對(duì)于非ASCII
的還是交給開辟控件的方式。
對(duì)于ASCII
:
如果我們不考慮非ASCII
的話恤浪,那么有以下方案可以用來存儲(chǔ)數(shù)據(jù):
- 方案一: 使用
8
位存儲(chǔ)一個(gè)字符至扰,這也是默認(rèn)計(jì)算機(jī)存儲(chǔ)ASCII
的方式,由于占用一個(gè)字節(jié)资锰,那么這種方式56
位可以放7
個(gè)字節(jié); - 方案二: 使用
7
位存儲(chǔ)一個(gè)字符阶祭,ASCII
其實(shí)真正存儲(chǔ)數(shù)據(jù)的是7
位绷杜,如果是用7
位表示一個(gè)字符的話,那么最多可以放8
個(gè)字節(jié)濒募,比方案一多出一個(gè)字節(jié)鞭盟; - 方案三: 使用
6
位存儲(chǔ),有人可能想6
位怎么可能瑰剃,存儲(chǔ)ASCII
最少也得7
位啊齿诉,6
怎么存儲(chǔ),是的晌姚,直接存是不行的粤剧,但是我們可以不直接存字符,而是提供一個(gè)表格挥唠,存索引抵恋。ASCII
一共有128
個(gè),但是我們常用的根本就沒有那么多宝磨,那么我們可以不可以選出一些常用的來作為我們的可選值 弧关?6
位的話盅安,最多可以存儲(chǔ)2^ 6 = 64
個(gè)不同的字符,所以肯定是不能滿查找ASCII
集合,但是世囊,我們可以找來常見的64
個(gè)字符比如[a-zA-z0-9./_-]
,這里就有66
個(gè)了别瞭,再從這個(gè)66
個(gè)里面取出2個(gè)不常用的就可以了,這樣的話我們就可以存儲(chǔ)9
個(gè)字節(jié)了株憾; - 方案四: 使用
5
位存儲(chǔ)蝙寨,這種的話我們的查找范圍就縮小為了2^5 = 32
個(gè),也就是我們要在方案三的基礎(chǔ)上在找出更加常用的32
個(gè)字符号胚,這種方案可以存儲(chǔ)11
個(gè)字符籽慢; - 方案五: 使用
4
位存儲(chǔ),那范圍就是2^4 = 16
個(gè)猫胁,這種感覺行也行箱亿,但是范圍太小了 - 更少的想想不大可能了
下面看下蘋果是如何實(shí)現(xiàn)的
五、對(duì)于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 真實(shí)地址:0x%lx %@ %@", immutable,_objc_decodeTaggedPointer_(immutable), immutable, object_getClass(immutable));
} while(((uintptr_t)immutable & 1) == 1);
輸出届惋,這里我省去了源地址,因?yàn)檫@里打印了類的類型更直觀寫
真實(shí)地址:0x6115 a NSTaggedPointerString
真實(shí)地址:0x626125 ab NSTaggedPointerString
真實(shí)地址:0x63626135 abc NSTaggedPointerString
真實(shí)地址:0x6463626145 abcd NSTaggedPointerString
真實(shí)地址:0x656463626155 abcde NSTaggedPointerString
真實(shí)地址:0x66656463626165 abcdef NSTaggedPointerString
真實(shí)地址:0x6766656463626175 abcdefg NSTaggedPointerString
真實(shí)地址:0x22038a01169585 abcdefgh NSTaggedPointerString
真實(shí)地址:0x880e28045a54195 abcdefghi NSTaggedPointerString
真實(shí)地址:0xf9eb5f3ca3c376e0 abcdefghij __NSCFString
前面提到過最后一個(gè)字節(jié)低4
位標(biāo)志是TaggedPointer
信息菠赚,高4
位存放字符串的長度脑豹,所以最后一個(gè)數(shù)字5
是標(biāo)志位,倒數(shù)一個(gè)數(shù)字就是字符串的長度衡查。
從上面的輸出可以看出:
- 當(dāng)字符串的長度
<=7
的時(shí)候瘩欺,蘋果是直接存儲(chǔ)的字符ASCII
值,a
的ASCII
值是61
拌牲,b
是62
...俱饿。 - 當(dāng)字符串長度大于
7
的時(shí)候具體如何做的,我們通過逆向CoreFoundation.framework
來查看
5.2 hopper -> length
先來看下length
方法塌忽,看看是不是和我們猜測的一樣
翻譯一下就是
rdi = self ^ *_objc_debug_taggedpointer_obfuscator; // 解密得到真實(shí)地址
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ù)來反推如何存的排惨,
下面開始簡化該偽代碼榕酒,如果你覺得不想看,可以直接跳到第四次簡化
開始看眷蜓。
___stack_chk_guard
是為了安全加的迄损,不考慮,前面分析過((((r8 ^ rdi) & 0xe) == 0xe ? 0x1 : 0x0) << 0x3 | 0x4)
在這里等價(jià)于0x4
账磺,arg2
就是傳進(jìn)來的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ù)分析這段代碼
-
self >> 0x4 & 0xf;
其實(shí)就是字符串的length
-
self >> 0x4 >> 0x4;
其實(shí)就是字符串的開始位置 -
0xffffffffffffffc7
其實(shí)是-0x39 = -57
的補(bǔ)碼,0xffffffffffffffc7
是-0x38 = -56
的補(bǔ)碼
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;
}
-
bp
其實(shí)就是棧指針芹敌,這里使用bp
說明是通過bp
來操控椚叮空間的,然后每次循環(huán)dx
都減1氏捞,然后r8
左移6
位或者5
位碧聪,這個(gè)一般都是數(shù)組操作了,如果是5
位的話最多存11
個(gè)字節(jié)液茎,所以這里使用一個(gè)長度11
的數(shù)組buffer[11]
逞姿,dx
其實(shí)就會(huì)游離指針了我們用變量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
到底是什么呢,其實(shí)就是字符串
也就是eilotrm.apdnsIc ufkMShjTRxgC4013bDNvwyUL2O856P-B79AFKEWV_zGJ/HYX
其實(shí)程序還少了一段代碼捆等,hopper
翻譯偽代碼的時(shí)候漏掉了
0000000000060d87 cmp rbx, 0x8
0000000000060d8b jb loc_60dd1 // 當(dāng)bs < 0x8時(shí)
...
loc_60dd1:
0000000000060dd1 mov qword [rbp+var_38], r8
var_38
就是-56
其實(shí)就是將r8
的值放到[bp-56]
的內(nèi)存處滞造,由于是小端存儲(chǔ),其實(shí)就是講self>> 8
的內(nèi)容存放到對(duì)應(yīng)的內(nèi)存地址栋烤,類似于下面的代碼谒养,但是是占8
個(gè)字節(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;
}
這就顯而易見了,對(duì)于字符串蘋果的處理如下:
- 對(duì)于小于
8
個(gè)字符的明郭,使用的是8
位存儲(chǔ)买窟; -
[8,10)
的是通過6
位存儲(chǔ)的; -
[10,11]
的是通過5
位存儲(chǔ)的薯定。
根據(jù)這個(gè)結(jié)論我們再來看下5.1
的現(xiàn)象始绍,對(duì)于上面的判斷條件分別選一個(gè)代表
5.3.4.1 小于8
位代表0x66656463626165 -> abcdef
可以看出是直接存儲(chǔ)的;
5.3.4.2 [8,10)
代表:0x22038a01169585 -> abcdefgh
去掉后面的95
剩下0x22038a011695
话侄,6
位排列如下
001000 100000 001110 001010 000000 010001 011010 010101
亏推,每一個(gè)就對(duì)應(yīng)這個(gè)字符串eilotrm.apdnsIc ufkMShjTRxgC4013bDNvwyUL2O856P-B79AFKEWV_zGJ/HYX
的索引值,為了方便查找做了一個(gè)對(duì)照表
所以
001000 100000 001110 001010 000000 010001 011010 010101
分別對(duì)應(yīng)
a b c d e f g h
5.3.4.3 [10,11]
位代表abcdefghij
但是這個(gè)類是__NSCFString
并不是我們的NSTaggedPointerString
年堆,按道理說5
位的話是可以存放10
個(gè)字節(jié)的啊径簿,這是什么原因呢?
原來:不管是5
位還是6
位都是查詢的同一個(gè)字符串eilotrm.apdnsIc ufkMShjTRxgC4013bDNvwyUL2O856P-B79AFKEWV_zGJ/HYX
嘀韧,也就是上圖索引表的顏色區(qū)分,5
位里面沒有包含b
字符缠捌,但是我們的abcdefghij
有b
字符锄贷,所以不行,修改demo如下看看
NSString *str = [NSString stringWithFormat:@"acdefghijk"];
NSString *str2 = [NSString stringWithFormat:@"acdefghijkm"];
NSString *str3 = [NSString stringWithFormat:@"acdefghijkmn"];
NSLog(@"真實(shí)地址:0x%lx %@ %@", str,_objc_decodeTaggedPointer_(str), str3, object_getClass(str));
NSLog(@"真實(shí)地址:0x%lx %@ %@", str2,_objc_decodeTaggedPointer_(str2), str3, object_getClass(str2));
NSLog(@"真實(shí)地址:0x%lx %@ %@", str3,_objc_decodeTaggedPointer_(str3), str3, object_getClass(str3));
輸出
真實(shí)地址:0x10e5023aa86d2a5 acdefghijk NSTaggedPointerString
真實(shí)地址:0x21ca047550da46b5 acdefghijkm NSTaggedPointerString
真實(shí)地址:0xc64838cff22b0b46 acdefghijkmn __NSCFString
可以看到能夠支持11
個(gè)字節(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)為前提的。
最后再來看下蘋果對(duì)于非ASCII
是怎么處理的哑芹,以漢字方
(Unicode)編碼為\u65b9
,占3個(gè)字節(jié)炎辨,按道理也是可以放進(jìn)指針里面的,我們看看蘋果有沒有這樣做
NSString *notAscii_1 = [NSString stringWithFormat:@"方"];
NSLog(@"源地址:%p %@ %@", notAscii_1,notAscii_1, object_getClass(notAscii_1));
輸出
源地址:0x101907df0 方 __NSCFString
發(fā)現(xiàn)蘋果并沒有放進(jìn)指針內(nèi)聪姿,而是真實(shí)的oc
對(duì)象碴萧。
至此乙嘀,我們之前的猜測一一驗(yàn)證了。
下面總結(jié)一下TaggedPointer
的特點(diǎn)
六破喻、什么樣的字符會(huì)放進(jìn)TaggedPointer虎谢?
總結(jié)了以下表格,注意這個(gè)只適用ASCII
的情況曹质,對(duì)于非ASCII
都是使用的oc
對(duì)象婴噩。
傳入的字符任意一個(gè)不在所在行的范圍,存的地方就會(huì)發(fā)生變化羽德。
七几莽、一個(gè)和TaggedPointer相關(guān)的面試題
下面代碼會(huì)發(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é)果吧 宅静,方式一會(huì)閃退章蚣,方式二正常運(yùn)行。
分析這個(gè)道題坏为,target
的set
方法實(shí)現(xiàn)
- (void)setTarget:(NSString *)target {
if(_target != target) {
[_target release];
target = [target retain];
}
}
方式一是真正的oc
對(duì)象究驴,由于是多線程會(huì)出現(xiàn)[_target release];
被調(diào)用多次,從而閃退匀伏;
方式二不是oc
對(duì)象洒忧,而是TaggedPointer
,在release
和retain
的時(shí)候都會(huì)判斷是不是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;
}
其他的方式可以加鎖解決,就不說了够颠。