内存管理- taggedpoint- retain&release

193 阅读5分钟

内存布局

image.png

  • 栈区:函数,方法指针,局部变量,由sp寄存器去定位。
  • 堆区 通过alloc分配的对象,block copy,由对象的地址去定位。
  • BSS段:未初始化的全局变量,静态变量
  • 数据段: 初始化的全局变量,静态变量
  • text:程序代码,加载到内存中
  • 栈区内存地址:⼀般为:0x7开头
  • 堆区内存地址:⼀般为:0x6开头
  • 数据段,BSS内存地址:⼀般为:0x1开头 
  • 内核区:系统级别的控制区。

内存管理⽅案

  • TaggedPointer:⼩对象-NSNumber,NSDate
  • NONPOINTER_ISA:⾮指针型isa
  • 散列表:引⽤计数表,弱引⽤表

taggedPointer

为什么要使用taggedPointer 假设要存储一个NSNumber对象,其值是一个整数。正常情况下,如果这个整数只是一个NSInteger的普通变量,在64位CPU下是占8个字节的。1个字节有8位,如果我们存储一个很小的值,会出现很多位都是0的情况,这样就造成了内存浪费,苹果为了解决这个问题,引入了taggedPointer的概念。

  • Tagged Pointer专门用来存储小的对象,例如NSNumber, NSDate, NSString。
  • Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要malloc和free。
  • 在内存读取上有着3倍的效率,创建时比以前快106倍。
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;
    }
}

  • 这里是taggedpoint的创建,在read_images函数中类的加载的时候调用,同时使用了随机数做了加密
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);
    }
}

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;
}

可以看到,系统对taggedPointer进行了 _objc_encodeTaggedPointer 编码,该编码的实现就是对value进行了 objc_debug_taggedpointer_obfuscator 的异或操作,而在读取taggedPointer的时候,通过 _objc_decodeTaggedPointer 进行解码,还是进行了objc_debug_taggedpointer_obfuscator的异或操作,这样进行了两次异或操作就还原了初始值

taggedpoint 各个位上表示的意思分析

  • 这个是仿照源码的解码
extern uintptr_t objc_debug_taggedpointer_obfuscator;

kc_objc_decodeTaggedPointer(id ptr)

{
    return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
  • 测试代码 string
    NSString *str = [NSString stringWithFormat:@"a"];

    NSLog(@"%p-%@-%@ - 0x%lx",str,str,str.class,kc_objc_decodeTaggedPointer(str));

    NSString *str1 = [NSString stringWithFormat:@"aa"];

    NSLog(@"%p-%@-%@ - 0x%lx",str1,str1,str1.class,kc_objc_decodeTaggedPointer(str1));
  • 测试结果一个是字符串a,一个字符串aa,分别的结果如下。 ` 2021-12-13 09:13:59.166141+0800 002---taggedPointer[62890:6996224] 0xa000000000000611-a-NSTaggedPointerString - 0x611

2021-12-13 09:14:01.128796+0800 002---taggedPointer[62890:6996224] 0xa000000000061612-aa-NSTaggedPointerString - 0x61612 `

  • 0xa 转为二进制 是 0b1010
    • 第一个1 表示的是否为taggedpoint 1为是,0位否
    • 剩下的三位是010 即为2
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表示的时字符串的意思,说明了这个是关于string的taggedpoint.
  • 最后以为是表示个数的意思,所以a最后位为1,aa最后位为2,61是 a 的ASCII吗,所以a是61,aa是6161.
  • 测试int
    int a = 3;
    NSNumber *number = @(a);
    NSLog(@"%p-%@ -%@- 0x%lx",number,number,number.class,kc_objc_decodeTaggedPointer(number));
  • 结果如下 0xb000000000000032-3 -__NSCFNumber- 0x32**
  • 0xb 同上分析得知是一个number类型的taggedpoint. 最后为的2表示的时类型int,3才是真实数据
  • 2、4、5、3分别代表int long float double类型。测试得出。 这是在模拟器上的测试结果真机则是架构不一致原理是差不多一样的,只是可能存放的位置不一样,就不概述了。

retain & release

isa中的extra_rc:当表示该对象的引⽤计数值,实际上是引⽤计数值减 1, 例如,如果对象的引⽤计数为 10,那么 extra_rc 为9。如果引⽤计数⼤于 10,则需要使⽤到下⾯的 has_sidetable_rc。

  • 源码位置 image.png

  • 重点在do-while中的操作 image.png

image.png 流程

  • 1.判断是否为nonpointer

    • 是 散列表操作引用计数
  • 2.判断是否正在释放

  • 3.newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

  • 4.判断是否存满,新的地方一半,散列表中一半,之所以放一半是为了relase方便。

  • release image.png

  • 引用计数-1 image.png

针对相应引⽤计数位减1

如果引⽤计数出现下溢出,就去散列表借来的引⽤计数- 1 存到extra_rc

release 就算借散列表的引⽤计数过来,还是下溢出,那么就调⽤dealloc 

  • 判断是否为空后调用dealloc image.png

dealloc

1:根据当前对象的状态是否直接调⽤free()释放

2:是否存在C++的析构函数、移除这个对象的关联属性

3:将指向该对象的弱引⽤指针置为nil

4:从弱引⽤表中擦除对该对象的引⽤计数