objc_storeStrong
oc中用strong修饰一个对象,实际上是调用了
void objc_storeStrong(id *location, id obj)
函数,location是引用对象的指针的地址,obj是对象本身。下面是该函数的完整实现:
void objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
实现很简单,首先和之前的引用相比判断是不是同一个引用,是的话就return;否则的话就对obj对象进行retain,并且释放*location之前的引用(也就是说*location指针不再指向之前的对象,要把之前对象引用计数减1)。
objc_retain
objc_retain函数主要是对对象引用计数加1,下面来看objc_retain函数的实现。objc_retain函数调用了id objc_object::sidetable_retain()函数,该函数的实现如下:
id objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
table.lock();
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();
return (id)this;
}
这个函数的实现也很简单,首先从SideTable中取出当前对象的引用计数信息refcntStorage,并且做了非负判断。再来看
refcntStorage += SIDE_TABLE_RC_ONE;
这一行,SIDE_TABLE_RC_ONE的定义如下:
#define SIDE_TABLE_RC_ONE (1UL<<2) // MSB-ward of deallocating bit
为什么refcntStorage加了个4呢?个人推测苹果可能是出于节省空间的考虑,把对象的一些信息都存储到了refcntStorage上,看下面这些宏定义:
// The order of these bits is important.
#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
#define SIDE_TABLE_DEALLOCATING (1UL<<1) // MSB-ward of weak bit
#define SIDE_TABLE_RC_ONE (1UL<<2) // MSB-ward of deallocating bit
#define SIDE_TABLE_RC_PINNED (1UL<<(WORD_BITS-1))
#define SIDE_TABLE_RC_SHIFT 2
#define SIDE_TABLE_FLAG_MASK (SIDE_TABLE_RC_ONE-1)
可以推断出refcntStorage的第一位存储了对象的弱引用信息,第二位用来标记对象是否正在被dealloc,第三位开始才真正存储了对象的引用计数。
objc_release
objc_release函数对对象的引用计数进行减一,里面调用了uintptr_t 函数。再来看该函数的实现:
objc_object::sidetable_release(bool performDealloc)
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
bool do_dealloc = false;
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end()) {
do_dealloc = true;
table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
} else if (it->second < SIDE_TABLE_DEALLOCATING) {
// SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
do_dealloc = true;
it->second |= SIDE_TABLE_DEALLOCATING;
} else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
it->second -= SIDE_TABLE_RC_ONE;
}
table.unlock();
if (do_dealloc && performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return do_dealloc;
}
首先判断该SideTable是否包含该对象(迭代器it==refcnts.end()就证明map里面没有这个对象),如果没有,那么把这个对象标记为正在被dealloc;否则的话判断该对象是否没有引用,如果是的话,那么也把这个对象标记为正在被dealloc;如果两者都不是,那么就取出引用计数信息减1(通过减SIDE_TABLE_RC_ONE来实现第三位开始减1)。接下来如果发现这个对象需要被dealloc,那么直接执行对象的dealloc函数。
这里其实有一个疑问,因为根据我们的了解,当对象的引用计数为0的时候,会自动被dealloc,那么it->second -= SIDE_TABLE_RC_ONE; 的结果就有可能是0,但是并没有这样的逻辑判断。其实apple在存储对象的引用计数信息的时候,保存的是其真正的引用计数减1,所以sidetable_release 函数能够正确执行。
Tagged Pointer和引用计数
Tagged Pointer是苹果为了优化内存管理而设计的一种技术,简单来说就是当对象的信息比较简单,内容较少,而64位平台每个指针的长度为64位,每个bit位不一定都会被使用,造成内存浪费,这时apple会使用指针的bit位来存储对象的一些信息。
那么如何确认apple是如何存储引用计数的呢,我们可以从对象的结构中寻找线索。
struct objc_object {
private:
isa_t isa;
public:
xxxx
xxxx
xxxx
对象中有一个isa_t类型的对象,isa是一个联合体,其中包含了这样的一组定义:
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 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;
uintptr_t extra_rc : 19;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
我们可以看到,当使用Tagged Pointer时候,从指针的第45位开始,一共19位,存储的都是对象的引用计数信息,标志为extra_rc。当存储空间不够时,苹果会使用对象的sidetable来存储对象的引用计数信息。这个过程实现如下:
// Move some retain counts to the side table from the isa field.
// Returns true if the object is now pinned.
bool objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
assert(isa.nonpointer);
SideTable& table = SideTables()[this];
size_t& refcntStorage = table.refcnts[this];
size_t oldRefcnt = refcntStorage;
// isa-side bits should not be set here
assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;
uintptr_t carry;
size_t newRefcnt =
addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
if (carry) {
refcntStorage =
SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
return true;
}
else {
refcntStorage = newRefcnt;
return false;
}
}
这段代码清楚的描述了引用计数信息是如何从指针中转移到sidetable里面的
#define SIDE_TABLE_RC_PINNED (1UL<<(WORD_BITS-1))
#define SIDE_TABLE_FLAG_MASK (SIDE_TABLE_RC_ONE-1)
refcntStorage =
SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
通过这段代码,apple把存储引用计数信息的refcntStorage变量的第32位标记为计数溢出位(也就是计数数值只能为正整数),并且将正真的引用计数减一。
另外当我们获取引用计数的时候,调用了如下函数:
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()) {
// this is valid for SIDE_TABLE_RC_PINNED too
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
}
table.unlock();
return refcnt_result;
}
也可以看到返回值是取出的数值+1,这也验证了我们上面的说法。
本文深入探讨Objective-C runtime中的`objc_storeStrong`、`objc_retain`和`objc_release`函数,详细解析引用计数的增减过程,以及Tagged Pointer在内存管理中的应用。通过对源码的分析,揭示了对象引用计数存储的细节和优化策略。
296

被折叠的 条评论
为什么被折叠?



