美文网首页iOS
iOS内存管理—内存管理方案(Tagged Pointer)

iOS内存管理—内存管理方案(Tagged Pointer)

作者: 十年开发初学者 | 来源:发表于2021-09-10 17:53 被阅读0次

    iOS内存管理方案有:

    • MRCARC
    • Tagged Pointer:专门用来处理小对象,例如NSNumber、NSDate、小NSString等
    • Nonpointer_isa:非指针类型的isa,主要是用来优化64位地址
    • SideTables:散列表,在散列表中主要有两个表,分别是引用计数表、弱引用表

    Tagged Pointer

    在学习Tagged Pointer前建议先去看下2020WWDC里面关于小对象类型的更新介绍

    • 普通对象:通过指针地址找到内存
    • 小对象类型:是指针+值的形式,这样不用去访问内存从而就能获取到,这样的话效率会高很多

    我们现在源码中搜索 TaggedPointer看过2020WWDC的应该会对playload比较敏感,而且decoded这个注释我们猜测小对象类型实际上是通过加密、解密的方式进行存储和读取

    image.png

    最终我们找到_objc_makeTaggedPointer这个方法,在这个方法中调用了_objc_encodeTaggedPointer方法,也就是对小对象类型进行加密

    
    // 小对象类型加密
    static inline void * _Nonnull
    _objc_encodeTaggedPointer(uintptr_t ptr)
    {
        // 通过 异或的方式进行加密
        uintptr_t value = (objc_debug_taggedpointer_obfuscator ^ ptr);
    #if OBJC_SPLIT_TAGGED_POINTERS
        // 如果 满足下面条件就不进行加密
        if ((value & _OBJC_TAG_NO_OBFUSCATION_MASK) == _OBJC_TAG_NO_OBFUSCATION_MASK)
            return (void *)ptr;
        uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
        uintptr_t permutedTag = _objc_basicTagToObfuscatedTag(basicTag);
        value &= ~(_OBJC_TAG_INDEX_MASK << _OBJC_TAG_INDEX_SHIFT);
        value |= permutedTag << _OBJC_TAG_INDEX_SHIFT;
    #endif
        return (void *)value;
    }
    /// 小对象类型解密
    static inline uintptr_t
    _objc_decodeTaggedPointer_noPermute(const void * _Nullable ptr)
    {
        uintptr_t value = (uintptr_t)ptr;
    #if OBJC_SPLIT_TAGGED_POINTERS
        // 如果满足下述条件 则不需要解密
        if ((value & _OBJC_TAG_NO_OBFUSCATION_MASK) == _OBJC_TAG_NO_OBFUSCATION_MASK)
            return value;
    #endif
        return value ^ objc_debug_taggedpointer_obfuscator;
    }
    
    static inline uintptr_t
    _objc_decodeTaggedPointer(const void * _Nullable ptr)
    {
        // 解密
        uintptr_t value = _objc_decodeTaggedPointer_noPermute(ptr);
    #if OBJC_SPLIT_TAGGED_POINTERS
        uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
    
        value &= ~(_OBJC_TAG_INDEX_MASK << _OBJC_TAG_INDEX_SHIFT);
        value |= _objc_obfuscatedTagToBasicTag(basicTag) << _OBJC_TAG_INDEX_SHIFT;
    #endif
        return value;
    }
    
    static inline void * _Nonnull
    _objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value)
    {
        // PAYLOAD_LSHIFT and PAYLOAD_RSHIFT are the payload extraction shifts.
        // They are reversed here for payload insertion.
    
        // ASSERT(_objc_taggedPointersEnabled());
        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(result);
        } else {
            // ASSERT(tag >= OBJC_TAG_First52BitPayload);
            // ASSERT(tag <= OBJC_TAG_Last52BitPayload);
            // ASSERT(((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT) == value);
            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(result);
        }
    }
    

    由上述代码发现在加密和解密方法中都调用objc_debug_taggedpointer_obfuscator这个东西和value进行异或运算,所谓的异或运算就是两段二进制进行运算相同为0,不相同为1

    objc_debug_taggedpointer_obfuscator其实就是个随机数,他是在类的加载阶段生成的

    image.png
    看到_read_images方法是不是很熟悉,这是我们前面讲类的加载篇章讲解的,接下来进入到initializeTaggedPointerObfuscator来看下
    static void
    initializeTaggedPointerObfuscator(void)
    {
        if (!DisableTaggedPointerObfuscation) {
            // Pull random data into the variable, then shift away all non-payload bits.
            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 {
            // Set the obfuscator to zero for apps linked against older SDKs,
            // in case they're relying on the tagged pointer representation.
            objc_debug_taggedpointer_obfuscator = 0;
        }
    }
    
    

    由上述代码可知这里对DisableTaggedPointerObfuscation进行了判断,如果为false则对objc_debug_taggedpointer_obfuscator这个地址指向的内存进行空间开辟随机数赋值。否则objc_debug_taggedpointer_obfuscator0

    DisableTaggedPointerObfuscation是下述代码,这里先记一下,后面要用

    OPTION( DisableTaggedPointerObfuscation, OBJC_DISABLE_TAG_OBFUSCATION,    "disable obfuscation of tagged pointers")
    
    【注意】下述讲解全部基于arm64架构下,x86架构下会有所不同

    通过上述讲解,知道了小对象类型其实就是通过对对象进行加密解密的过程,接下来我们实践一下
    首先DisableTaggedPointerObfuscation这个东西是用来定义是否要加密,这个地方我们改成不加密的方式

    image.png

    首先我们发现这里的NSString为NSTaggedPointerString类型

    image.png
    这里将指针地址打印成二进制的形式
    image.png

    接下来我们验证下


    image.png
    enum
    #endif
    {
        // 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,
    
        // 60-bit reserved
        OBJC_TAG_RESERVED_7        = 7, 
    
        // 52-bit payloads
        OBJC_TAG_Photos_1          = 8,
        OBJC_TAG_Photos_2          = 9,
        OBJC_TAG_Photos_3          = 10,
        OBJC_TAG_Photos_4          = 11,
        OBJC_TAG_XPC_1             = 12,
        OBJC_TAG_XPC_2             = 13,
        OBJC_TAG_XPC_3             = 14,
        OBJC_TAG_XPC_4             = 15,
        OBJC_TAG_NSColor           = 16,
        OBJC_TAG_UIColor           = 17,
        OBJC_TAG_CGColor           = 18,
        OBJC_TAG_NSIndexSet        = 19,
        OBJC_TAG_NSMethodSignature = 20,
        OBJC_TAG_UTTypeRecord      = 21,
    
        // When using the split tagged pointer representation
        // (OBJC_SPLIT_TAGGED_POINTERS), this is the first tag where
        // the tag and payload are unobfuscated. All tags from here to
        // OBJC_TAG_Last52BitPayload are unobfuscated. The shared cache
        // builder is able to construct these as long as the low bit is
        // not set (i.e. even-numbered tags).
        OBJC_TAG_FirstUnobfuscatedSplitTag = 136, // 128 + 8, first ext tag with high bit set
    
        OBJC_TAG_Constant_CFString = 136,
    
        OBJC_TAG_First60BitPayload = 0, 
        OBJC_TAG_Last60BitPayload  = 6, 
        OBJC_TAG_First52BitPayload = 8, 
        OBJC_TAG_Last52BitPayload  = 263,
    
        OBJC_TAG_RESERVED_264      = 264
    };
    
    • 上述代码是源码中的对象类型对应的枚举值,我们打印出的为2,是个字符串没问题。
    • 字符串长度也是2
    • 115ASSIC中代表s
    • 104ASSIC中代表h

    【结论】由上述证明小对象类型的值、对象类型都存储在指针地址里面

    接下来看一个案例
    - (void)taggedPointerDemo {
      
        self.queue = dispatch_queue_create("com.cooci.cn", DISPATCH_QUEUE_CONCURRENT);
        
        for (int i = 0; i<10000; i++) {
            dispatch_async(self.queue, ^{
                self.nameStr = [NSString stringWithFormat:@"sh"];
                 NSLog(@"%@",self.nameStr);
            });
        }
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        NSLog(@"来了");
        for (int i = 0; i<1000000; i++) {
            dispatch_async(self.queue, ^{
                self.nameStr = [NSString stringWithFormat:@"测试小对象类型taggedPointer"];
                NSLog(@"%@",self.nameStr);
            });
        }
    }
    

    我们发现这里发生了崩溃


    image.png

    这里的NSString小对象类型

    image.png
    这里的NSString正常的NSString
    image.png
    这里发生崩溃的原因是因为,这里启用了多线程,导致NSStringrelease 和retain发生混乱

    我们来看下retainrelease的源码,发现这里对小对象类型进行了判断,如果是小对象类型直接return

    image.png
    image.png

    由此可见小对象类型对运行效率会大大的提升

    image.png

    相关文章

      网友评论

        本文标题:iOS内存管理—内存管理方案(Tagged Pointer)

        本文链接:https://www.haomeiwen.com/subject/qmgiwltx.html