舉例
描述tagged pointer技術(shù)前,先做一道題:以下兩個(gè)case,會(huì)crash嗎?
case1
@property (noatomic, strong) NSString* val1;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.val1 = [NSString stringWithFormat:@"abcdefghijk"];
});
}
case2
@property (noatomic, strong) NSString* val2;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.val2 = [NSString stringWithFormat:@"abc"];
});
}
實(shí)際運(yùn)行會(huì)發(fā)現(xiàn),case1崩潰了,case2不會(huì)。case1會(huì)崩潰,報(bào)錯(cuò)BAD_ACCESS,這個(gè)原因不解釋了,不清楚的可以看我的文章:http://www.lxweimin.com/p/d0e5ec2d0d85
那為啥case2沒(méi)有崩潰了,這個(gè)就和tagged pointer的優(yōu)化有關(guān)了。經(jīng)過(guò)通過(guò)打印發(fā)現(xiàn)val1的class是 __NSCFString
,str2的class是 NSTaggedPointerString
,是兩個(gè)不同的String類型。(明明寫的NSString,為啥會(huì)不一樣,這個(gè)涉及到 類簇
的概念,后續(xù)文章會(huì)介紹)。
簡(jiǎn)介
從64位開(kāi)始,iOS引入了Tagged Pointer技術(shù),用于優(yōu)化NSNumber、NSDate、NSString等小對(duì)象的存儲(chǔ)。
- 在沒(méi)有使用Tagged Pointer之前, NSNumber等對(duì)象需要?jiǎng)討B(tài)分配內(nèi)存、維護(hù)引用計(jì)數(shù)等。
- NSNumber指針存儲(chǔ)的是堆中NSNumber對(duì)象的地址值使用Tagged Pointer之后,NSNumber指針里面存儲(chǔ)的數(shù)據(jù)變成了:Tag + Data,也就是將數(shù)據(jù)直接存儲(chǔ)在了指針中。
- 當(dāng)指針不夠存儲(chǔ)數(shù)據(jù)時(shí),才會(huì)使用動(dòng)態(tài)分配內(nèi)存的方式來(lái)存儲(chǔ)數(shù)據(jù)。
- objc_msgSend能識(shí)別Tagged Pointer,比如NSNumber的intValue方法,直接從指針提取數(shù)據(jù),節(jié)省了以前的調(diào)用開(kāi)銷
可以理解為把指針指向的內(nèi)容直接放在了指針變量的內(nèi)存地址中,因?yàn)樵?64 位環(huán)境下指針變量的大小達(dá)到了 8 字節(jié)足以容納一些長(zhǎng)度較小的內(nèi)容。于是使用了標(biāo)簽指針這種方式來(lái)優(yōu)化數(shù)據(jù)的存儲(chǔ)方式。
- 從其的引用計(jì)數(shù)可以看出,這也是一個(gè)釋放不掉的單例常量對(duì)象。當(dāng)我們使用不同的字符串對(duì)象進(jìn)行創(chuàng)建時(shí)當(dāng)內(nèi)容相同,其對(duì)象的地址也相同。在運(yùn)行時(shí)根據(jù)實(shí)際情況創(chuàng)建。
- 對(duì)于 NSString 對(duì)象來(lái)講,當(dāng)非字面值常量的數(shù)字,英文字母字符串的長(zhǎng)度小于等于 9 的時(shí)候會(huì)自動(dòng)成為 NSTaggedPointerString 類型。
如何判斷是否是Tagged Pointer
#if __arm64__
# define OBJC_SPLIT_TAGGED_POINTERS 1
#else
# define OBJC_SPLIT_TAGGED_POINTERS 0
#endif
#if (TARGET_OS_OSX || TARGET_OS_MACCATALYST) && __x86_64__
// 64-bit Mac - tag bit is LSB
# define OBJC_MSB_TAGGED_POINTERS 0
#else
// Everything else - tag bit is MSB
# define OBJC_MSB_TAGGED_POINTERS 1
#endif
#if OBJC_SPLIT_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL<<63)
#elif OBJC_MSB_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL<<63)
#else
# define _OBJC_TAG_MASK 1UL
#endif
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
答疑
- 如何判斷一個(gè)指針是否為Tagged Pointer?
- iOS平臺(tái),最高有效位是1(第64bit)
- Mac平臺(tái),最低有效位是1
- Tagged Pointer的引用計(jì)數(shù)是多少?
- 通過(guò)打印retainCount的值:2^64 - 1,可以理解成不會(huì)dealloc