【iOS】Tagged Pointer

news2024/9/20 22:02:28

目录

    • 前言
    • 什么是Tagged Pointer?
      • 引入Tagged Pointer技术之前
      • 引入Tagged Pointer之后
      • 总结
    • Tagged Pointer原理(Tag+Data分析)
      • 关闭数据混淆
      • MacOS分析
        • NSNumber
        • NSString
      • iOS分析
    • 判断Tagged Pointer
      • Tagged Pointer应用
      • Tagged Pointer 注意点
    • Tagged Pointer注释
    • 创建Tagged Pointer
      • Tagged Pointer初始化
        • 初始变量设置
        • Tagged Pointer注册校验
      • 生成Tagged Pointer指针
    • 相关题目


前言

学习Tagged Pointer原理,参考文章:

老生常谈内存管理(五):Tagged Pointer

什么是Tagged Pointer?

由于NSNumber、NSDate、NSString一类的变量本身的值需要占用的内存大小常常不需要8个字节,拿整数来说,4 个字节所能表示的有符号整数就可以达到 20 多亿(注:2^31=2147483648,另外 1 位作为符号位),对于绝大多数情况都是可以处理的

为了节省内存和提高执行效率,苹果提出了Tagged Pointer的概念。对于64位程序,引入Tagged Pointer后,相关逻辑能减少一半的内存占用,以及3倍的访问速度提升,100倍的创建、销毁速度提升

引入Tagged Pointer技术之前

对象存储在堆上,对象的指针存储的是堆中对象的地址值(即指向堆中的对象)

从内存上看

这样的存储方式会在CPU位数升级的情况下导致占用存储翻倍(以NSNumber对象为例):

在这里插入图片描述

32位系统下的指针占4字节,64位系统下的指针占8字节

从效率上看

为了存储和访问一个 NSNumber 对象,我们需要在堆上为其分配内存,另外还要维护它的引用计数,管理它的生命期。这些都给程序增加了额外的逻辑,造成运行效率上的损失

引入Tagged Pointer之后

会将一个对象的指针拆成两部分,一部分直接保存数据,另一部分作为特殊标记,表示这是一个特别的指针,不指向任何一个地址。所以,引入了Tagged Pointer对象之后,64位CPU下NSNumber的内存图变成了以下这样:

在这里插入图片描述

如图,Tagged Pointer是将值的信息直接存储到了指针本身里面
要注意的是,当8字节可以承载用于表示的数值时,系统就会以Tagged Pointer的方式生成指针,如果8字节承载不了时,则又用以前的方式来生成普通的指针,才会将对象存储在堆上

由于Tagged Pointer不是对象,所以它的isa应该是无指向的:

NSNumber *number1 = @7;

在这里插入图片描述

总结

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

使用了Tagged Pointer,NSNumber对象的值直接存储在了指针上,不会在堆上申请内存。则使用一个NSNumber对象只需要指针的8个字节内存就够了,大大的节省了内存占用

Tagged Pointer原理(Tag+Data分析)

关闭数据混淆

在现在的版本中,为了保证数据安全,苹果对Tagged Pointer做了数据混淆,开发者通过打印指针无法判断它是不是一个Tagged Pointer,更无法读取Tagged Pointer的存储数据

所以在分析Tagged Pointer之前,我们需要先关闭Tagged Pointer的数据混淆,以方便我们调试程序。通过设置环境变量OBJC_DISABLE_TAG_OBFUSCATIONYES

  • 进入Edit Scheme页面:
    在这里插入图片描述

  • 添加环境变量OBJC_DISABLE_TAG_OBFUSCATION并将其设置为YES

    在这里插入图片描述

  • 添加OBJC_DISABLE_TAGGED_POINTERS并设置为YES,会禁用Tagged Pointer

MacOS分析

NSNumber

MacOSNSNumber的Tagged Pointer位视图:

请添加图片描述
MacOS下采用 LSB(Least Significant Bit,即最低有效位)为Tagged Pointer标识位

比如,打印存储整型1的NSNumber变量地址,为0x127十六进制:

  • 1表示整型1

  • 2表示数据类型为int

    倒数第二位对应数据类型
    0char
    1short
    2int
    3long
    4float
    5double
  • 7的二进制为0111,最后一位1Tagged Pointer标识位,表示这个指针是Tagged Pointer;前面三位011类标识位,对应十进制为3,表示NSNumber类
    objc4源码可查出各个类的标识位:

    // objc-internal.h
    {
        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,
        // ......
    }
    
NSString

MacOSNSString的Tagged Pointer位视图:

请添加图片描述
MacOS下采用 LSB(Least Significant Bit,即最低有效位)为Tagged Pointer标识位

64 bit的MacOS下,如果一个NSString对象指针为Tagged Pointer,那么它的后4位(0-3)作为标识位,第4-7位表示字符串长度,剩余的56位就可以用来存储字符串

比如,打印下列字符串的NSString变量地址:

// MRC 环境
#define HTLog(_var) \
{ \
    NSString *name = @#_var; \
    NSLog(@"%@: %p, %@, %lu", name, _var, [_var class], [_var retainCount]); \
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSString *a = @"a";
        NSMutableString *b = [a mutableCopy];
        NSString *c = [a copy];
        NSString *d = [[a mutableCopy] copy];
        NSString *e = [NSString stringWithString:a];
        NSString *f = [NSString stringWithFormat:@"f"];
        NSString *string1 = [NSString stringWithFormat:@"abcdefg"];
        NSString *string2 = [NSString stringWithFormat:@"abcdefghi"];
        NSString *string3 = [NSString stringWithFormat:@"abcdefghij"];
        HTLog(a);
        HTLog(b);
        HTLog(c);
        HTLog(d);
        HTLog(e);
        HTLog(f);
        HTLog(string1);
        HTLog(string2);
        HTLog(string3);
    }
    return 0;
}
/*
a: 0x100002038, __NSCFConstantString
b: 0x10071f3c0, __NSCFString
c: 0x100002038, __NSCFConstantString
d: 0x6115, NSTaggedPointerString
e: 0x100002038, __NSCFConstantString
f: 0x6615, NSTaggedPointerString
string1: 0x6766656463626175, NSTaggedPointerString
string2: 0x880e28045a54195, NSTaggedPointerString
string3: 0x10071f6d0, __NSCFString
*/

从打印结果来看,有三种NSString类型:

类型描述
__NSCFConstantString1. 常量字符串,存储在字符串常量区,继承于 __NSCFString相同内容的 __NSCFConstantString 对象的地址相同,也就是说常量字符串对象是一种单例,可以通过 == 判断字符串内容是否相同2. 这种对象一般通过字面值@"..."创建。如果使用 __NSCFConstantString 来初始化一个字符串,那么这个字符串也是相同的 __NSCFConstantString。
__NSCFString1. 存储在堆区,需要维护其引用计数,继承于 NSMutableString2. 通过stringWithFormat:等方法创建的NSString对象(且字符串值过大无法使用Tagged Pointer存储)一般都是这种类型。
NSTaggedPointerStringTagged Pointer,字符串的值直接存储在了指针上

打印结果分析:

NSString对象类型分析
a__NSCFConstantString通过字面量@"..."创建
b__NSCFStringa 的深拷贝,指向不同的内存地址,被拷贝到堆区
c__NSCFConstantStringa 的浅拷贝,指向同一块内存地址
dNSTaggedPointerString单独对 a 进行 copy(如 c),浅拷贝是指向同一块内存地址,所以不会产生Tagged Pointer;单独对 a 进行 mutableCopy(如 b),复制出来是可变对象,内容大小可以扩展;而Tagged Pointer存储的内容大小有限,因此无法满足可变对象的存储要求。
e__NSCFConstantString使用 __NSCFConstantString 来初始化的字符串
fNSTaggedPointerString通过stringWithFormat:方法创建,指针足够存储字符串的值。
string1NSTaggedPointerString通过stringWithFormat:方法创建,指针足够存储字符串的值。
string2NSTaggedPointerString通过stringWithFormat:方法创建,指针足够存储字符串的值。
string3__NSCFString通过stringWithFormat:方法创建,指针不足够存储字符串的值。

可以看到,为Tagged Pointer的有dfstring1string2指针,它们的指针值分别为
0x61150x66150x67666564636261750x880e28045a54195

  • 其中0x610x660x67666564636261分别对应字符串的ASCII
  • 最后一位5的二进制为0101,最后一位1是代表这个指针是Tagged Pointer,010对应十进制为2,表示NSString
  • 倒数第二位1179代表字符串长度

对于string2的指针值0x880e28045a54195,虽然从指针中看不出来字符串的值,但其也是一个Tagged Pointer

iOS分析

iOS下则采用 MSB(Most Significant Bit,即最高有效位)为Tagged Pointer标识位

iOSNSNumber的Tagged Pointer位视图:

请添加图片描述

iOSNSString的Tagged Pointer位视图:

请添加图片描述

与MacOS主要区别就是标识位的位置不同

判断Tagged Pointer

依据就是Tagged Pointer的标识位

查以下函数源码:

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

可以看到,_objc_isTaggedPointer函数的实现是将指针值与一个_OBJC_TAG_MASK掩码进行按位与运算,查看该掩码:

在这里插入图片描述

再查宏判断条件的值:

在这里插入图片描述

可以看到,arm64架构下的掩码为1UL<<63x86 64架构且MacOS系统下的掩码为1UL,其他情况下的掩码为1UL<<63

简言之,iOSarm64下采用最高有效位为Tagged Pointer的标识位,MacOSx86 64下采用最低有效位为Tagged Pointer的标识位

而存储在堆空间的对象由于内存对齐(16的倍数),它的内存地址的最低有效位为0。由此可以辨别Tagged Pointer一般对象指针

在这里插入图片描述

Tagged Pointer应用

objc_msgSend能识别Tagged Pointer,比如NSNumberintValue方法,直接从指针提取数据,不会进行objc_msgSend的三大流程,节省了调用开销(消息转发、寻找isa指向等)

内存管理相关的,如retain方法中调用的rootRetain

ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    // 如果是 tagged pointer,直接返回 this
    if (isTaggedPointer()) return (id)this; 

    bool sideTableLocked = false;
    bool transcribeToSideTable = false; 

    isa_t oldisa;
    isa_t newisa;
    ......

isTaggedPointer()函数实现:

inline bool 
objc_object::isTaggedPointer() 
{
    return _objc_isTaggedPointer(this);
}

Tagged Pointer 注意点

我们知道,所有OC对象都有isa指针,而Tagged Pointer并不是真正的对象,它没有isa指针,所以如果你直接访问Tagged Pointer的isa成员的话,将会有如下警告:

在这里插入图片描述

Tagged Pointer注释

源码中对Tagged Pointer有这样一段注释:

/***********************************************************************
* Tagged pointer objects.
*
* Tagged pointer objects store the class and the object value in the
* object pointer; the "pointer" does not actually point to anything.
*
* Tagged pointer objects currently use this representation:
* (LSB)
*  1 bit   set if tagged, clear if ordinary object pointer
*  3 bits  tag index
* 60 bits  payload
* (MSB)
* The tag index defines the object's class.
* The payload format is defined by the object's class.
*
* If the tag index is 0b111, the tagged pointer object uses an
* "extended" representation, allowing more classes but with smaller payloads:
* (LSB)
*  1 bit   set if tagged, clear if ordinary object pointer
*  3 bits  0b111
*  8 bits  extended tag index
* 52 bits  payload
* (MSB)
*
* Some architectures reverse the MSB and LSB in these representations.
*
* This representation is subject to change. Representation-agnostic SPI is:
* objc-internal.h for class implementers.
* objc-gdb.h for debuggers.
**********************************************************************/

翻译:

  1. Tagged pointer 指针对象将class和对象数据存储在对象指针中,指针实际上不指向任何东西。

  2. Tagged pointer 当前使用此表示形式:

    (LSB)(macOS)64位分布如下:

    • 1 bit 标记是 Tagged Pointer
    • 3 bits 标记类型
    • 60 bits 负载数据容量,(存储对象数据)

    (MSB)(iOS)64位分布如下:

    • tag index 表示对象所属的 class
    • 负载格式由对象的 class 定义
    • 如果 tag index 是 0b111(7), tagged pointer 对象使用 “扩展” 表示形式
    • 允许更多类,但 有效载荷 更小

    (LSB)(macOS)(带有扩展内容)64位分布如下:

    • 1 bit 标记是 Tagged Pointer
    • 3 bits 是 0b111
    • 8 bits 扩展标记格式
    • 52 bits 负载数据容量,(存储对象数据)
  3. 在这些表示中,某些体系结构反转了MSB和LSB

从注释中我们得知:

  • Tagged pointer存储对象数据目前分为60bits负载容量和52bits负载容量。
  • 类标识允许使用扩展形式

那么如何判断负载容量?类标识的扩展类型有那些?我们来看下全面的objc_tag_index_t源码:

