当前位置: 首页 > news >正文

【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();
}

相关文章:

【iOS】Tagged Pointer

目录 前言什么是Tagged Pointer&#xff1f;引入Tagged Pointer技术之前引入Tagged Pointer之后总结 Tagged Pointer原理&#xff08;TagData分析&#xff09;关闭数据混淆MacOS分析NSNumberNSString iOS分析 判断Tagged PointerTagged Pointer应用Tagged Pointer 注意点 Tagge…...

Mysql explain 优化解析

explain 解释 select_type 效率对比 MySQL 中 EXPLAIN 语句的 select_type 列描述了查询的类型,不同的 select_type 类型在效率上会有所差异。下面我们来比较一下各种 select_type 的效率: SIMPLE: 这是最简单的查询类型,表示查询不包含子查询或 UNION 操作。 这种查询通常是…...

wget下载github文件得到html文件

从github/gitee下载源文件&#xff0c;本来是22M下载下来只有11k 原因&#xff1a; Github会提供html页面&#xff0c;包括指定的文件、上下文与相关操作。通过wget或者curl下载时&#xff0c;会下载该页面 解决方式&#xff1a; github点击Code一栏的raw按钮&#xff0c;获得源…...

【es】elasticsearch 自定义排序-按关键字位置排序

一 背景 要求es查询的结果按关键字位置排序&#xff0c;位置越靠前优先级越高。 es版本7.14.0&#xff0c;项目是thrift&#xff0c;也可以平替springboot&#xff0c;使用easyes连接es。 二 easyes使用 配easyes按官方文档就差不多了 排序 | Easy-Es 主要的一个问题是easy…...

堆的相关知识点

目录 大小堆 堆的实现 堆的创建 堆的销毁 交换 向上调整 向下调整 弹出首个元素 取出首个元素 判空 堆插入 大小堆 大堆&#xff1a;最上面的数字是最小的&#xff0c;越往下越大 小堆&#xff1a;最上面的数字是最大的&#xff0c;越往下越小 堆的复杂程度&#…...

【Sass】常用全局sass高级函数,可使用原子化CSS减轻代码量,方便快速开发

文章目录 前言一、安装二、样式custom.scssflex.scsscolor.scssmargin-padding.scssorther 总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 针对style的预编译器为scss 转载自git前端知识库 原博主是B站up程序员郑清&#xff0c;可以看他的v3教程…...

MYSQL 第四次作业

任务要求&#xff1a; 具体操作&#xff1a; 新建数据库&#xff1a; mysql> CREATE DATABASE mydb15_indexstu; Query OK, 1 row affected (0.01 sec) mysql> USE mydb15_indexstu; Database changed 新建表&#xff1a; mysql> CREATE TABLE student( ->…...

depcheck 前端依赖检查

介绍 depcheck 是一款用于检测项目中 未使用依赖项 的工具。 depcheck 通过扫描项目文件&#xff0c;帮助你找出未被引用的依赖&#xff0c;从而优化项目。 优势&#xff1a; 简单易用: 仅需几个简单的命令&#xff0c;就能够扫描并列出未使用的依赖项&#xff0c;让你快速了…...

Qt/C++音视频开发79-采集websocket视频流/打开ws开头的地址/音视频同步/保存到MP4文件/视频回放

一、前言 随着音视频的爆发式的增长,各种推拉流应用场景应运而生,基本上都要求各个端都能查看实时视频流,比如PC端、手机端、网页端,在网页端用websocket来接收并解码实时视频流显示,是一个非常常规的场景,单纯的http-flv模式受限于最大6个通道同时显示,一般会选择ws-f…...

网络安全等级保护制度1.0与2.0的演进与变革

等保1.0概述 等保1.0是我国在网络安全领域迈出的重要一步&#xff0c;它于2008年正式发布。该版本的等保制度以《信息安全技术 信息系统安全等级保护基本要求》为核心标准&#xff0c;主要聚焦于信息系统的物理安全、网络安全、主机安全、应用安全和数据安全等方面的基础防护。…...

