【iOS】Tagged Pointer

目录

    • 前言
    • 什么是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,直接返回 thisif (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 payloadsOBJC_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 payloadsOBJC_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 setOBJC_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_obfuscatorOBJC_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,// 防止他们正依赖TaggedPointerobjc_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 == 7Class *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_POINTERSif (tag >= OBJC_TAG_FirstUnobfuscatedSplitTag)return &objc_tag_ext_classes[index];
#endifuintptr_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_POINTERSextern "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_POINTERSuintptr_t obfuscatedTag = _objc_basicTagToObfuscatedTag(tag);return &objc_tag_classes[obfuscatedTag];
#elseuintptr_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_POINTERSreturn &objc_tag_classes[0x8 | obfuscatedTag];
#   elsereturn &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_POINTERSif ((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;
#endifreturn (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_POINTERSuintptr_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;
#endifreturn 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.mzph.cn/bicheng/49115.shtml

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

相关文章

Qt绘制指南针(仪表盘绘制封装使用)

指南针是一种用来确定方向的工具。它由一个磁针制成&#xff0c;其一端被磁化&#xff0c;可以自由旋转。当放置在水平面上时&#xff0c;磁针会指向地球的磁北极。通过观察磁针的指向&#xff0c;我们可以确定地理北方的方向。本示例是在Qt中绘制一个指南针&#xff0c;通过继…

Android WebViewClient 的 `shouldOverrideUrlLoading` 方法

简介 在Android开发中&#xff0c;WebView是一个强大的工具&#xff0c;可以在你的应用中显示网页内容。了解 WebViewClient 中的 shouldOverrideUrlLoading 方法是至关重要的&#xff0c;因为这个方法允许你控制 URL 在 WebView 中的处理方式。 在本文中&#xff0c;我们将详…

S71200 - 笔记

1 S71200 0 ProfiNet - 2 PLC编程 01.如何零基础快速上手S7-1200_哔哩哔哩_bilibili 西门子S7-1200PLC编程设计学习视频&#xff0c;从入门开始讲解_哔哩哔哩_bilibili

Linux:进程信号(一.认识信号、信号的产生及深层理解、Term与Core)

上次结束了进程间通信的知识介绍&#xff1a;Linux&#xff1a;进程间通信&#xff08;二.共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量 文章目录 1.认识信号进程看待信号方式 2.信号的产生2.1信号的处理的方式 --- signal()函数2.2kill指令产生信号2.3键盘产生…

最新快乐二级域名分发系统重置版v1.7源码-最新美化版+源码+可对接支付

源码简介&#xff1a; 最新快乐二级域名分发系统重置版v1.7源码&#xff0c;它是最新美化版源码可对接支付。 快乐二级域名分发系统重置版v1.7源码&#xff0c;简单快捷、功能强大的控制面板。系统稳定长久&#xff0c;控制面板没任何广告&#xff0c;让网站更实用方便。 最…

现货白银交易中spring形态的应用

在现货白银市场中交易想取得成功并从市场中获利&#xff0c;掌握一些工具是必不可少的&#xff0c;而今天我们要介绍的现货白银的交易工具就是spring形态。 对于spring这个英文&#xff0c;我们都很熟悉&#xff0c;它有春天的意思&#xff0c;但这里所说的spring形态并不是指春…

ComfyUI进阶:Comfyroll节点 (最终篇)+应用实例

前言&#xff1a; 学习ComfyUI是一场持久战&#xff0c;而Comfyroll 是一款功能强大的自定义节点集合&#xff0c;专为 ComfyUI 用户打造&#xff0c;旨在提供更加丰富和专业的图像生成与编辑工具。借助这些节点&#xff0c;用户可以在静态图像的精细调整和动态动画的复杂构建…

【LabVIEW作业篇 - 5】:水仙花数、数组与for循环的连接

文章目录 水仙花数数组与for循环的连接 水仙花数 水仙花数&#xff0c;是指一个3位数&#xff0c;它的每个位上的数字的3次幂之和等于它本身。如371 3^3 7^3 1^3&#xff0c;则371是一个水仙花数。 思路&#xff1a;水仙花数是一个三位数&#xff0c;通过使用for循环&#xf…

redis的使用场景和持久化方式

redis的使用场景 热点数据的缓存。热点&#xff1a;频繁读取的数据。限时任务的操作&#xff1a;短信验证码。完成session共享的问题完成分布式锁。 redis的持久化方式 什么是持久化&#xff1a;把内存中的数据存储到磁盘的过程&#xff0c;同时也可以把磁盘中的数据加载到内存…

FPGA实现二选一数据选择器

在FPGA开发当中&#xff0c;我们最早开始接触的就是关于二选一选择器的设计。 1、原理 通过一个sel选择位判断输出out为a还是b&#xff0c;这里我们规定&#xff1a; sel0时&#xff0c;outa sel1时&#xff0c;outb 2、工程代码 多路选择器的缩写为MUX&#xff0c;这里我们见…

Git报错fatal: detected dubious ownership in repository

报错信息 fatal: detected dubious ownership in repository at 解决办法 一行代码解决 git config --global --add safe.directory "*";如何使用git工具初始胡项目并且和远程仓库建立联系 git init–建立一个本地仓库 git add README.md–将README.md文件加入…

【Day1415】Bean管理、SpringBoot 原理、总结、Maven 高级

0 SpringBoot 配置优先级 从上到下 虽然 springboot 支持多种格式配置文件&#xff0c;但是在项目开发时&#xff0c;推荐统一使用一种格式的配置 &#xff08;yml是主流&#xff09; 1 Bean管理 1.1 从 IOC 容器中获取 Bean 1.2 Bean 作品域 可以通过注解 Scope("proto…

计算机网络八股文(后续更新)

文章目录 一、计算机网络体系结构1、计算机网络的各层协议及作用&#xff1f; 二、物理层三、数据链路层四、网络层五、传输层1、TCP和UDP的区别&#xff1f;2、UDP 和 TCP 对应的应用场景是什么&#xff1f;3、详细介绍一下 TCP 的三次握手机制4、为什么需要三次握手&#xff…

测试管理工具、自动化测试工具、跨浏览器测试工具 推荐

测试管理工具 1&#xff09;Xray Xray 是排名第一的手动与自动化测试管理应用&#xff0c;专为质量保证而设计。它是一个功能齐全的工具&#xff0c;能够无缝集成于 Jira 中。其目的是通过有效和高效的测试帮助公司提高产品质量。 功能特点&#xff1a; 需求、测试、缺陷和执…

Java | Leetcode Java题解之第275题H指数II

题目&#xff1a; 题解&#xff1a; class Solution {public int hIndex(int[] citations) {int n citations.length;int left 0, right n - 1;while (left < right) {int mid left (right - left) / 2;if (citations[mid] > n - mid) {right mid - 1;} else {lef…

深入浅出mediasoup—协议交互

本文主要分析 mediasoup 一对一 WebRTC 通信 demo 的协议交互&#xff0c;从协议层面了解 mediasoup 的设计与实现&#xff0c;这是深入阅读 mediasoup 源码的重要基础。 1. 时序图 下图是 mediasoup 客户端-服务器协议交互的总体架构&#xff0c;服务器是一个 Node.js 进程加…

【计算机毕业设计】877陪诊系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

使用nginx实现一个端口和ip访问多个vue前端

前言&#xff1a;由于安全组要求&#xff0c;前端页面只开放一个端口&#xff0c;但是项目有多个前端&#xff0c;此前一直使用的是一个前端使用单独一个端口进行访问&#xff0c;现在需要调整。 需要实现&#xff1a;这里以80端口为例&#xff0c;两个前端分别是&#xff1a;p…

AI 驱动下的一体化分布式数据库:滴滴、快手、中国恩菲、好未来、翼鸥教育共话创新应用实践|OceanBase Meetup 精彩回顾

7月6日&#xff0c;OceanBase Meetup 北京站——“AI 驱动下的一体化分布式数据库&#xff1a;跨行业多场景的创新应用与实战”举办。来自滴滴、快手、中国恩菲、好未来、翼鸥教育、蚂蚁集团及OceanBase等众多行业技术专家与资深用户&#xff0c;围绕众多用户关注的AI 与数据库…

物联网mqtt网关搭建背后的技术原理

前言 物联网是现在比较热门的软件领域&#xff0c;众多物联网厂商都有自己的物联网平台&#xff0c;而物联网平台其中一个核心的模块就是Mqtt网关。这篇文章的目的是手把手教大家写书写一个mqtt网关&#xff0c;后端存储支持Kafka/Pulsar&#xff0c;支持mqtt 连接、断链、发送…