问题来源
在最近的一次面试过程中,被问到Apple对tagged pointer
的优化的问题,当时一脸懵逼,除了说上一句“提高访问效率和节约内存成本”,就没有然后了,被问及其实现原理一概不知。一时陷入尴尬,不知道对话该怎么继续。词穷的原因,大概是因为我的无知吧!所以过后,赶紧补上这方面的知识!
探索历程
1、百度
既然打算掌握tagged pointer
的相关知识,肯定要知道它是什么,干什么用的,怎么实现的,优势&劣势等等。一大堆的问题,将我湮灭,还好,我有度娘。
so…
一看这么多,求知欲爆棚的我瞬间高潮了,内心简直。。。。。无法用言语形容。尝试着阅读了几篇,发现解释的都很片面和肤浅,和我想要的相差甚远。有点小失落。但是,我可是“好好学习天天向上”的好学生,祖国的花朵,共产主义的接班人啊,怎能轻言放弃呢。于是继续在一堆优(垃)秀(圾)中追求真理。。。
皇天不负有心人。
看到唐巧大佬也有过这方面的研究。嗯嗯,看来我和大佬之间,就差一个tagged pointer
的掌握!!!(窃喜&YY自己正走在成为大佬的路上.png)
点开大佬博客,似乎好像,大佬对tagged pointer
也没有太多的解析,让读者知其大概。顿时陷入了迷茫和思考:我想要的仅仅是这些表面的知其然而不知其所以然的所谓的知识吗?脑海中闪过无数鸡汤,瞬间顿悟:去吧皮卡丘,追寻你想要的吧!
2、Developer Documention
干劲是有了,那从哪里入手呢,怎么去学习tagged pointer
呢。这时,我想到了官方文档,于是打开xcode,Help
–>Developer Documention
,搜索Tagged Pointer
:
WTF….
内心一万头草泥马奔腾而过~~~
后来想想,感觉自己好傻逼,Developer Documention
是开发者接口文档,tagged pointer又不是接口,是Apple对NSString、NSNumber、NSDate等类的优化。优化。优化。额,,好像哪里不对,优化不是底层实现的吗,探索底层,不应该去看runtime
吗。诶,终究还是太年轻,为什么现在才想起来要看runtime,看来共产主义迟迟不让你接班,还是有原因的!!!
runtime源码
既然知道了路在何方,就不在迷茫~
废话不多说,先去OpenSource下载一份runtime
源码。
这里下载一份最新的objc-750
。
下载完后,我隐隐感觉到,我离掌握Tagged Pointer
已经不远了。
于是,抑制住激动的心,控制住颤抖的手,点开objc-750
源码,好吧,我又迷失在了无尽的头文件中。
这尼玛,Tagged Pointer相关的知识,在哪个头文件?总不能一个个去找吧?你可能觉得我已经在头文件中迷失自我了,我只能说:呵,天真,我可是共产主义的接班人,这就想难道我?
于是我点开了文件搜索😎~
这TM也有好几个啊啊啊啊啊啊。没办法,只能再次求助度娘。
一番查找,终于找到了objc-internal.h
。(窃喜.jpg)
总算进入正题了(进入全神贯注模式.jpg)。
开始阅读源码:
这里可以看注释,文件中关于Tagged Pointer
的,大概在209行的位置:
所以直接看这里。
开始的位置,是对64位系统的判定:
1 | #if __LP64__ |
然后下面就对OBJC_HAVE_TAGGED_POINTERS
宏进行判定:
1 |
|
继续往下:
1 | #if __has_feature(objc_fixed_enum) || __cplusplus >= 201103L |
一看到#if
、#else
、#endif
,就知道在做判断。
看一下具体在判断什么:
1 | #if __has_feature(objc_fixed_enum) || __cplusplus >= 201103L |
下面是一些枚举值:
1 | { |
接下来,就是一些关于tagged pointer
的函数了:
1 | // Returns true if tagged pointers are enabled. |
中间也有各个函数的注释,还算注释的比较清楚。那我们就根据注释和源码,一个个看其实现吧(下面将声明和实现的代码粘贴到一起):_objc_taggedPointersEnabled
:
1 | // Returns true if tagged pointers are enabled. |
这个函数的实现其实很简单,关键是objc_debug_taggedpointer_mask
,这是个什么东西?
它既然被extern
引入,说明他在其他文件中肯定被定义并初始化过。so,找到它。
在objc-gdb.h
中找到了:
1 | OBJC_EXPORT uintptr_t objc_debug_taggedpointer_mask |
然而,搞了半天,还是不知道objc_debug_taggedpointer_mask
的值啊。
所以继续探索。
一番寻找之后,终于找到了其赋值的位置:
1 | uintptr_t objc_debug_taggedpointer_mask = _OBJC_TAG_MASK; |
绕这么一大圈,居然给我说是这个?so interesting!!!
回到最最初的那个_objc_taggedPointersEnabled
函数:
1 | static inline bool |
这个函数算是弄清楚了,接下来看另一个函数。_objc_registerTaggedPointerClass
:
1 | OBJC_EXPORT void |
继续往下看的话,又遇到一个之前没有看过的函数:
1 | Class * classSlotForTagIndex(tag); |
在runtime中找到它的实现部分:
1 | // Returns a pointer to the class's storage in the tagged class arrays, |
分析classSlotForBasicTagIndex
函数。
1 | // Returns a pointer to the class's storage in the tagged class arrays. |
分析objc_debug_taggedpointer_obfuscator
。
1 | /*********************************************************************** |
objc_debug_taggedpointer_obfuscator
也算是囫囵吞枣掌握了,回到刚刚那个classSlotForBasicTagIndex
函数:
1 | static Class * |
回到开始的_objc_registerTaggedPointerClass
函数:
1 | void |
到这里,这个函数算是解析完了。其实它做的就是:
1、判断tagged pointer disabled与否。
2、用tag值在objc_tag_classes数组中查找相对应的Class(查找过程上面已经分析过)。
3、判断是否查找到。如果查找到,判断是否被占用。
下一个函数_objc_getClassForTag
:
1 | // Returns the registered class for the given tag. |
嘿,巧了,classSlotForTagIndex
函数,在分析上个函数的时候,分析过了。详细过程见classSlotForBasicTagIndex
函数的分析过程。
下一个函数_objc_makeTaggedPointer
:
1 | // Create a tagged pointer object with the given tag and payload. |
先分析result
的值:
1 | uintptr_t result = (_OBJC_TAG_MASK | ((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) | ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT)); |
再来看看_objc_encodeTaggedPointer
:
1 | static inline void * _Nonnull |
看下一个函数_objc_isTaggedPointer
:
1 | // Return true if ptr is a tagged pointer object. |
_objc_getTaggedPointerTag
:
1 | // Extract the tag value from the given tagged pointer object. |
_objc_getTaggedPointerValue
:
1 | // Extract the payload from the given tagged pointer object. |
_objc_getTaggedPointerSignedValue
1 | // Extract the payload from the given tagged pointer object. |
理论到这算是走完了。
这里,总结一下:
tagged pointer是Apple在64bit系统上对NSNumber类的一些优化,主要目的是为了节省内存。其实现原理为:将指针的一部分(4bit)拿出来充当tag值,标记对象指针是否为tagged pointer。并在这4bit中,还存储了该指针的所属类在objc_tag_classes数组中的index。通过一系列函数可以对其进行操作,例如判断指针是否为tagged pointer(_objc_isTaggedPointer)、获取tagged pointer的数据(_objc_getTaggedPointerValue)、获取tagged pointer的类型(_objc_getTaggedPointerTag)等等。
实践(环境:MAC,版本:10.14.5)
理论搞懂了,那就来指导一下实践吧:
- 1 由已知tagged pointer 获取其相关信息。
(1)查看tagged pointer enable or disable。
根据上述分析,判断pagged pointer是否启用,应该是调用函数_objc_taggedPointersEnabled
。
但是_objc_taggedPointersEnabled
函数是被static
修饰的,所以不能extern
并使用。只能直接重写其实现。查看打印:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#import <Foundation/Foundation.h>
bool sl_objc_taggedPointersEnabled(void)
{
extern uintptr_t objc_debug_taggedpointer_mask;
return (objc_debug_taggedpointer_mask != 0);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
bool b = sl_objc_taggedPointersEnabled();
NSLog(@"Enable:%d",b);
}
return 0;
}可知,在mac10.14.5上,是enabled的。1
2019-07-01 17:37:14.584230+0800 AAA[10865:711050] Enable:1
(2)判断一个指针是不是Tagged Pointer。打印:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22# define _OBJC_TAG_MASK 1UL//本来需要判断的,这里测试的是已知的mac 10.14.5.所以直接定义
bool sl_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSNumber *n = @123;
NSString *s = @"123";
NSString *s_m_c = [[s mutableCopy] copy];
NSObject *o = [NSObject new];
bool nb = sl_objc_isTaggedPointer((__bridge void *)n);
bool sb = sl_objc_isTaggedPointer((__bridge void *)s);
bool smcb = sl_objc_isTaggedPointer((__bridge void *)s_m_c);
bool ob = sl_objc_isTaggedPointer((__bridge void *)o);
NSLog(@"nb(0x%lx) is TaggedPointer %d",(uintptr_t)n,nb);
NSLog(@"sb(0x%lx) is TaggedPointer %d",(uintptr_t)s,sb);
NSLog(@"smcb(0x%lx) is TaggedPointer %d",(uintptr_t)s_m_c,smcb);
NSLog(@"ob(0x%lx) is TaggedPointer %d",(uintptr_t)o,ob);
}
return 0;
}(3)获取tagged pointer的类型(tag枚举值)1
2
3
42019-07-01 17:58:36.116391+0800 AAA[10973:719191] nb(0x56b19f9c2784f641) is TaggedPointer 1
2019-07-01 17:58:36.116530+0800 AAA[10973:719191] sb(0x100001058) is TaggedPointer 0
2019-07-01 17:58:36.116538+0800 AAA[10973:719191] smcb(0x56b19f9c14b6bc53) is TaggedPointer 1
2019-07-01 17:58:36.116544+0800 AAA[10973:719191] ob(0x10051e520) is TaggedPointer 0log:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21# define _OBJC_TAG_INDEX_SHIFT 1
#define _OBJC_TAG_INDEX_MASK 0x7
extern uintptr_t objc_debug_taggedpointer_obfuscator;
uintptr_t sl_objc_getTaggedPointerTag(const void * _Nullable ptr)
{
uintptr_t value = objc_debug_taggedpointer_obfuscator ^ (uintptr_t)ptr;
uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
return basicTag;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSNumber *n = @123;
NSString *s = @"123";
NSString *s_m_c = [[s mutableCopy] copy];
uintptr_t n_t = sl_objc_getTaggedPointerTag((__bridge void *)n);
uintptr_t s_m_c_t = sl_objc_getTaggedPointerTag((__bridge void *)s_m_c);
NSLog(@"n's type is %ld",n_t);
NSLog(@"s_m_c's type is %ld",s_m_c_t);
}
return 0;
}对比log和objc_tag_index_t:1
22019-07-04 09:41:34.017122+0800 AAA[11655:849484] n's type is 3
2019-07-04 09:41:34.017318+0800 AAA[11655:849484] s_m_c's type is 2(4)获取taggedpointer的所属类:1
2
3
4
5
6
7
8// 60-bit payloads
OBJC_TAG_NSAtom = 0,
OBJC_TAG_1 = 1,
OBJC_TAG_NSString = 2,
OBJC_TAG_NSNumber = 3,
OBJC_TAG_NSIndexPath = 4,
OBJC_TAG_NSManagedObjectID = 5,
OBJC_TAG_NSDate = 6,log:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33# define _OBJC_TAG_INDEX_SHIFT 1
#define _OBJC_TAG_INDEX_MASK 0x7
extern uintptr_t objc_debug_taggedpointer_obfuscator;
uintptr_t sl_objc_getTaggedPointerTag(const void * _Nullable ptr)
{
uintptr_t value = objc_debug_taggedpointer_obfuscator ^ (uintptr_t)ptr;
uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
return basicTag;
}
extern Class objc_debug_taggedpointer_classes[];
Class *sl_classSlotForBasicTagIndex(uintptr_t tag)
{
uintptr_t tagObfuscator = ((objc_debug_taggedpointer_obfuscator
>> _OBJC_TAG_INDEX_SHIFT)
& _OBJC_TAG_INDEX_MASK);
uintptr_t obfuscatedTag = tag ^ tagObfuscator;
// Array index in objc_tag_classes includes the tagged bit itself
return &objc_debug_taggedpointer_classes[(obfuscatedTag << 1) | 1];
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSNumber *n = @123;
NSString *s = @"123";
NSString *s_m_c = [[s mutableCopy] copy];
uintptr_t n_t = sl_objc_getTaggedPointerTag((__bridge void *)n);
uintptr_t s_m_c_t = sl_objc_getTaggedPointerTag((__bridge void *)s_m_c);
Class *n_t_c = sl_classSlotForBasicTagIndex(n_t);
Class *s_m_c_t_c = sl_classSlotForBasicTagIndex(s_m_c_t);
NSLog(@"n's Class is %@",*n_t_c);
NSLog(@"s_m_c's Clss is %@",*s_m_c_t_c);
}
return 0;
}看到上面的例子,或许我该露出张狂但又不失谦虚的笑容,因为一切都那么顺利,理论得到了印证。然而,真的是这么一回事吗?下面看一个不尽人意的例子。1
22019-07-04 10:58:05.075581+0800 AAA[11913:876264] n's Class is __NSCFNumber
2019-07-04 10:58:05.075754+0800 AAA[11913:876264] s_m_c's Clss is NSTaggedPointerString
(5)通过taggedpointer获取value:
1 | # define _OBJC_TAG_PAYLOAD_LSHIFT 0 |
log:
1 | 2019-07-04 11:14:59.639165+0800 AAA[11959:881845] hex(123) = 0x7b |
是不是感觉有点不对劲?
换一个数据:
1 | NSNumber *n = [NSNumber numberWithLongLong:121111]; |
log:
1 | 2019-07-04 11:18:03.279558+0800 AAA[11967:882991] hex(121111) = 0x1d917 |
每次都发现算出来的value后面总是多一位。。。。
查了蛮多资料,只知道:value的第1-4位是NSNumber的类型:比如,char是0、short是1、int是2、float是4。