多线程优化API请求:CountDownLatch与PriorityBlockingQueue的应用

目录 前言 CountDownLatch是什么&#xff1f; PriorityBlockingQueue是什么&#xff1f; 场景描述 解决方案 定义统一工厂制造类 定义制造厂 定义客户请求实现 定义控制器 定义启动类 结果呈现 启动项目 请求制造操作 总结 前言 写这篇文章的缘由是因为之前在面…...

谷粒商城实战笔记-54-商品服务-API-三级分类-拖拽效果

文章目录 一&#xff0c;54-商品服务-API-三级分类-修改-拖拽效果1&#xff0c;el-tree控件加上允许拖拽的属性2&#xff0c;是否允许拖拽3&#xff0c;完整代码 一&#xff0c;54-商品服务-API-三级分类-修改-拖拽效果 本节的主要内容是给三级分类树形结构加上拖拽功能&#…...

AI大模型学习必备十大网站

随着人工智能技术的快速发展&#xff0c;AI大模型&#xff08;如GPT-3、BERT等&#xff09;在自然语言处理、计算机视觉等领域取得了显著的成果。对于希望深入学习AI大模型的开发者和研究者来说&#xff0c;找到合适的学习资源至关重要。本文将为大家推荐十大必备网站&#xff…...

Elasticsearch:Golang ECS 日志记录 - zap

ECS 记录器是你最喜欢的日志库的格式化程序/编码器插件。它们可让你轻松地将日志格式化为与 ECS 兼容的 JSON。 编码器以 JSON 格式记录日志&#xff0c;并在可能的情况下依赖默认的 zapcore/json_encoder。它还处理 ECS 错误格式的错误字段记录。 默认情况下&#xff0c;会添…...

关于线性代数(考研)

1.AE的特征值的问题 若λ是A的特征值&#xff0c;对应的特征向量是x&#xff0c;则Axλx&#xff0c;所以(AE)xAxExλxx(λ1)x&#xff0c;所以λ1是AE的特征值。所以若A的特征值是1&#xff0c;1&#xff0c;0&#xff0c;则AE的特征值就是11&#xff0c;11&#xff0c;01&am…...

【java基础】spring springMVC springboot 的区别

Spring, Spring MVC, 和 Spring Boot 是三个紧密相关的技术&#xff0c;它们都是由 Pivotal 团队&#xff08;原SpringSource&#xff09;开发的&#xff0c;主要用于构建企业级的Java应用程序。尽管它们在功能上有所交集&#xff0c;但各自也有独特的定位和用途。 Spring Fra…...

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 开源项目热度排行榜(100分) - 三语言AC题解(Python/Java/Cpp)

🍭 大家好这里是清隆Coding ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 👏 感谢大家的订阅➕ 和 喜欢💗 🍿 最新华为OD机试D卷目录,全、新、准,题目覆盖率达 95% 以上,支持题目在线评测,专栏文章质量平均 93 分 最新华为OD机试目录…...

大模型算法面试题(十一)

本系列收纳各种大模型面试题及答案。 1、说一下目前主流或前沿的预训练模型&#xff0c;包括nlp&#xff08;百度ERNIE3.0&#xff0c;华为NEZHA&#xff0c;openAI gpt-3&#xff0c;nvidia MegatronLM&#xff0c;macrosoft T5&#xff09;和cv&#xff08;我只知道CLIP&…...

CSS 基础知识

CSS(级联样式表)是设置 Web 内容样式的代码。CSS 基础知识将介绍入门所需的内容。我们将回答以下问题:如何将文本设置为红色?如何使内容显示在(网页)布局中的某个位置?如何用背景图片和颜色装饰我的网页? 什么是CSS? 像HTML一样,CSS不是一种编程语言。它也不是一种标…...

IntelliJ IDEA 和 Eclipse的区别

