本文源自本人的學(xué)習(xí)記錄整理與理解跨嘉,其中參考閱讀了部分優(yōu)秀的博客和書籍兆览,盡量以通俗簡(jiǎn)單的語(yǔ)句轉(zhuǎn)述勃黍。引用到的地方如有遺漏或未能一一列舉原文出處還望見(jiàn)諒與指出踱蛀,另文章內(nèi)容如有不妥之處還望指教,萬(wàn)分感謝 馋辈!
關(guān)于TaggedPointer
- 從64bit開(kāi)始抚芦,IOS引入了
Tagged Pointer
技術(shù);用于優(yōu)化NSNumber
、NSDate
叉抡、NSString
等小對(duì)象(OC對(duì)象
)的存儲(chǔ)尔崔;
Tagged Pointer指針的值不再是地址了,而是真正的值褥民。所以季春,實(shí)際上它不再是一個(gè)對(duì)象了,它只是一個(gè)披著對(duì)象皮的普通變量而已消返。所以载弄,它的內(nèi)存并不存儲(chǔ)在堆中,也不需要malloc和free
在內(nèi)存讀取上有著3倍的效率撵颊,創(chuàng)建時(shí)比以前快106倍
NSString的內(nèi)存管理主要分為3種
__NSCFConstantString
:字符串常量宇攻,是一種編譯時(shí)常量,retainCount值很大倡勇,對(duì)其操作逞刷,不會(huì)引起引用計(jì)數(shù)變化,存儲(chǔ)在字符串常量區(qū)
__NSCFString
:是在運(yùn)行時(shí)創(chuàng)建的NSString子類译隘,創(chuàng)建后引用計(jì)數(shù)會(huì)加1亲桥,存儲(chǔ)在堆上
NSTaggedPointerString
:標(biāo)簽指針,是蘋果在64位環(huán)境下對(duì)NSString固耘、NSNumber等對(duì)象做的優(yōu)化。對(duì)于NSString對(duì)象來(lái)說(shuō)
比如:NSString 就會(huì)被優(yōu)化成NSTaggedPointerString類型
注意:
Tagged Pointer
技術(shù)不會(huì)
運(yùn)用在可變
類型對(duì)象词身,比如:NSMutableString
在沒(méi)有使用
Tagged Pointer
之前厅目,NSNumber等對(duì)象需要?jiǎng)討B(tài)分配內(nèi)存、維護(hù)引用計(jì)數(shù)等法严,NSNumber指針存儲(chǔ)的是堆空間NSNumber對(duì)象的地址值
使用Tagged Pointer 技術(shù)之后损敷,NSNumber指針里面存儲(chǔ)的數(shù)據(jù)變成了:Tag+Data (Tag 是一個(gè)標(biāo)記信息:數(shù)據(jù)類型),也就是NSNumber指針存儲(chǔ)的是NSNumber
對(duì)象的數(shù)據(jù)
當(dāng)對(duì)象指針地址的最高有效位是1,則該指針為
Tagged Pointer
比如:十六進(jìn)制0x7 轉(zhuǎn)換成二進(jìn)制0b1110深啤;十六進(jìn)制0x6 轉(zhuǎn)換成二進(jìn)制0b1100拗馒;最左邊數(shù)字是最高有效位 ;總體來(lái)說(shuō)NSNumber
溯街、NSDate
诱桂、NSString
內(nèi)存地址十六進(jìn)制最后一位是只要不是0,就是Tagged Pointer指針當(dāng)指針不夠存儲(chǔ)這些數(shù)據(jù)是呈昔,才會(huì)使用動(dòng)態(tài)分配內(nèi)存的方式來(lái)存儲(chǔ)數(shù)據(jù)挥等;即將之前的直接動(dòng)態(tài)分配內(nèi)存,改變?yōu)橄劝褦?shù)據(jù)加上tag值存儲(chǔ)在指針中堤尾,如果不夠再動(dòng)態(tài)分配
Tagged Pointer
指針的賦值過(guò)程是不會(huì)走setter方法和getter方法的Tagged Pointer
是由編譯器來(lái)實(shí)現(xiàn)肝劲,開(kāi)發(fā)者全程無(wú)感
示例代碼:
int main(int argc, char * argv[]) {
@autoreleasepool {
NSNumber *number = @10;
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
*圖解:
- 十進(jìn)制的10 轉(zhuǎn)化成十六進(jìn)制就是 0xa
優(yōu)點(diǎn):
- Tagged Pointer技術(shù)節(jié)省了內(nèi)存空間開(kāi)銷,本來(lái)正常的局部變量的NSNumber指針占8字節(jié),堆空間動(dòng)態(tài)分配內(nèi)存至少16字節(jié)辞槐;這樣看來(lái)需要在兩塊內(nèi)存區(qū)域分配共24字節(jié)掷漱;但是Tagged Pointer技術(shù)可以實(shí)現(xiàn)只在棧空間分配8字節(jié)就可以榄檬。
- 在方法調(diào)用方面也變的更簡(jiǎn)單切威,objc_msgSend能夠識(shí)別Tagged Pointer,比如NSNumber的intValue方法丙号,直接從指針提取數(shù)據(jù)先朦,節(jié)省了以前的調(diào)用開(kāi)銷
示例代碼二:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSNumber *number1 = @10;
NSNumber *number2 = @11;
NSNumber *number3 = @12;
NSLog(@"\n %p \n %p \n %p", number1, number2, number3);
}
疑問(wèn):如何判斷一個(gè)指針是否是Tagged Pointer ?
- iOS 平臺(tái),最高有效位是1 (第64bit)
- Mac平臺(tái)犬缨,最低有效位是1
- 這里的有效位是二進(jìn)制數(shù)
- 通過(guò)retainCount判斷喳魏,如果retainCount是一個(gè)非常大的值那么就是Tagged Pointer類型,本質(zhì)上這個(gè)值就是該指針地址轉(zhuǎn)成的十進(jìn)制數(shù) (注意:這里十進(jìn)制數(shù)值是通過(guò) uintptr_t retain_count = (uintptr_t)this; 得到的怀薛,不是直接調(diào)用retainCount方法)
還有一個(gè)笨辦法 刺彩!
讓指針對(duì)象調(diào)用一個(gè)在編譯階段不報(bào)錯(cuò),運(yùn)行時(shí)就報(bào)錯(cuò)的方法枝恋;看崩潰日志......
比如:
如何區(qū)分內(nèi)存地址是堆控件還是棿淳螅空間 ?
十六進(jìn)制看最后一位是否是0焚碌,如果是0那就是堆空間地址畦攘;否則就是棧空間地址十电;
NSTaggedPointerString
Tagged Pointer
是一個(gè)很棒的技術(shù)知押,能把它運(yùn)用在字符串上很不尋常。顯然Apple花了很多心思在這上面鹃骂,他們必須要看到一個(gè)顯著的好處台盯。看他們?nèi)绾伟堰@些技術(shù)融合在一起畏线,看他們?nèi)绾卧诜浅S邢薜目臻g里面盡可能的存儲(chǔ)信息静盅,實(shí)在有趣。
做了什么 寝殴?
利用有限的空間蒿叠,把小數(shù)據(jù)存儲(chǔ)在指針內(nèi)部
有什么用 ?
- 提高訪問(wèn)速度杯矩,對(duì)性能提升很有幫助
- 節(jié)省了內(nèi)存空間
- 簡(jiǎn)單來(lái)說(shuō):體量更小栈虚,??跑的更快 !
用什么編碼方式實(shí)現(xiàn) 史隆?
NSTaggedPointerString
的存儲(chǔ)有三種編碼方式:ASCII碼
魂务,六位編碼
,五位編碼
。
ASCII碼:除去第一位和最后一位粘姜,用8位的ascll碼的話最多可以存儲(chǔ)7個(gè)字符鬓照。字符串?dāng)?shù)目0~7
之間
六位編碼: 六位二進(jìn)制編碼, (144)/6=9.333… ;最多存儲(chǔ)9位字符孤紧。字符數(shù)目在8~9使用
五位編碼:五位二進(jìn)制編碼豺裆,(144)/5 = 11.2;最多存儲(chǔ)11位字符号显。字符數(shù)目在10~11使用
神秘的表:
完整的六位編碼表是:
`eilotrm.apdnsIc ufkMShjTRxgC4013bDNvwyUL2O856P-B79AFKEWV_zGJ/HYX`
一個(gè)顯然的問(wèn)題是:為什么這個(gè)指令這么奇怪?
因?yàn)檫@個(gè)表同時(shí)用于六位編碼和五位編碼臭猜,它不完全按字母順序排列是有意義的。最常使用的字符應(yīng)該是上半部分押蚤,而較少使用的字符應(yīng)該在下半部分蔑歌。這可以確保盡可能多的長(zhǎng)字符串可以使用五位編碼。
表中前幾個(gè)字母與字母出現(xiàn)在英語(yǔ)里的頻率相似揽碘。最常見(jiàn)的英文字母是E次屠,然后是T,A雳刺,O劫灶,I,N掖桦,S本昏。E作為表的開(kāi)頭是正確的,其他的則靠近開(kāi)頭滞详。表似乎是按使用頻率排序凛俱。與英語(yǔ)的差異可能是因?yàn)镃ocoa APP中的短字符串并不是一個(gè)從英語(yǔ)散文中隨機(jī)選擇的單詞,而是更專業(yè)的語(yǔ)言料饥。
但這并不是一成不變的,apple
會(huì)持續(xù)更新并統(tǒng)計(jì)字母使用頻率朱监,系統(tǒng)每次升級(jí)都可能不一樣
岸啡,當(dāng)前第一位是字母e,之后是i,l赫编,o巡蘸,t…;這兩種編碼是從左向右的擂送;
根據(jù)編碼位數(shù)我們顯然也能推測(cè)出并不是所有字符都可以進(jìn)行ASCII
或者六位五位編碼
的悦荒,當(dāng)出現(xiàn)這樣不能編碼(搜狗emoji
、顏文字
嘹吨、符號(hào)表情
)的時(shí)候搬味,系統(tǒng)也就不會(huì)使用NSTaggedPointerString類。
舉例證明:
# define _OBJC_TAG_MASK (1UL<<63)
static inline bool objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
- (void)test10 {
NSString *string1 = [NSString stringWithFormat:@"????"];
NSString *string2 = [NSString stringWithFormat:@"* ̄? ̄*)"];
NSString *string3 = [NSString stringWithFormat:@"222"];
BOOL isTagP1 = objc_isTaggedPointer((__bridge const void * _Nullable)(string1));
BOOL isTagP2 = objc_isTaggedPointer((__bridge const void * _Nullable)(string2));
BOOL isTagP3 = objc_isTaggedPointer((__bridge const void * _Nullable)(string3));
if (isTagP1) {
NSLog(@"??? string1是TaggedPointer指針");
}else {
NSLog(@"??? string1不是TaggedPointer指針");
}
if (isTagP2) {
NSLog(@"??? string2是TaggedPointer指針");
}else {
NSLog(@"??? string2不是TaggedPointer指針");
}
if (isTagP3) {
NSLog(@"??? string3是TaggedPointer指針");
}else {
NSLog(@"??? string3不是TaggedPointer指針");
}
}
??? string1不是TaggedPointer指針
??? string2不是TaggedPointer指針
??? string3是TaggedPointer指針
### retainCount 的底層實(shí)現(xiàn)
inline uintptr_t objc_object::rootRetainCount() {
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED);
if (bits.nonpointer) {
uintptr_t rc = bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
開(kāi)發(fā)中那些字符串符合TaggedPointer呢 ?
- 對(duì)于以前的@""符號(hào)創(chuàng)建的字符串還是常量字符串碰纬,這個(gè)沒(méi)有改變萍聊,但是采用
stringWithFormat:
、stringWithUTF8String:
悦析、stringWithString:
等NSString方法的創(chuàng)建字符串對(duì)象則有了區(qū)別寿桨。 - 用這些方法創(chuàng)建的字符串字符數(shù)目在
11位之內(nèi)
的會(huì)被存儲(chǔ)在TaggedPointer指針內(nèi)部
參考優(yōu)秀文章: