舉例
描述tagged pointer技術(shù)前,先做一道題:以下兩個case,會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"];
});
}
實際運行會發(fā)現(xiàn),case1崩潰了,case2不會。case1會崩潰,報錯BAD_ACCESS,這個原因不解釋了,不清楚的可以看我的文章:http://m.itdecent.cn/p/d0e5ec2d0d85
那為啥case2沒有崩潰了,這個就和tagged pointer的優(yōu)化有關(guān)了。經(jīng)過通過打印發(fā)現(xiàn)val1的class是 __NSCFString,str2的class是 NSTaggedPointerString,是兩個不同的String類型。(明明寫的NSString,為啥會不一樣,這個涉及到 類簇的概念,后續(xù)文章會介紹)。
簡介
從64位開始,iOS引入了Tagged Pointer技術(shù),用于優(yōu)化NSNumber、NSDate、NSString等小對象的存儲。
- 在沒有使用Tagged Pointer之前, NSNumber等對象需要動態(tài)分配內(nèi)存、維護引用計數(shù)等。
- NSNumber指針存儲的是堆中NSNumber對象的地址值使用Tagged Pointer之后,NSNumber指針里面存儲的數(shù)據(jù)變成了:Tag + Data,也就是將數(shù)據(jù)直接存儲在了指針中。
- 當(dāng)指針不夠存儲數(shù)據(jù)時,才會使用動態(tài)分配內(nèi)存的方式來存儲數(shù)據(jù)。
- objc_msgSend能識別Tagged Pointer,比如NSNumber的intValue方法,直接從指針提取數(shù)據(jù),節(jié)省了以前的調(diào)用開銷
可以理解為把指針指向的內(nèi)容直接放在了指針變量的內(nèi)存地址中,因為在 64 位環(huán)境下指針變量的大小達到了 8 字節(jié)足以容納一些長度較小的內(nèi)容。于是使用了標簽指針這種方式來優(yōu)化數(shù)據(jù)的存儲方式。
- 從其的引用計數(shù)可以看出,這也是一個釋放不掉的單例常量對象。當(dāng)我們使用不同的字符串對象進行創(chuàng)建時當(dāng)內(nèi)容相同,其對象的地址也相同。在運行時根據(jù)實際情況創(chuàng)建。
- 對于 NSString 對象來講,當(dāng)非字面值常量的數(shù)字,英文字母字符串的長度小于等于 9 的時候會自動成為 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;
}
答疑
- 如何判斷一個指針是否為Tagged Pointer?
- iOS平臺,最高有效位是1(第64bit)
- Mac平臺,最低有效位是1
- Tagged Pointer的引用計數(shù)是多少?
- 通過打印retainCount的值:2^64 - 1,可以理解成不會dealloc