IntelliJ IDEA 和 Eclipse 是两个非常流行的 Java 集成开发环境&#xff08;IDE&#xff09;&#xff0c;它们各自具有不同的特点和优势。下面是它们之间的一些主要对比&#xff1a; 性能和资源使用 IntelliJ IDEA 被认为在某些方面更加智能&#xff0c;能够提供更好的代码分…...

Ansible之playbook剧本编写(二)

tags 模块 可以在一个playbook中为某个或某些任务定义“标签”&#xff0c;在执行此playbook时通过ansible-playbook命令使用--tags选项能实现仅运行指定的tasks。 playbook还提供了一个特殊的tags为always。作用就是当使用always作为tags的task时&#xff0c;无论执行哪一个t…...

力扣第二十九题——两数相除

内容介绍 给你两个整数&#xff0c;被除数 dividend 和除数 divisor。将两数相除&#xff0c;要求 不使用 乘法、除法和取余运算。 整数除法应该向零截断&#xff0c;也就是截去&#xff08;truncate&#xff09;其小数部分。例如&#xff0c;8.345 将被截断为 8 &#xff0c;-…...

解析三款热门的文献翻译工具:优势与使用指南

今儿咱们来聊聊那些让咱们头疼又不得不面对的事儿——文献翻译。在浩瀚的学术海洋里遨游时&#xff0c;遇到外文文献那是家常便饭&#xff0c;但语言障碍就像海上的迷雾&#xff0c;一不小心就能让你偏离航向。别担心&#xff0c;我这不就带着几款亲测好用的文献翻译神器来了嘛…...

git 过滤LFS文件下载

git config --global filter.lfs.smudge "git-lfs smudge --skip -- %f" git config --global filter.lfs.process "git-lfs filter-process --skip" 恢复下载 git config --global filter.lfs.smudge "git-lfs smudge -- %f" git config --g…...

内存泄漏详解

文章目录 什么是内存泄漏内存泄漏的原因排查及解决内存泄漏避免内存泄漏及时释放资源设置合理的变量作用域及时清理不需要的对象避免无限增长避免内部类持有外部类引用使用弱引用 什么是内存泄漏 内存泄漏是指不使用的对象持续占有内存使得内存得不到释放&#xff0c;从而造成…...

多角度解析高防CDN防御DDOS及CC攻击

网络攻击的形式也日益多样化&#xff0c;其中DDoS&#xff08;分布式拒绝服务&#xff09;和CC&#xff08;Challenge Collapsar&#xff09;攻击尤为突出&#xff0c;给网站和企业带来了巨大的安全威胁。高防CDN&#xff08;Content Delivery Network&#xff09;作为一种专业…...

(7) cmake 编译C++程序(二)

文章目录 概要整体代码结构整体代码小结 概要 在ubuntu下&#xff0c;通过cmake编译一个稍微复杂的管理程序 整体代码结构 整体代码 boss.cpp #include "boss.h"Boss::Boss(int id, string name, int dId) {this->Id id;this->Name name;this->DeptId …...

C语言系统调用linux文件系统

在C语言中&#xff0c;open、write和read函数是系统调用&#xff08;system calls&#xff09;&#xff0c;它们直接由操作系统提供&#xff0c;用于底层的文件操作。这些函数是UNIX和类UNIX系统&#xff08;如Linux&#xff09;中的标准接口&#xff0c;不同于C标准库中的文件…...

LeetCode142 环形链表 II

前言 题目&#xff1a; 142. 环形链表 II 文档&#xff1a; 代码随想录——环形链表 II 编程语言&#xff1a; C 解题状态&#xff1a; 思路错误&#xff0c;链表不允许被修改 思路 两步走&#xff0c;第一步&#xff0c;判断有没有环&#xff0c;第二步&#xff0c;判断入环口…...

逆向案例二十八——某高考志愿网异步请求头参数加密,以及webpack

网址&#xff1a;aHR0cDovL3d3dy54aW5nYW9rYW90Yi5jb20vY29sbGVnZXMvc2VhcmNo 抓包分析&#xff0c;发现请求头有参数u-sign是加密的&#xff0c;载荷没有进行加密&#xff0c;直接跟栈分析。 进入第二个栈&#xff0c;打上断点&#xff0c;分析有没有加密位置。 可以看到参数…...

WebKit的文本装饰艺术:CSS Text Decoration全解析

WebKit的文本装饰艺术&#xff1a;CSS Text Decoration全解析 CSS文本装饰&#xff08;Text Decoration&#xff09;是一组用于美化和增强网页文本表现的属性&#xff0c;它们可以为文本添加下划线、上划线、线删除和强调标记等效果。WebKit作为许多现代浏览器的渲染引擎&…...

【linux】Shell脚本三剑客之sed命令的详细用法攻略

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全…...

解析class字节码文件获取魔数和版本号

写在前面 本文看下如何获取class字节码文件的魔数和版本号信息。 1&#xff1a;正文 需要对class字节码的结构有一定的了解&#xff0c;可以参考这篇文章 。 直接看代码&#xff1a; package org.example;import java.math.BigInteger;public class TTTT {//取部分字节码&…...

技术文档总结----思维导图

性能调优| ProcessOn免费在线作图,在线流程图,在线思维导图 mysql| ProcessOn免费在线作图,在线流程图,在线思维导图 kafka| ProcessOn免费在线作图,在线流程图,在线思维导图 mybatis缓存| ProcessOn免费在线作图,在线流程图,在线思维导图 java锁| ProcessOn免费在线作图,在…...

【iOS】—— retain\release实现原理和属性关键字

【iOS】—— retain\release实现原理和属性关键字 1. retain\reelase实现原理1.1 retain实现原理1.2 release实现原理 2. 属性关键字2.1 属性关键字的分类2.2 内存管理关键字2.2.1 weak2.2.2 assgin2.3.3 strong和copy 2.4 线程安全的关键字2.5 修饰变量的关键字2.5.1常量const…...

这一文,关于Java泛型的点点滴滴 一

作为一个 Java 程序员&#xff0c;用到泛型最多的&#xff0c;我估计应该就是这一行代码&#xff1a; List<String> list new ArrayList<>();这也是所有 Java 程序员的泛型之路开始的地方啊。 不过本文讲泛型&#xff0c;先不从这里开始讲&#xff0c;而是再往前…...

微信小程序之调查问卷

一、设计思路 1、界面 调查问卷又称调查表&#xff0c;是以问题的形式系统地记载调查内容的一种形式。微信小程序制作的调查问卷&#xff0c;可以在短时间内快速收集反馈信息。具体效果如下所示&#xff1a; 2、思路 此调查问卷采用服务器客户端的方式进行设计&#xff0c;服…...

基于Qt的视频剪辑

在Qt中进行视频剪辑可以通过多种方式实现&#xff0c;但通常需要使用一些额外的库来处理视频数据。以下是一些常见的方法和步骤&#xff1a; 使用FFmpeg FFmpeg是一个非常强大的多媒体框架&#xff0c;可以用来处理视频和音频数据。你可以使用FFmpeg的命令行工具或者其库来实现…...

electron 网页TodoList工具打包成win桌面应用exe

参考&#xff1a; electron安装&#xff08;支持win、mac、linux桌面应用&#xff09; https://blog.csdn.net/weixin_42357472/article/details/140643624 TodoList工具 https://blog.csdn.net/weixin_42357472/article/details/140618446 electron打包过程&#xff1a; 要将…...

数据结构之判断二叉树是否为搜索树(C/C++实现)

文章目录 判断二叉树是否为搜索树方法一&#xff1a;递归法方法二&#xff1a;中序遍历法总结 二叉树是一种非常常见的数据结构&#xff0c;它在计算机科学中有着广泛的应用。二叉搜索树&#xff08;Binary Search Tree&#xff0c;简称BST&#xff09;是二叉树的一种特殊形式&…...

