1、引用计数
在讲MRC、ARC的时候,我们知道是使用引用计数(retainCount)来管理OC对象的内存的。
那引用计数这个值又是存储在哪里的呢?
带着这个问题,我们往下看。
2、isa的结构
我们要知道,在OC语言的内部,每个对象都有一个isa指针,指向该对象的类。
#pragma -mark NSObject的结构
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
那么这个isa里面有哪些内容呢?
#pragma -mark 只看arm64情况下
struct {
uintptr_t nonpointer : 1;//0代表普通指针,存贮着class、meta-class对象的内存地址;1代表优化过,使用位域存贮着更多信息
uintptr_t has_assoc : 1;//是否有设置过关联对象,如果没有会释放更快
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;//标记是否有被弱引用指向过,如果没有会释放的更快
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1; //标记引用计数是否过大,无法存在isa中,如果为1,则表示引用计数存在一个sideTable表中
uintptr_t extra_rc : 19;//这里面存储的值是引用计数-1
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
3、引用计数存储在哪
从上面isa的结构内容中,我们可以看出引用计数的存储肯定是和isa有关的。
一般都是通过retainCount这个方法来获取某个对象的引用计数的,那我们就通过retainCount的底层实现来看看引用计数到底是怎么得到的。
//获取引用计数的方法
- (NSUInteger)retainCount {
return ((id)self)->rootRetainCount();//调用rootRetainCount方法
}
//C++函数
inline uintptr_t
objc_object::rootRetainCount()
{
//如果是taggedPointer ,那么就返回自己
//因为如果你是个Tagged Pointer的话,那你就不是OC对象,你就是个普通的指针
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);//isa.bits其实就是isa本身内容
ClearExclusive(&isa.bits);
if (bits.nonpointer) {//判断非指针类型,如果是个优化过的isa指针,引用计数就为isa中的extra_rc + 1
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {//判断一下是否是存在sideTable里,如果为1的话,不是存贮在isa中,是存在sideTable中
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();//这个方法基本和上面sidetable_getExtraRC_nolock差不多,都是去sideTable去取值
}
//在sideTable中取引用计数
size_t
objc_object::sidetable_getExtraRC_nolock()
{
assert(isa.nonpointer);
SideTable& table = SideTables()[this];//根据this这个key值去取一个value,这个this指的是对象本身
RefcountMap::iterator it = table.refcnts.find(this);//在散列表SideTable中的refcnts中去取这个引用计数
if (it == table.refcnts.end()) return 0;
else return it->second >> SIDE_TABLE_RC_SHIFT;
}
从源码可以看出,获取retainCount的逻辑基本是:
- 判断是否为Tagged Pointer:为Tagged Pointer则没有引用计数
- 判断nonpointer是否为1,为1则表明优化过,存贮着更多信息(extra_rc中便存储着引用计数)。但是优化过的isa也并不一定就存储这引用计数,因为19bit保存引用计数不一定够。
- 还要判断has_sidetable_rc是否为1。为1表明引用计数存在SideTable 的类的属性中。
4、散列表
屏幕快照 2019-01-15 下午3.11.36.pngSideTable 这个类,它用于管理引用计数表和后面将要提到的 weak 表,并使用 spinlock_lock 自旋锁来防止操作表结构时可能的竞态条件。
其中refcnts对应的作用就是管理引用计数。
5、总结
在64bit中,引用计数可以直接存储在优化过的isa指针中,也可能存储在SideTable类中。
如果对象地址为一个Tagged Pointer,那么则没有引用计数,
如果为一个优化过的指针且has_sidetable_rc为0,则引用计数存储在isa结构的extra_rc中。
其他情况下,基本是引用计数存在sideTable中的refcnts中。
网友评论