iOS内存管理方案有:
-
MRC
和ARC
-
Tagged Pointer
:专门用来处理小对象,例如NSNumber、NSDate、小NSString等 -
Nonpointer_isa
:非指针类型的isa,主要是用来优化64位地址 -
SideTables
:散列表,在散列表中主要有两个表,分别是引用计数表、弱引用表
Tagged Pointer
在学习Tagged Pointer
前建议先去看下2020WWDC里面关于小对象类型的更新介绍
- 普通对象:通过
指针地址
找到内存 - 小对象类型:是
指针+值
的形式,这样不用去访问内存从而就能获取到值
,这样的话效率会高很多
我们现在源码中搜索 TaggedPointer
看过2020WWDC
的应该会对playload
比较敏感,而且decoded
这个注释我们猜测小对象类型
实际上是通过加密、解密的方式
进行存储和读取
最终我们找到_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
其实就是个随机数,他是在类的加载阶段
生成的
看到
_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_obfuscator
为0
DisableTaggedPointerObfuscation
是下述代码,这里先记一下,后面要用
OPTION( DisableTaggedPointerObfuscation, OBJC_DISABLE_TAG_OBFUSCATION, "disable obfuscation of tagged pointers")
【注意】下述讲解全部基于arm64
架构下,x86
架构下会有所不同
通过上述讲解,知道了小对象类型
其实就是通过对对象
进行加密解密
的过程,接下来我们实践一下
首先DisableTaggedPointerObfuscation
这个东西是用来定义是否要加密,这个地方我们改成不加密的方式
首先我们发现这里的NSString为NSTaggedPointerString
类型
这里将
指针地址
打印成二进制
的形式接下来我们验证下
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
-
115
在ASSIC
中代表s
-
104
在ASSIC
中代表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);
});
}
}
我们发现这里发生了崩溃
这里的NSString
为小对象类型
这里的
NSString
为正常的NSString
这里发生崩溃的原因是因为,这里启用了
多线程
,导致NSString
的release 和retain
发生混乱
我们来看下retain
和release
的源码,发现这里对小对象类型进行了判断
,如果是小对象类型
直接return
由此可见小对象类型
对运行效率会大大的提升