最近在看源码的过程中,问了自己几个问题。
1.引用计数存在哪!?
2.isa指针内部结构是什么?
3.retainCount内部实现是什么样?
4.dealloc的内部实现是什么样的?
5.sideTables为什么要是个数组。
isa我们平时肯定知道是什么?但是内部结构呢?
通过查资料
在 arm64 架构之前,isa就是一个普通的指针,直接指向objc_class,存储着Class、Meta-Class对象的内存地址。instance对象的isa指向class对象,class对象的isa指向meta-class对象;
// objc.h
struct objc_object {
Class isa; // 在 arm64 架构之前
};
从 arm64 架构开始,对isa进行了优化,用nonpointer表示,变成了一个共用体(union)结构,还使用位域来存储更多的信息。将 64 位的内存数据分开来存储着很多的东西,其中的 33 位才是拿来存储class、meta-class对象的内存地址信息。要通过位运算将isa的值& ISA_MASK掩码,才能得到class、meta-class对象的内存地址。
// objc-private.h
struct objc_object {
private:
isa_t isa; // 在 arm64 架构开始
};
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if SUPPORT_PACKED_ISA
// extra_rc must be the MSB-most field (so it matches carry/overflow flags)
// nonpointer must be the LSB (fixme or get rid of it)
// shiftcls must occupy the same bits that a real class pointer would
// bits + RC_ONE is equivalent to extra_rc + 1
// RC_HALF is the high bit of extra_rc (i.e. half of its range)
// future expansion:
// uintptr_t fast_rr : 1; // no r/r overrides
// uintptr_t lock : 2; // lock for atomic property, @synch
// uintptr_t extraBytes : 1; // allocated with extra bytes
# if __arm64__ // 在 __arm64__ 架构下
# define ISA_MASK 0x0000000ffffffff8ULL // 用来取出 Class、Meta-Class 对象的内存地址
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 1; // 0:代表普通的指针,存储着 Class、Meta-Class 对象的内存地址
// 1:代表优化过,使用位域存储更多的信息
uintptr_t has_assoc : 1; // 是否有设置过关联对象,如果没有,释放时会更快
uintptr_t has_cxx_dtor : 1; // 是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
uintptr_t shiftcls : 33; // 存储着 Class、Meta-Class 对象的内存地址信息
uintptr_t magic : 6; // 用于在调试时分辨对象是否未完成初始化
uintptr_t weakly_referenced : 1; // 是否有被弱引用指向过,如果没有,释放时会更快
uintptr_t deallocating : 1; // 对象是否正在释放
uintptr_t has_sidetable_rc : 1; // 如果为1,代表引用计数过大无法存储在 isa 中,那么超出的引用计数会存储在一个叫 SideTable 结构体的 RefCountMap(引用计数表)散列表中
uintptr_t extra_rc : 19; // 里面存储的值是对象本身之外的引用计数的数量,retainCount - 1
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
...... // 在 __x86_64__ 架构下
};
请仔细看nonpointer,has_assoc,has_cxx_dtor,shiftcls,magic,weakly_referenced,deallocating,has_sidetable_rc,extra_rc后面备注。
后面要说的这句话才是重点。
nonpointer也就是之前说过的TaggedPointer技术
如果isa非nonpointer,即 arm64 架构之前的isa指针。由于它只是一个普通的指针,存储着Class、Meta-Class对象的内存地址,所以它本身不能存储引用计数,所以以前对象的引用计数都存储在一个叫SideTable结构体的RefCountMap(引用计数表)散列表中。
如果isa是nonpointer,则它本身可以存储一些引用计数。从以上union isa_t的定义中我们可以得知,isa_t中存储了两个引用计数相关的东西:extra_rc和has_sidetable_rc。
extra_rc:里面存储的值是对象本身之外的引用计数的数量,这 19 位如果不够存储,has_sidetable_rc的值就会变为 1;
has_sidetable_rc:如果为 1,代表引用计数过大无法存储在isa中,那么超出的引用计数会存储SideTable的RefCountMap中。
所以,如果isa是nonpointer,则对象的引用计数存储在它的isa_t的extra_rc中以及SideTable的RefCountMap中。
可以看下retainCount的源码流程
uintptr_t
_objc_rootRetainCount(id obj)
{
assert(obj);
return obj->rootRetainCount();
}
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;//如果是采用isTaggedPointer直接返回this本身
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);//取出isa_t
ClearExclusive(&isa.bits);
if (bits.nonpointer) { //如果是优化的指针
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {//如果引用计数过大无法存贮在isa中
rc += sidetable_getExtraRC_nolock();//去sidetable中去拿取计数
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
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;
else return it->second >> SIDE_TABLE_RC_SHIFT;
}
//没有优化过的isa去sidetable中拿计数
uintptr_t
objc_object::sidetable_retainCount()
{
SideTable& table = SideTables()[this];//根据地址拿到SideTable
size_t refcnt_result = 1;
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);//从SideTable中根据地址拿取RefcountMap
if (it != table.refcnts.end()) {
// this is valid for SIDE_TABLE_RC_PINNED too
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
}
table.unlock();
return refcnt_result;//返回RefcountMap中的计数
}
通过上面我们可以看到如果isa中无法存储指针,那么就存储在SideTable中的RefcountMap中。
从这里我们可以引出SideTable,那么他是什么呢?
static StripedMap& SideTables() {
return *reinterpret_cast*>(SideTableBuf);
}
typedef objc::DenseMap,size_t,true> RefcountMap;
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(); }
// Address-ordered lock discipline for a pair of side tables.
template
static void lockTwo(SideTable *lock1, SideTable *lock2);
template
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
SideTable存储在SideTables()中,SideTables()本质也是一个散列表,可以通过对象指针来获取它对应的(引用计数表或者弱引用表)在哪一个SideTable中。
查找对象的引用计数表需要经过两次哈希查找:
① 第一次根据当前对象的内存地址,经过哈希查找从SideTables()中取出它所在的SideTable;
② 第二次根据当前对象的内存地址,经过哈希查找从SideTable中的refcnts中取出它的引用计数表。
那么回到文章开头的疑问,为什么不是一个SideTable而是存在SideTables中。
通过源码中我看到了,SideTable是需要加锁解锁的。那么如果不同的对象,共用一个SideTable对象,那么则会造成效率问题了。所以需要拆分成不同的SideTable对象存放在SideTables中。
下面是查资料的解释
如果只有一个SideTable,那我们在内存中分配的所有对象的引用计数或者弱引用都放在这个SideTable中,那我们对对象的引用计数进行操作时,为了多线程安全就要加锁,就存在效率问题。
系统为了解决这个问题,就引入 “分离锁” 技术方案,提高访问效率。把对象的引用计数表分拆多个部分,对每个部分分别加锁,那么当所属不同部分的对象进行引用操作的时候,在多线程下就可以并发操作。所以,使用多个SideTable组成SideTables()结构。
来看下dealloc的流程
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
如果做了isa优化,并且没有弱引用,没有关联对象,没有析构函数,没有存在sideTable中,则释放的更快
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.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
void object_cxxDestruct(id obj)
{
if (!obj) return;
if (obj->isTaggedPointer()) return;
object_cxxDestructFromClass(obj, obj->ISA());
}
void _object_remove_assocations(id object) {
vector< ObjcAssociation,ObjcAllocator > elements;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
if (associations.size() == 0) return;
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// copy all of the associations that need to be removed.
ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
// remove the secondary table.
delete refs;
associations.erase(i);
}
}
// the calls to releaseValue() happen outside of the lock.
for_each(elements.begin(), elements.end(), ReleaseValue());
}
inline void
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
void
objc_object::sidetable_clearDeallocating()
{
SideTable& table = SideTables()[this];
// clear any weak table items
// clear extra retain count and deallocating bit
// (fixme warn or abort if extra retain count == 0 ?)
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
table.refcnts.erase(it);
}
table.unlock();
}
objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}