{
    // 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,
    OBJC_TAG_Foundation_1      = 22,
    OBJC_TAG_Foundation_2      = 23,
    OBJC_TAG_Foundation_3      = 24,
    OBJC_TAG_Foundation_4      = 25,


    OBJC_TAG_FirstUnobfuscatedSplitTag = 136, // 128 + 8, first ext tag with high bit set

    OBJC_TAG_Constant_CFString = 136,
	
	// 前60位负载内容
    OBJC_TAG_First60BitPayload = 0, 
    // 后60位负载内容
    OBJC_TAG_Last60BitPayload  = 6, 
    // 前52位负载内容
    OBJC_TAG_First52BitPayload = 8, 
    // 后52位负载内容
    OBJC_TAG_Last52BitPayload  = 263,
	
	// 保留位
    OBJC_TAG_RESERVED_264      = 264
};

小结

  1. 区分什么位置为负载内容位

    MacOS下采用 LSB 即OBJC_TAG_First60BitPayload、OBJC_TAG_First52BitPayload。
    iOS下则采用 MSB 即OBJC_TAG_Last60BitPayload、OBJC_TAG_Last52BitPayload。

  2. 区分负载数据容量

    当类标识为0-6时,负载数据容量为60bits。
    当类标识为7时(对应二进制为 0b111),负载数据容量为52bits。

  3. 类标识的扩展类型有哪些?

    如果tag index 是 0b111(7), tagged pointer对象使用 “扩展” 表示形式
    类标识的扩展类型为上面OBJC_TAG_Photos_1 ~OBJC_TAG_NSIndexSet。

  4. 类标识与负载数据容量对应关系

    当类标识为0-6时,负载数据容量为 60bits。即OBJC_TAG_First60BitPayload 和 OBJC_TAG_Last60BitPayload,负载数据容量 的取值区间也为 0 - 6。
    当类标识为7时,负载数据容量为52bits。即OBJC_TAG_First52BitPayload 和 OBJC_TAG_Last52BitPayload,负载数据容量的取值区间为 8 - 263。

只要一个tag,既可以区分负载数据容量,也可以区分类标识

创建Tagged Pointer

我们知道了Tagged Pointer展现给我们的是Tag + Data,那么它底层是怎么生成这种类型的伪指针的呢?为什么NSNumber、NSDate、NSString会生成,而其他的不会呢?
下面来来研究一下,这几个对象是如何生成Tagged Pointer

Tagged Pointer初始化

初始变量设置

_read_images()函数:

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) {
	// ...
    if (DisableTaggedPointers) {
    	// 禁用Tagged Pointer,与环境变量OBJC_DISABLE_TAGGED_POINTERS相关
        disableTaggedPointers();
    }
	// 初始化 TaggedPointer 混淆器:用于保护 Tagged Pointer 上的数据
    initializeTaggedPointerObfuscator();
    // ...
}

initializeTaggedPointerObfuscator()函数:

OBJC_EXPORT uintptr_t objc_debug_taggedpointer_obfuscator
    OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0);

