iOS内存管理(一)、内存分区和引用计数
iOS内存管理(二)alloc、retain、release、dealloc
一、内存分区
0xc0000000转化出来,正好为3GB,所以我们的运行内存最多为3GB
在动态分配内存的时候,栈区的栈帧不断往下走,而堆区随着内存开辟越多会不断往上走,当它们重合的时候,就形成了堆栈溢出。
在dyld加载可执行文件到内存的时候,它会将加载的数据给分别存放到.bss、.data、.text段。
iOS的内存分区指RAM中的内存分区,它主要分为五大区:
二、内存管理方案 isa_t
TaggedPointer:小对象-NSNumber,NSDate以及长度短的NSSTring
NONPOINTER_ISA:非指针型isa
散列表:引用计数表、弱引用表
在这里我们先介绍一下isa,它在源码中的结构为(只看arm64的)
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
struct {
//0表示普通的isa指针,1表示使用优化,存储引用计数
uintptr_t nonpointer : 1; \
//表示该对象是否包含关联对象,如果没有,则析构时会更快
uintptr_t has_assoc : 1; \
//表示该对象是否有C++或ARC的析构函数,如果没有,则析构时更快
uintptr_t has_cxx_dtor : 1; \
//类的指针
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
//固定值为0xd2,用于在调试时分辨对象是否未完成初始化
uintptr_t magic : 6; \
//表示该对象是否有过weak对象,如果没有,则析构时更快
uintptr_t weakly_referenced : 1; \
//表示该对象是否正在析构
uintptr_t deallocating : 1; \
//表示该对象的引用计数值是否过大需要额外存储到sidetable中
uintptr_t has_sidetable_rc : 1; \
//存储最多2^8-1的引用计数值,存储sidetable以外的引用计数值减1的结果
uintptr_t extra_rc : 19
};
};
三、TaggedPointer
TaggedPointer常用来存储小对象如NSNumber,NSDate以及长度短的NSSTring,TaggedPointer指针的值不再是地址了,而是真正的值,所以实际上它不再是一个对象了,它只是披着对象皮的普通变量而已!所以,它的内存并不存储在堆中,也不需要malloc和free
TaggedPointer内存读取是读取对象的3倍,创建对象过程是创建对象的106倍
一般TaggedPointer的打印的结构为tag+值+值类型
源码中,如果为taggedPointer,直接返回非类类型
inline bool objc_object::isClass()
{
//如果是TaggedPointer,返回false
if (isTaggedPointer()) return false;
return ISA()->isMetaClass();
}
inline bool objc_object::isTaggedPointer()
{
return _objc_isTaggedPointer(this);
}
static inline bool _objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
define _OBJC_TAG_MASK (1UL<<63)
在_objc_isTaggedPointer
函数中,将自身指针值与1...0
进行按位与操作,如果还是1...0
,则是使用了taggedPointer机制,也就是说,在iOS中,判断是否taggedPoint,就是看其最高位是否为1。
拓展:在MACOS中,由于define _OBJC_TAG_MASK 1UL
,所以,判断在MACOS中是否为taggedPoint,就是看其最低位是否为1
我们再看这个函数
static inline void * _Nonnull
_objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value)
{
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(result);
} 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(result);
}
}
我们不看其它部分,最终不论是哪一种形式,最后函数都会调用_objc_encodeTaggedPointer(result)
,我们再来看这个函数
uintptr_t objc_debug_taggedpointer_obfuscator = 0;
//编码方法
static inline void * _Nonnull_objc_encodeTaggedPointer(uintptr_t ptr)
{
return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);
}
//解码方法
static inline uintptr_t _objc_decodeTaggedPointer(const void * _Nullable ptr)
{
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
编码方法中,与0进行按位异或进行编码,解码方法中,同样与0进行按位异或进行解码
在最新版的objc4-781源码中,关于objc_debug_taggedpointer_obfuscator
进行更多一步的操作,在_read_images
方法中,调用initializeTaggedPointerObfuscator()
对objc_debug_taggedpointer_obfuscator
进行了初始化(做了代码混淆),我们看看这个函数的源码:
static void
initializeTaggedPointerObfuscator(void)
{
if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) || DisableTaggedPointerObfuscation) {
//老版本直接为0
objc_debug_taggedpointer_obfuscator = 0;
} else {
//新版本给一个随机数,和_OBJC_TAG_MASK按位取反后的值进行与操作
arc4random_buf(&objc_debug_taggedpointer_obfuscator,
sizeof(objc_debug_taggedpointer_obfuscator));
objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
}
}
define _OBJC_TAG_MASK (1UL<<63)
所以按照现在的源码结构,我们如果想要看到原来的tag+值+值类型
结构,需要我们将_objc_decodeTaggedPointer
方法拷贝出来,自己对它进行解码操作,
- (void)taggedPointerTest{
int num1 = 22;
long num2 = 55;
float num3 = 2.0f;
double num4 = 4.0;
double num5 = 22.33;
NSNumber *number1 = @(num1);
NSNumber *number2 = @(num2);
NSNumber *number3 = @(num3);
NSNumber *number4 = @(num4);
NSNumber *number5 = @(num5);
NSLog(@"所属类:%@---指针:%p---值:%@---0x%lx",object_getClass(number1),number1,number1,_objc_decodeTaggedPointer(number1));
NSLog(@"所属类:%@---指针:%p---值:%@---0x%lx",object_getClass(number2),number2,number2,_objc_decodeTaggedPointer(number2));
NSLog(@"所属类:%@---指针:%p---值:%@---0x%lx",object_getClass(number3),number3,number3,_objc_decodeTaggedPointer(number3));
NSLog(@"所属类:%@---指针:%p---值:%@---0x%lx",object_getClass(number4),number4,number4,_objc_decodeTaggedPointer(number4));
NSLog(@"所属类:%@---指针:%p---值:%@---0x%lx",object_getClass(number5),number5,number5,_objc_decodeTaggedPointer(number5));
}
extern uintptr_t objc_debug_taggedpointer_obfuscator;
uintptr_t
_objc_decodeTaggedPointer(id ptr)
{
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
//打印结果
// 所属类:__NSCFNumber---指针:0xfd8919a30310b479---值:22---0xb000000000000162
// 所属类:__NSCFNumber---指针:0xfd8919a30310b668---值:55---0xb000000000000373
// 所属类:__NSCFNumber---指针:0xfd8919a30310b53f---值:2---0xb000000000000024
// 所属类:__NSCFNumber---指针:0xfd8919a30310b55e---值:4---0xb000000000000045
// 所属类:__NSCFNumber---指针:0x6000013d0d80---值:22.33---0x4d8979a3022db89b
我们可以看到指针打印出来的值已经没有原来的那种含义,因为现在源码中已做了代码混淆。而在我们自己解码出来的值中,可以看到最后一位代表它的类型,2表示int类型,3代表long类型,4代表float类型,5代表double类型,而对于复杂的浮点数,如num5,最后一位并不能确定其类型。除此以外,在类型前面则显示的是它的值的16进制值。
NONPOINTER_ISA
若isa_t中位域中的nonpointer为1,表示优化过的isa,用于存储引用计数
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
struct {
//0表示普通的isa指针,1表示使用优化,存储引用计数
uintptr_t nonpointer : 1; \
//表示该对象是否包含关联对象,如果没有,则析构时会更快
uintptr_t has_assoc : 1; \
//表示该对象是否有C++或ARC的析构函数,如果没有,则析构时更快
uintptr_t has_cxx_dtor : 1; \
//类的指针
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
//固定值为0xd2,用于在调试时分辨对象是否未完成初始化
uintptr_t magic : 6; \
//表示该对象是否有过weak对象,如果没有,则析构时更快
uintptr_t weakly_referenced : 1; \
//表示该对象是否正在析构
uintptr_t deallocating : 1; \
//表示该对象的引用计数值是否过大需要额外存储到sidetable中
uintptr_t has_sidetable_rc : 1; \
//存储最多2^8-1的引用计数值,存储sidetable以外的引用计数值减1的结果
uintptr_t extra_rc : 19
};
};
现在我们来研究retainCount
在源码中的实现
- (NSUInteger)retainCount {
return _objc_rootRetainCount(self);
}
uintptr_t _objc_rootRetainCount(id obj)
{
//判断对象是否为空,为空断在这里
ASSERT(obj);
return obj->rootRetainCount();
}
inline uintptr_t objc_object::rootRetainCount()
{
//如果是taggedPointer 直接返回指针值
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
//如果是优化后的指针(存储引用计数)
if (bits.nonpointer) {
//将extra_rc值加1,rc是引用计数
uintptr_t rc = 1 + bits.extra_rc;
//如果有sidetable
if (bits.has_sidetable_rc) {
//获取sidetable中存储的引用计数值
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
//如果不是优化后的指针,那么引用计数从sidetable中返回
return sidetable_retainCount();
}
//从sideTable中取出引用计数值,不上锁,而且不加1,因为在前面的函数中已经+1
size_t objc_object::sidetable_getExtraRC_nolock()
{
ASSERT(isa.nonpointer);
SideTable& table = SideTables()[this];
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end()) return 0;
//从refcnts中取出值,然后右移两位获取到引用计数
//这里为什么要右移两位,因为最低位存储了是否有弱引用,低二位存储了SideTable是否在析构,所以需要右移两位
else return it->second >> SIDE_TABLE_RC_SHIFT;
}
//从sideTable中取出引用计数值,上锁,而且要加1
uintptr_t objc_object::sidetable_retainCount()
{
SideTable& table = SideTables()[this];
size_t refcnt_result = 1;
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
//从refcnts中取出值,然后右移两位获取到引用计数
//这里为什么要右移两位,因为最低位存储了是否有弱引用,低二位存储了SideTable是否在析构,所以需要右移两位
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
}
table.unlock();
return refcnt_result;
}
#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
#define SIDE_TABLE_DEALLOCATING (1UL<<1)
#define SIDE_TABLE_RC_ONE (1UL<<2)
#define SIDE_TABLE_RC_SHIFT 2
//SideTable的数据结构
struct SideTable {
//锁
spinlock_t slock;
//存储了引用计数的哈希表
RefcountMap refcnts;
//弱引用表
weak_table_t weak_table;
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
void forceReset() { slock.forceReset(); }
template
static void lockTwo(SideTable *lock1, SideTable *lock2);
template
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
//SideTable中RefcountMap的定义,它是一张哈希表
typedef objc::DenseMap,size_t,RefcountMapValuePurgeable> RefcountMap;
RefcountMap refcnts
:它是一张哈希表,查询的时候使用对象的地址作为key,经过哈希算法,得到一个值,这个值的最低位存储了是否有弱引用,低二位存储了是否正在析构,将这个值右移两位,如果nonpointer
为0,右移两位后加1,则是引用计数值;如果nonpointer
为1,我们从isa_t的extra_rc中取出值,加1,再加上右移两位的值,则得到了引用计数值
从上面的源码中,对于非TaggedPointer对象,我们可以得出以下结论:
- 如果
nonpointer
为0,表示isa未优化过,不作为存储引用计数,那么引用计数值都存放在SideTable中成员refcnts中,从refcnts中取出值加1,则得到了我们的引用计数值 - 如果
nonpointer
为1,表示isa优化过,存储了部分引用计数值,我们从isa_t的extra_rc中取出值,加1,再从SideTable中成员refcnts中取出值右移两位,加上前面的值,则得到了引用计数值