iOS中的TaggedPointer

Tagged Pointer的背景

在64机器中,一个指针占据8个字节,一个对象包含isa指针,也是8个字节。对于包含整形的NSNumber来说,还必须有8个字节存储这个整型数字。所以一个NSNumber类型的对象加上一个指针,至少会占据24个字节。

苹果为了优化对象的内存设计了Tagged Pointer,在64位的机器上,把诸如整型,char类型,或者一些长度较小的字符串,直接放入指针里面,然后在高四位和低四位加上标记位,表示当前的指针为Tagged Pointer并且指明当前的数据类型。这样就可以方便地存储和访问数据了。引入Tagged Pointer后,内存占用会减少一半以上,访问速度会提升3倍。Tagged Pointer并不是对象,它的创建和销毁过程比对象也快很多。以一个整型的NSNumber为例,不使用Tagged Pointer的情况下,至少占用24字节,而使用了Tagged Pointer后,占用的字节数为8个字节,可见,内存方便的提升还是很明显的。

Tagged Pointer支持的类型

常见的数据类型NSString,NSNumber,NSIndexPath,NSDate和UIColor支持Tagged Pointer

    OBJC_TAG_NSString          = 2, 
    OBJC_TAG_NSNumber          = 3, 
    OBJC_TAG_NSIndexPath       = 4, 
    OBJC_TAG_NSDate            = 6,
    OBJC_TAG_UIColor           = 17,

Tagged Pointer生成过程

在app启动过程中,dyld链接动态库的时候,会生成Tagged Pointer混淆数字,DisableTaggedPointerObfuscation这个属性默认为NO,也就是说混淆数字默认是存在的。

static void
initializeTaggedPointerObfuscator(void)
{
    
    
    //DisableTaggedPointerObfuscation为禁用Tagged Pointer混淆
    if (!DisableTaggedPointerObfuscation){
    
    
        arc4random_buf(&objc_debug_taggedpointer_obfuscator,
                       sizeof(objc_debug_taggedpointer_obfuscator));
        objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;

#if OBJC_SPLIT_TAGGED_POINTERS
        // The obfuscator doesn't apply to any of the extended tag mask or the no-obfuscation bit.
        objc_debug_taggedpointer_obfuscator &= ~(_OBJC_TAG_EXT_MASK | _OBJC_TAG_NO_OBFUSCATION_MASK);

        // Shuffle the first seven entries of the tag permutator.
        int max = 7;
        for (int i = max - 1; i >= 0; i--) {
    
    
            int target = arc4random_uniform(i + 1);
            swap(objc_debug_tag60_permutations[i],
                 objc_debug_tag60_permutations[target]);
        }
#endif
    } else {
    
    
        objc_debug_taggedpointer_obfuscator = 0;
    }
}

在应用程序的运行阶段,使用这个混淆数字,生成实际的Tagged Pointer。

static inline void * _Nonnull
_objc_makeTaggedPointer_withObfuscator(objc_tag_index_t tag, uintptr_t value,
                                      uintptr_t obfuscator)
{
    
    
    if (tag <= OBJC_TAG_Last60BitPayload) {
    
    
        // ASSERT(((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT) == value);
        uintptr_t result =
            (_OBJC_TAG_MASK | 
             ((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) | 
             ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT));
        return _objc_encodeTaggedPointer_withObfuscator(result, obfuscator);
    } else {
    
    
        uintptr_t result =
            (_OBJC_TAG_EXT_MASK |
             ((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) |
             ((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT));
        return _objc_encodeTaggedPointer_withObfuscator(result, obfuscator);
    }
}

在生成Tagged Pointer的过程中,实际是对指针做了位运算。

static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
    
    
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

在程序的运行过程中,如果要判断一个指针是否为Tagged Pointer,只要看标记位就可以了。

Tagged Pointer和对象之间的差异

Tagged Pointer并不是对象,也没有isa指针,内存分配和销毁的过程和对象也不一样。

id objc_retain(id obj)
{
    
    
    if (_objc_isTaggedPointerOrNil(obj)) return obj;
    return obj->retain();
}
void 
objc_release(id obj)
{
    
    
    if (_objc_isTaggedPointerOrNil(obj)) return;
    return obj->release();
}

引用计数管理的时候,如果是Tagged Pointer,函数会直接return。

id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
{
    
    
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;
    //如果是Tagged Pointer,直接返回
    if (_objc_isTaggedPointerOrNil(referent)) return referent_id;

    //其他代码
    //...
}

在修改weak表的时候,如果被弱引用的是Tagged Pointer,这个时候Tagged Pointer不会加入到weak表里面。

 // @property (nonatomic, strong) NSString *string;

 for (int i = 0; i < 10000; i++) {
    
    
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
    
            self.string = [NSString stringWithFormat:@"hello world!"];
        });
    }

对于上述代码,如果string是nonatomic修饰,代码执行过程中会发生crash。因为多线程的环境多个线程同时访问读写string,会发生坏地址访问而crash。

// @property (nonatomic, strong) NSString *string;

 for (int i = 0; i < 10000; i++) {
    
    
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
    
            self.string = [NSString stringWithFormat:@"hello"];
        });
    }

这段代码在执行的过程中不会发生crash,因为string此时被初始化为较短的字符串@“hello”,程序在运行过程中会把它优化为Tagged Pointer,在对Tagged Pointer操作的时候实际是对uintptr_t类型的位运算,不涉及对象内存的分配和销毁过程。

猜你喜欢

转载自blog.csdn.net/u011608357/article/details/128292135