static void
initializeTaggedPointerObfuscator(void)
{
    if (!DisableTaggedPointerObfuscation){
//    if (!DisableTaggedPointerObfuscation && dyld_program_sdk_at_least(dyld_fall_2018_os_versions)) {
        // 将随机数据拉入变量,然后移走所有非有效负载位
        arc4random_buf(&objc_debug_taggedpointer_obfuscator,
                       sizeof(objc_debug_taggedpointer_obfuscator));
        objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;

#if OBJC_SPLIT_TAGGED_POINTERS
        // 混淆器不适用于任何扩展标记掩码或非混淆位
        objc_debug_taggedpointer_obfuscator &= ~(_OBJC_TAG_EXT_MASK | _OBJC_TAG_NO_OBFUSCATION_MASK);

        // 打乱标签排列器的前七个条目
        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 {
        // 对于与旧版 SDK 链接的应用程序,将混淆器变量设置为0,
        // 防止他们正依赖TaggedPointer
        objc_debug_taggedpointer_obfuscator = 0;
    }
}
  • objc_debug_taggedpointer_obfuscator是一个unsigned long的全局变量
  • 对于一些旧版环境和环境变量OBJC_DISABLE_TAG_OBFUSCATION,禁用数据混淆,设置objc_debug_taggedpointer_obfuscator为0,不混淆
  • 获得objc_debug_taggedpointer_obfuscator的值:
    • 将随机数据放入变量中,然后移走所有非有效位
    • ~_OBJC_TAG_MASK作一次按位与&操作
Tagged Pointer注册校验

为什么NSNumber、NSDate、NSString会转成为伪指针呢?其他的为什么不会呢?

加载程序时,从dyld库的_dyld_start()函数开始,经历了多般步骤,开始调用_objc_registerTaggedPointerClass()函数:

void
_objc_registerTaggedPointerClass(objc_tag_index_t tag, Class cls)
{
    if (objc_debug_taggedpointer_mask == 0) {
        _objc_fatal("tagged pointers are disabled");
    }

    Class *slot = classSlotForTagIndex(tag);
    if (!slot) {
        _objc_fatal("tag index %u is invalid", (unsigned int)tag);
    }

    Class oldCls = *slot;

    if (cls  &&  oldCls  &&  cls != oldCls) {
        _objc_fatal("tag index %u used for two different classes "
                    "(was %p %s, now %p %s)", tag, 
                    oldCls, oldCls->nameForLogging(), 
                    cls, cls->nameForLogging());
    }

	// 如果尚未设置,则将占位类存储在为扩展tag空间保留的基本标记槽中。
	// 在注册第一个扩展标记时延迟执行此操作,以便
	// 旧调试器能够更频繁地正确表征伪指针。
    *slot = cls;
    if (tag < OBJC_TAG_First60BitPayload || tag > OBJC_TAG_Last60BitPayload) {
    	// OBJC_TAG_RESERVED_7 == 7
        Class *extSlot = classSlotForBasicTagIndex(OBJC_TAG_RESERVED_7);
        if (*extSlot == nil) {
            extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
            *extSlot = (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
        }
    }
}
  • 判断是否禁用了Tagged Pointer,若禁用,则终止程序
  • 根据指定tag获取类指针。若tag被用于两个不同的类,则终止程序
  • 判断负载数据容量如果是52bits进行特殊处理,在OBJC_TAG_RESERVED_7处存储占位类OBJC_CLASS_$___NSUnrecognizedTaggedPointer

其实这个方法起的名字是注册,其实应该叫校验。校验在全局数组(以tag进行位操作为索引,类为value的全局数组)中,用 tag取出来的类指针注册的类 是否相符

根据指定tag获取类指针函数classSlotForTagIndex()

// 返回指向tag类数组中类存储的指针,如果标记超出范围,则返回 nil。
static ptrauth_taggedpointer_table_entry Class *
classSlotForTagIndex(objc_tag_index_t tag)
{
    if (tag >= OBJC_TAG_First60BitPayload && tag <= OBJC_TAG_Last60BitPayload) {
        return classSlotForBasicTagIndex(tag);
    }

    if (tag >= OBJC_TAG_First52BitPayload && tag <= OBJC_TAG_Last52BitPayload) {
        int index = tag - OBJC_TAG_First52BitPayload;
#if OBJC_SPLIT_TAGGED_POINTERS
        if (tag >= OBJC_TAG_FirstUnobfuscatedSplitTag)
            return &objc_tag_ext_classes[index];
#endif
        uintptr_t tagObfuscator = ((objc_debug_taggedpointer_obfuscator
                                    >> _OBJC_TAG_EXT_INDEX_SHIFT)
                                   & _OBJC_TAG_EXT_INDEX_MASK);
        return &objc_tag_ext_classes[index ^ tagObfuscator];
    }

    return nil;
}
  • 根据负载数据容量是60bits还是52bits,区分为类标识是基础类标识还是扩展类标识。也可以说根据 tag类标识区间 判断
  • tag是基础类标识,返回classSlotForBasicTagIndex(tag)的结果
  • tag是扩展类标识,对tag进行操作,然后取出存在objc_tag_ext_classes数组里的结果返回

这里有两个重要的全局数组:

#if SUPPORT_TAGGED_POINTERS

extern "C" { 
    extern ptrauth_taggedpointer_table_entry Class objc_debug_taggedpointer_classes[_OBJC_TAG_SLOT_COUNT];
    extern ptrauth_taggedpointer_table_entry Class objc_debug_taggedpointer_ext_classes[_OBJC_TAG_EXT_SLOT_COUNT];
}
#define objc_tag_classes objc_debug_taggedpointer_classes
#define objc_tag_ext_classes objc_debug_taggedpointer_ext_classes

#endif

数组objc_tag_classes:存储苹果定义的几个基础类
数组objc_tag_ext_classes:存储苹果预留的扩展类

classSlotForBasicTagIndex()函数:

// 返回指向tag类数组中类存储的指针
// 假定该tag是一个有效的基本tag
static ptrauth_taggedpointer_table_entry Class *
classSlotForBasicTagIndex(objc_tag_index_t tag)
{
#if OBJC_SPLIT_TAGGED_POINTERS
    uintptr_t obfuscatedTag = _objc_basicTagToObfuscatedTag(tag);
    return &objc_tag_classes[obfuscatedTag];
#else
    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
#   if SUPPORT_MSB_TAGGED_POINTERS
    return &objc_tag_classes[0x8 | obfuscatedTag];
#   else
    return &objc_tag_classes[(obfuscatedTag << 1) | 1];
#   endif
#endif
}
  • tag类标识,进行了一系列的位运算
  • 根据判断是macOS or iOS,来获取objc_tag_classes数组里面的类指针

生成Tagged Pointer指针

static inline void * _Nonnull
_objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value)
{
    return _objc_makeTaggedPointer_withObfuscator(tag, value, objc_debug_taggedpointer_obfuscator);
}

__attribute__((no_sanitize("unsigned-shift-base")))
static inline void * _Nonnull
_objc_makeTaggedPointer_withObfuscator(objc_tag_index_t tag, uintptr_t value,
                                       uintptr_t obfuscator)
{
    if (tag <= OBJC_TAG_Last60BitPayload) {
        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);
    }
}
  • 根据负载内容位进行区分:传入的tag为类标识,同时也可以用于区分负载数据容量,苹果根据不同的负载数据容量对Tagged Pointer进行了不同的处理
  • 对传入objc_tag_index_t tag和value进行位运算:
    • 以NSNumber *a = @(1); 为例:
      1. tag 为OBJC_TAG_NSNumber(3) 二进制:0b011,16进制为 0x0000000000000003,负载数据容量 为 OBJC_TAG_Last60BitPayload
      2. value 为 数据数值(1) + 数据类型(int 为 2) 16进制为 0x0000000000000012
    • 在 iOS 下 源码中的宏定义:
      1. _OBJC_TAG_MASK :#define _OBJC_TAG_MASK (1UL<<63)
      2. _OBJC_TAG_INDEX_SHIFT:#define _OBJC_TAG_INDEX_SHIFT 60
      3. _OBJC_TAG_PAYLOAD_RSHIFT:#define _OBJC_TAG_PAYLOAD_RSHIFT 4
    • 对tag 和 value进行运算得到指针result:uintptr_t result = (_OBJC_TAG_MASK | ((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) | ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT));
      1. (uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT):tag 为 0x0000000000000003 左移 _OBJC_TAG_INDEX_SHIFT(60) 得到十六进制: 0x3000000000000000
      2. ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT):value 为0x0000000000000012,位运算后为 0x0000000000000012
      3. result 为 _OBJC_TAG_MASK(1UL<<63) 和 0x3000000000000000 和 0x0000000000000012 进行 “或” 操作
      4. result 为 0xb000000000000012
  • 进行编码(数据混淆,数据保护):对result (0xb000000000000012)进行编码,我们看下:
