内存管理:引用计数器源码分析
一、什么是引用计数器
每个对象都维护了自己的引用计数器,
它表示了,当前有多少个对象引用了它。
一旦有对象引用该对象地址,那么它的引用计数器就加1,移除一个引用,引用计数器就减一,当引用计数器为0 的时候,该对象就会被销毁,回收内存。
二、引用计数器的操作
在MRC的时候,需要手动管理内存。
当使用alloc、new或者copy创建一个对象时,对象的引用计数器默认就是1
发送retain消息后 引用计数器 + 1
发送release消息后 引用计数器 -1
发送retainCount消息,可以查看当前引用计数器的值
在ARC中,编译器已经自动为我们进行管理,不需要我们使用retain、release操作
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 只要创建一个对象默认引用计数器的值就是1
Person *p = [[Person alloc] init];
NSLog(@"retainCount = %lu", [p retainCount]); // 1
// 只要给对象发送一个retain消息, 对象的引用计数器就会+1
[p retain];
NSLog(@"retainCount = %lu", [p retainCount]); // 2
// 通过指针变量p,给p指向的对象发送一条release消息
// 只要对象接收到release消息, 引用计数器就会-1
// 只要一个对象的引用计数器为0, 系统就会释放对象
[p release];
// 需要注意的是: release并不代表销毁\回收对象, 仅仅是计数器-1
NSLog(@"retainCount = %lu", [p retainCount]); // 1
[p release]; // 0
NSLog(@"--------");
}
// [p setAge:20]; // 此时对象已经被释放
return 0;
}
三、引用计数器的存储
64位系统之后,对象的isa指针已经被优化过,isa是一个共用体
不同的变量,存在同一个单元里。通过位运算&不同的掩码值,可以获取到不同位置的值
union isa_t { // union 共用体
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
// ISA_BITFIELD 宏定义中的内容有以下内容
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33;
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19
合并到一起isa 的数据结构就是
union isa_t { // union 共用体
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
struct {
//nonpointer 是否是优化过的指针 0表示普通的指针,存储的是内存地址
//1.表示优化过,使用位域存储更多的信息
uintptr_t nonpointer : 1;
// has_assoc 是否设置过关联对象,如果没有释放时更快
uintptr_t has_assoc : 1;
// has_cxx_dtor 是否有C++的析构函数,如果没有释放的更快
uintptr_t has_cxx_dtor : 1;
//shiftcls 真正存储这Class、Meta-class对象的内存地址信息
uintptr_t shiftcls : 33;
//magic 用于在调试时分辨对象是否未完成初始化
uintptr_t magic : 6;
//weakly_referenced 是否有被弱引用指向过,如果没有释放更快
uintptr_t weakly_referenced : 1;
//deallocating 对象是否正在释放
uintptr_t deallocating : 1;
//has_sidetable_rc 引用计数器是否过大,无法存储在isa中,如果为1表示引用计数器会存储在一个叫SideTable的类的属性中
uintptr_t has_sidetable_rc : 1;
// extra_rc 里面存储的值是引用计数器的值-1
uintptr_t extra_rc : 19
};
由此可见,引用计数器的值可分为两个地方存储
1.isa中的extra_rc,isa存储的数量有限
2.一个叫SideTable的类中
四、SideTable结构
struct SideTable {
spinlock_t slock;
RefcountMap refcnts; // 通过这个散列表存储引用计数器的值
weak_table_t weak_table; // 弱引用表
}
五、源码分析验证
查看retainCount的源码,分析引用计数器的存储
// retainCount方法
- (NSUInteger)retainCount {
return ((id)self)->rootRetainCount();
}
// 内部的主要逻辑
objc_object::rootRetainCount()
{
// 首先判断是否是TaggedPointer,如果是,直接返回
if (isTaggedPointer()) return (uintptr_t)this;
// 如果不是
sidetable_lock();
// 获取isa信息
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
// 判断isa 是否是优化过的指针
if (bits.nonpointer) { // 如果是优化过的指针
uintptr_t rc = 1 + bits.extra_rc; // 获取extra_rc的值 + 1
if (bits.has_sidetable_rc) { // 判断引用计数器是否存储在sidetable中
// 如果是,获取sidetable中存储的值 +isa中的值
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
// 返回 引用计数器的值
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
由此可见,引用计数器的值
1.保存在isa的extra_rc中,extra_rc的值为引用计数器的值-1
2.如果引用计数器的值过大,无法继续存储,剩余的值存储在SideTable中
具体在SideTable中的哪个属性中呢,继续分析代码
sidetable_getExtraRC_nolock的实现
objc_object::sidetable_getExtraRC_nolock()
{
assert(isa.nonpointer);
SideTable& table = SideTables()[this];
// 获取SideTable中的refcnts,是一个遍历器
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end()) return 0; // 如果到最后没有查询到数据,返回0
// 判断里面的second通过位运算,计算出数值并返回
else return it->second >> SIDE_TABLE_RC_SHIFT;
}
因此,可以确定,引用计数器剩余部分的值,是存储在SideTable中的refcnts的散列表中,通过一个位运算可以获得
retain方法的内部实现
//首先
- (id)retain {
return ((id)self)->rootRetain();
}
objc_object::rootRetain()
{
return rootRetain(false, false);
}
// 具体的实现内容
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
// 判断是否是TaggedPointer,如果是直接返回它本身,说明taggedpointer不会被引用
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
// 获取没有retain前的isa
oldisa = LoadExclusive(&isa.bits);
// 将新的isa赋值
newisa = oldisa;
// 是否是优化过的指针,如果不是,
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits); //此方法内部没有做任何处理
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
// 进入sidetable 中,将引用计数器+1
else return sidetable_retain();
}
// 后面的代码执行的都是优化过的isa指针的处理方法
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
// uintptr_t 是unsigned int的别名
uintptr_t carry;
// 新的isa的内容中extra_rc进行 ++的操作
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if (!handleOverflow) {
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);
}
// Leave half of the retain counts inline and
// 准备复制另一半到表中
// 获取sidetable
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;// RC_HALF = 1ULL<<18
newisa.has_sidetable_rc = true;
}
// 判断isa中extra_rc是否能继续存储
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
// 向sidetable中存储引用计数器
if (slowpath(transcribeToSideTable)) {
// 向sidetable中存储引用计数器
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
关于release的操作大同小异
- (oneway void)release {
((id)self)->rootRelease();
}
objc_object::rootRelease()
{
return rootRelease(true, false);
}
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
// 判断TaggedPointer
if (isTaggedPointer()) return false;
bool sideTableLocked = false;
isa_t oldisa;
isa_t newisa;
retry:
do {
// 获取isa信息
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
// 判读是否是优化过的isa,如果不是,直接sidetable_release
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return sidetable_release(performDealloc);
}
// 如果是优化过的isa,将extra_rc--操作
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (slowpath(carry)) {
// don't ClearExclusive()
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
......
}
//sidetable_release 中会判断对应的引用计数器是否为0,如果是会对对象发送一个dealloc的消息,进行销毁对象
dealloc的调用流程
- (void)dealloc {
_objc_rootDealloc(self);
}
void _objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}
inline void
objc_object::rootDealloc()
{
// 判断是否是TaggedPointer,如果是直接返回
if (isTaggedPointer()) return; // fixme necessary?
// 如果是优化过的isa & 没有弱引用 & 没有C++的析构函数 & 没有关联对象 & 引用计数器没有保存到sidetable中,直接释放对象
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
// 如果上面的判断不成立,调用object_dispose方法,进行移除清空上述内容
else {
object_dispose((id)this);
}
}
//object_dispose方法内部又调用objc_destructInstance方法
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
// 具体的释放方式
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
// 判断是否具有C++析构函数
bool cxx = obj->hasCxxDtor();
// 是否具有关联对象
bool assoc = obj->hasAssociatedObjects();
//如果有析构函数,
if (cxx) object_cxxDestruct(obj);
//如果有关联对象,移除关联对象
if (assoc) _object_remove_assocations(obj);
// 移除弱引用表中的数据和引用计数器
obj->clearDeallocating();
}
return obj;
}
总结:
1.iOS中通过引用计数器管理维护对象是否会被释放
2.引用计数器会被存储在isa中的extra_rc中,如果值过大会存储到一个sidetable中refcnts属性中