golang长连接的误用

误用一&#xff1a;忘记读取响应的body 由于忘记读取响应的body导致创建大量处于TIME_WAIT状态的连接&#xff08;同时产生大量处于transport.go的readLoop和writeLoop的协程&#xff09; 在linux下运行下面的代码: package mainimport ("fmt""html"&qu…...

Springboot @Validate @Valid 基于复杂嵌套对象的参数校验示例

Springboot Validate Valid 基于复杂嵌套对象的参数校验示例 复杂对象 Data public class Object1 {Length(max 50,message "长度不能超过50位字符")NotBlank(message "名称不能为空")private String name;NotNull(message "不能为空")pri…...

算力共享下的,分级路由转发报文协议与通告

目录 网络双 SLA 约束 一、双SLA约束的定义与背景 二、双SLA约束的应用场景 三、双SLA约束的管理与实施 四、双SLA约束的优势与挑战 算力共享下的,分级路由转发报文协议与通告 基础设施即服务(IaaS)类 型算力资源 函数即服务(FaaS)类型算力服务 软件即服务(SaaS…...

滚动数组详解

滚动数组详解 何为滚动数组&#xff1f;滚动数组是如何优化空间的&#xff1f;交替滚动例题&#xff1a;来自某某轮廓线DP的题目 自我滚动(~~不如交替~~ 完结&#xff01;&#xff01;&#xff01; ( 宇宙免责任书&#xff1a;我用的是C) 何为滚动数组&#xff1f; 什么是滚动…...

C 语言动态链表

线性结构->顺序存储->动态链表 一、理论部分 从起源中理解事物&#xff0c;就是从本质上理解事物。 -杜勒鲁奇 动态链表是通过结点&#xff08;Node&#xff09;的集合来非连续地存储数据&#xff0c;结点之间通过指针相互连接。 动态链表本身就是一种动态分配内存的…...

【Leetcode】二十、记忆化搜索:零钱兑换

文章目录 1、记忆化搜索2、leetcode509&#xff1a;斐波那契数列3、leetcode322&#xff1a;零钱兑换 1、记忆化搜索 也叫备忘录&#xff0c;即把已经计算过的结果存下来&#xff0c;下次再遇到&#xff0c;就直接取&#xff0c;不用重新计算。目的是以减少重复计算。 以前面提…...

json数据格式 继续学习

1.定义 轻量级的数据交互格式&#xff0c;可以按照json数据格式去组织和封装数据。 本质是一个带有特定格式的字符串。 2.功能 负责不同编程语言中的数据传递和交互。 3.json数据格式转化 """ 演示json数据和python字典之间的转换 """ impor…...

gradle 构建项目添加版本信息

gradle 构建项目添加版本信息&#xff0c;打包使用 spring boot 的打包插件 build.gradle 配置文件 bootJar {manifest {attributes(Project-Name: project.name,Project-Version: project.version,"project-Vendor": "XXX Corp","Built-By": &…...

vue3 学习笔记17 -- 基于el-menu封装菜单

vue3 学习笔记17 – 基于el-menu封装菜单 前提条件&#xff1a;组件创建完成 配置路由 // src/router/index.ts import { createRouter, createWebHashHistory } from vue-router import type { RouteRecordRaw } from vue-router export const Layout () > import(/lay…...

使用 Redis 实现验证码、token 的存储,用自定义拦截器完成用户认证、并使用双重拦截器解决 token 刷新的问题

可以看一下我以前做过的笔记&#xff1a;黑马点评 短信登录部分 基于session实现登录流程 1.发送验证码 用户在提交手机号后&#xff0c;会校验手机号是否合法&#xff0c;如果不合法&#xff0c;则要求用户重新输入手机号 如果手机号合法&#xff0c;后台此时生成对应的验…...