static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
{
    return _objc_encodeTaggedPointer_withObfuscator(ptr, objc_debug_taggedpointer_obfuscator);
}

static inline void * _Nonnull
_objc_encodeTaggedPointer_withObfuscator(uintptr_t ptr, uintptr_t obfuscator)
{
    uintptr_t value = (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(const void * _Nullable ptr)
{
    return _objc_decodeTaggedPointer_withObfuscator(ptr, objc_debug_taggedpointer_obfuscator);
}

static inline uintptr_t
_objc_decodeTaggedPointer_withObfuscator(const void * _Nullable ptr,
                                         uintptr_t obfuscator)
{
    uintptr_t value
      = _objc_decodeTaggedPointer_noPermute_withObfuscator(ptr, obfuscator);
#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;
}

无论是编码还是解码,都是对tagged pointersobjc_debug_taggedpointer_obfuscator来进行 “异或” 操作

相关题目

执行下面两段代码:

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; ++i) {
    dispatch_async(queue, ^{
        person.name = [NSString stringWithFormat: @"abcdefghij"];
    });
}
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; ++i) {
    dispatch_async(queue, ^{
        person.name = [NSString stringWithFormat: @"abcdefghi"];
    });
}

这两段代码的区别就是字符串长度少了一位,恰这个临界长度导致了字符串类型的不同:

第一段代码中
nameNSCFString类型
在这里插入图片描述

看第一段代码崩溃的地方,objc_release函数执行报错:
在这里插入图片描述

__NSCFString存储在堆上,它是个正常对象,需要维护引用计数的。name通过setter方法为其赋值。而setter方法的实现如下:

- (void)setName:(NSString *)name {
    if(_name != name) {
        [_name release];
        _name = [name copy]; // or [name retain]
    }
}

异步并发执行setter方法,可能就会有多条线程同时执行[_name release]连续release两次就会造成对象的过度释放,导致Crash

