1.内存布局
- 栈区: 函数,方法
- 堆区: 通过alloc分配的对象
- BSS段: 未初始化的全局变量,静态变量
- data数据段: 初始化的全局变量,静态变量
- text: 程序代码,加载到内存中
全局变量保存在bss+data段
局部变量保存在栈区
2. 内存管理方案TaggedPointer
比较下面两段代码的输出结果
代码1
self.queue = dispatch_queue_create("com.aaa.cn", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10000; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"a"]; // alloc 堆 iOS优化 - taggedpointer
NSLog(@"%@",self.nameStr);
});
}
代码2
self.queue = dispatch_queue_create("com.aaa.cn", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10000; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"内存管理,TaggedPointer,和谐学习不急不躁"];
NSLog(@"%@",self.nameStr);
});
}
结果:
代码1正常运行
代码2发生崩溃
代码2为什么会崩溃?
ios使用引用计数管理对象,由于多线程的影响,会在某个瞬间多条线程对同一对象释放,从而导致过度释放。
代码1为什么没有奔溃
通过lldb调试查看
nameStr 是一个NSTaggedPointerString 类型
再看代码2
代码2的属性nameStr 是__NSCFString 类型
相同的代码只是在字符串的内容不一样就导致了崩溃,主要是因为对NSString类型做了优化,称为小对象
通过查看objc781源码objc_setProperty
->reallySetProperty
->objc_retain
__attribute__((aligned(16), flatten, noinline))
id
objc_retain(id obj)
{
//判断当前对象是否是taggedPointer或者nil
if (obj->isTaggedPointerOrNil()) return obj;
return obj->retain();
}
objc在对引用计数进行加一操作的时候会先判断是否为taggedpointer类型,如果是taggedpointer类型则不会加一操作。
isTaggedPointerOrNil
->_objc_isTaggedPointerOrNil
static inline bool
_objc_isTaggedPointerOrNil(const void * _Nullable ptr)
{
// this function is here so that clang can turn this into
// a comparison with NULL when this is appropriate
// it turns out it's not able to in many cases without this
//!ptr 判断是否为nil
//判断ptr与上一个掩码是否等于掩码
return !ptr || ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
同理oc对象在进行release
时也需要判断是否taggedPointer类型。
其中,判断是否为taggedPointer类型代码如下
((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK
_OBJC_TAG_MASK
是一个宏,源码中根据架构不同分为1或者1<<63,指针与上掩码后还等于掩码。
上例子中,
ptr&mask == mask
,所以taggedpointer在内存将其中某个位标记为有值,则这个类就是taggedpointer类型。如果第64位为1则这个类是taggedpointer类型
如何变成taggedpointer类型
将某个对象变成taggedpointer
// Create a tagged pointer object with the given tag and payload.
// Assumes the tag is valid.
// Assumes tagged pointers are enabled.
// The payload will be silently truncated to fit.
static inline void * _Nonnull
_objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t payload);
_objc_makeTaggedPointer源码
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_TAG_Last60BitPayload
为6,根据传入的tag值不同,将生成不同的taggedPonter,查看tag为枚举
#if __has_feature(objc_fixed_enum) || __cplusplus >= 201103L
enum objc_tag_index_t : uint16_t
#else
typedef uint16_t objc_tag_index_t;
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,
....
OBJC_TAG_RESERVED_264 = 264
};
NSString、NSNumber等是使用第一种算法。最后将结果进行编码,_objc_encodeTaggedPointer(result);
基于objc4-781源码
static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
{
return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);
}
objc_debug_taggedpointer_obfuscator是在read_images-> initializeTaggedPointerObfuscator进行初始化,
static void
initializeTaggedPointerObfuscator(void)
{
if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) ||
// Set the obfuscator to zero for apps linked against older SDKs,
// in case they're relying on the tagged pointer representation.
DisableTaggedPointerObfuscation) {
objc_debug_taggedpointer_obfuscator = 0;
} else {
// Pull random data into the variable, then shift away all non-payload bits.
//这里获取objc_debug_taggedpointer_obfuscator值
arc4random_buf(&objc_debug_taggedpointer_obfuscator,
sizeof(objc_debug_taggedpointer_obfuscator));
objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
}
}
taggedpointer对象存储的是经过混淆后的值,有encode就存在decode,
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
- (void)viewDidLoad {
[super viewDidLoad];
NSString *str2 = [NSString stringWithFormat:@"b"];
NSLog(@"0x%lx",_objc_decodeTaggedPointer_(str2));
NSNumber *number3 = @2.0;
NSLog(@"0x%lx",_objc_decodeTaggedPointer_(number3));
}
uintptr_t
_objc_decodeTaggedPointer_(id ptr)
{
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
输出
0xa 换算为16进制 0x1010,最高为1表示这个是taggedpointer类型, 010缓存成二进制为2,对应了objc_tag_index_t 中的OBJC_TAG_NSString
为taggedpointer的情况
总结:
Tagged Pointer(用于存储NSNumber、NSDate、小NSString等)指针存储的不是地址,是具有标识的地址值。本质上可以理解为常量常量,直接进行读取。优点是
占用空间小/节省内存
Tagged Pointer类型不会进行
retain
和release
操作,意味着不需要ARC进行管理,可以直接被系统自主的释放和回收Tagged Pointer的内存并不存储在堆中,而是在常量区中,也不需要malloc和free,效率快
Tagged Pointer的64位地址中,前4位代表类型,后4位主要适用于系统做一些处理,中间56位用于存储值
参考
iOS-底层原理 33:内存管理(一)TaggedPointer/retain/release/dealloc/retainCount 底层分析