解决方案:

  1. 使用atomic原子性关键字

    @property (atomic, copy)NSString* name;
    
  2. 加锁

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for (int i = 0; i < 1000; ++i) {
        dispatch_async(queue, ^{
       	   // 加锁
           person.name = [NSString stringWithFormat: @"abcdefghij"];
           // 解锁
        });
    }
    

第二段代码中
name中是NSTaggedPointerString类型

在这里插入图片描述

objc_release函数中会判断指针是不是TaggedPointer类型,是的话就不对对象进行release操作,也就避免了因过度释放对象而导致的Crash,因为根本就没执行释放操作

__attribute__((aligned(16), flatten, noinline))
void 
objc_release(id obj)
{
    if (!obj) return;
    if (obj->isTaggedPointer()) return;  // 直接返回,不会释放
    return obj->release();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1940540.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[MySQL][深入理解隔离性][上][MVCC]详细讲解

目录 0.铺垫1.初识MVCC2.三个记录隐藏列字段1.是什么&#xff1f;2.示例 3.undo日志4.模拟MVCC5.思考 0.铺垫 在RR级别的时候&#xff0c;多个事务的update&#xff0c;多个事务的insert&#xff0c;多个事务的delete&#xff0c;是否会有加锁现象&#xff1f; 现象结果是&…

【C++】deque以及优先级队列

容器适配器 deque的介绍deque的原理介绍 priority_queue的介绍与使用priority_queue的介绍priority_queue的使用constructor&#xff08;构造函数&#xff09;emptypushpoptopsize priority_queue的模拟实现 仿函数何为适配器容器适配器deque的缺陷选择deque作为适配器的理由ST…

保姆级教程:十分钟快速上手Coze自定义插件

&#x1f43c; 关注我, 了解更多 AI 前沿资讯和玩法&#xff0c;AI 学习之旅上&#xff0c;我与您一同成长&#xff01; 我已经不止一次听到这样的话&#xff1a;未来的 AI 应用属于各行各业的智能体 (Agent) &#xff5e; 无论是国内的 扣子Coze、Dify&#xff0c;还是国外的…

网易易盾图标点选验证码识别代码

简介 网易图标点选一直都是一个大难题&#xff0c;如上图所示。难点之一是图标变幻莫测&#xff0c;很难刷出有重复的图标&#xff0c;所以使用传统等等方式去标注、识别具有较大的难度。 经过我们大量的数据标注&#xff0c;终于完成了这款验证码的识别。 目前我们提供两种识…

FPGA:频闪灯设计

1、需求 若在FPGA上实现LED灯一秒闪烁一次&#xff0c;先进行计算&#xff0c;1秒闪烁一次&#xff0c;即周期为1秒&#xff0c;开发板XC7A35TFFG-2的基本时钟输入由板载 50MHz 有源晶振提供&#xff0c;即频率为f 50MHz 。 则一个周期为 T 1 f 1 50 M H z 20 n s T\frac{…

C++初阶:模版初阶【范式编程】【函数模板】【类模板】

一.范式编程 我们在写C函数重载的时候&#xff0c;可能会写许多同一类的函数。 比如交换函数&#xff1a; void Swap(int& left, int& right) {int temp left;left right;right temp; }void Swap(double& left, double& right) {double temp left;left …

多线程.下

目录 1.线程等待 2.join&#xff08;&#xff09;介绍 3.获取当前对象引用 4.线程的状态 5.线程安全 6.synchronized()关键字 7.synchronized关键字底层介绍 1.线程等待 对于操作系统而言&#xff0c;内部多个线程的执行是“随机调度&#xff0c;抢占式执行”的。简而言…

孟浩然,山水田园一山人

孟浩然&#xff0c;字浩然&#xff0c;号孟山人&#xff0c;生于唐睿宗永昌元年&#xff08;公元689年&#xff09;&#xff0c;卒于唐玄宗开元二十八年&#xff08;公元740年&#xff09;&#xff0c;享年51岁。他出生于盛唐时期的襄州襄阳&#xff08;今湖北省襄阳市&#xf…

网络安全常见错误及解决办法(更新中)

# 开启代理&#xff0c;无法连接网络 把代理关掉 # 上一秒还在安装tree&#xff0c;下一秒xshell就连接不上了 —》sshd服务的key这个文件权限过高&#xff0c;跟装tree没有关系&#xff0c;装一个epel 源&#xff0c;epel-release​ 部分命令&#xff1a;chmod 600 /etc/ssh…

QXlsx读写excel

QXlsx读写excel 安装 QXlsx使用 qmake使用 CMake 基本用法1. 写入 Excel 文件2. 读取 Excel 文件 详细用法1. 设置单元格样式2. 合并单元格3. 创建图表4. 设置列宽和行高 完整示例 QXlsx 是一个用于在 Qt 应用中读写 Excel 文件的第三方库。它提供了丰富的 API&#xff0c;可以…

人工智能与社交变革:探索Facebook如何领导智能化社交平台

在过去十年中&#xff0c;人工智能&#xff08;AI&#xff09;技术迅猛发展&#xff0c;彻底改变了我们与数字世界互动的方式。Facebook作为全球最大的社交媒体平台之一&#xff0c;充分利用AI技术&#xff0c;不断推动社交平台的智能化&#xff0c;提升用户体验。本文将深入探…

昇思25天学习打卡营第23天|ResNet50图像分类

课程打卡凭证 ResNet网络 ResNet&#xff08;Residual Networks&#xff0c;残差网络&#xff09;是一种深度神经网络结构&#xff0c;它的核心思想是引入了“残差学习”来解决深度网络中的退化问题。在深度神经网络中&#xff0c;当网络层数增加到一定程度时&#xff0c;网络…

解读:基于图的大模型提示技术

【引子】大模型的兴起&#xff0c; 使得读论文成为了学习中的一种常态。如果一篇论文没有读懂&#xff0c;不用担心&#xff0c;可以再读一篇该领域内容相近的论文&#xff0c;量变可能会产生质变。就像编程语言一样&#xff0c;你永远无法精通一门编程语言&#xff0c;除非&am…

【Gin】精准应用:Gin框架中工厂模式的现代软件开发策略与实施技巧(上)

【Gin】精准应用&#xff1a;Gin框架中工厂模式的现代软件开发策略与实施技巧(上) 大家好 我是寸铁&#x1f44a; 【Gin】精准应用&#xff1a;Gin框架中工厂模式的现代软件开发策略与实施技巧(上)✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 本次文章分为上下两部分&…

linux进程优先级——优先值、调度算法、进程性质

前言&#xff1a;本篇内容主要讲解linux下进程的优先级。 优先级的内容相对较少&#xff0c; 最重要的内容就是cpu的调度方法。 内容相对容易理解。 ps&#xff1a;本节内容适合了解冯诺依曼和操作系统的管理方式以及进程PCB的友友们进程观看 进程的优先级是什么 进程的优先级…

【制作100个unity游戏之31】用unity制作一个爬坡2d赛车小游戏

最终效果 【制作100个unity游戏之31】用unity制作一个爬坡2d赛车小游戏 前言 今天用unity制作一个简单的爬坡2d赛车小游戏 素材 https://www.spriters-resource.com/mobile/hillclimbracing/ 拼装车素材 车身添加碰撞体&#xff0c;摩檫力0 轮胎添加碰撞体和刚体&#xff0…

CVE-2020-7248 OpenWRT libubox堆栈溢出漏洞复现(更新中)

提要 该文档会一直处于更新当中&#xff0c;当状态为完毕后&#xff0c;才是更新完成。由于网络上关于该漏洞原理的分析文档和资源实在是太少&#xff0c;而本人关于该方向也才是刚入门&#xff0c;能力有限&#xff0c;所以复现需要的时间较长&#xff0c;需要补充和学习的东西…

《从C/C++到Java入门指南》- 9.字符和字符串

字符和字符串 字符类型 Java 中一个字符保存一个Unicode字符&#xff0c;所以一个中文和一个英文字母都占用两个字节。 // 计算1 .. 100 public class Hello {public static void main(String[] args) {char a A;char b 中;System.out.println(a);System.out.println(b)…

Spring通过工厂方法进行配置

在Spring的世界中&#xff0c; 我们通常会利用 xml配置文件 或者 annotation注解方式来配置bean实例&#xff01; 在第一种利用 xml配置文件 方式中&#xff0c; 还包括如下三小类 反射模式&#xff08;我们前面的所有配置都是这种模式&#xff09;工厂方法模式Factory Bean模…

跳妹儿学编程之ScratchJr(10):结束积木篇—放学回家

博主资深软件架构师&#xff0c;拥有13年大型软件与互联网系统开发、设计和架构经验&#xff0c;曾就职于华为&#xff0c;现任职于国内知名互联网公司。平时在家教咱家“跳妹儿”编程&#xff0c;并将心得和过程记录下来。希望可以帮助更多对编程感兴趣的家庭。 前言 继上一篇…