从源码看 iOS property strong
深入理解之前,先熟悉一个概念,Tagged Pointer
http://www.infoq.com/cn/articles/deep-understanding-of-tagged-pointer
strong
引用计数加1,我们深入看看,详细的过程
写个Demo
@interface pandaTest : NSObject
@property (nonatomic, strong) pandaTest *testProp;
@end
这里忽略循环引用,只是看看strong的过程。
单步调试
objc-debug`-[pandaTest setTestProp:]:
0x100001420 <+0>: pushq %rbp
0x100001421 <+1>: movq %rsp, %rbp
0x100001424 <+4>: subq $0x20, %rsp
0x100001428 <+8>: movq %rdi, -0x8(%rbp)
0x10000142c <+12>: movq %rsi, -0x10(%rbp)
0x100001430 <+16>: movq %rdx, -0x18(%rbp)
0x100001434 <+20>: movq -0x18(%rbp), %rdx
0x100001438 <+24>: movq -0x8(%rbp), %rsi
0x10000143c <+28>: movq 0x5225(%rip), %rdi ; pandaTest._testProp
0x100001443 <+35>: addq %rdi, %rsi
0x100001446 <+38>: movq %rsi, %rdi
0x100001449 <+41>: movq %rdx, %rsi
0x10000144c <+44>: callq 0x100004bc2 ; symbol stub for: objc_storeStrong
0x100001451 <+49>: addq $0x20, %rsp
0x100001455 <+53>: popq %rbp
0x100001456 <+54>: retq
看看内部会走什么流程。symbol stub for: objc_storeStrong
我们发现了这个。这里跳到runtime中调用 objc_storeStrong 方法。一步步来。
看看 objc_storeStrong方法的具体实现
void
objc_storeStrong(id *location, id obj)
{
//取出之前指向的地址
id prev = *location;
if (obj == prev) {
return;
}
///引用计数加1
objc_retain(obj);
///strong修饰的指针指向obj
*location = obj;
///释放前一个对象
objc_release(prev);
}
这里参数有两个,一个 location 一个obj
此处,location 是我们使用 strong 修饰的指针,obj 是 通过alloc init 产生的对象。
///对齐方式
__attribute__((aligned(16)))
void
objc_release(id obj)
{
if (!obj) return;
if (obj->isTaggedPointer()) return;
return obj->release();
}
释放函数比较简单,
所以重点就在 objc_retain(obj);
方法里面
id
objc_retain(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
这里直接调用 retain。
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
// don't check newisa.fast_rr; we already called any RR overrides
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
uintptr_t carry;
///retaincount 加1
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
// prepare to copy the other half to the side table.
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
if (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
///在全局的表中改变retainCount的值
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
此处,参数有两个,tryRetain,handleOverflow,会有溢出情况?
来看看 isa 指针的定义,
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__
# 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)
};
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8;
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
};
比如在 x86_64 情况下, extra_rc 就是引用计数的个数标示,只占8位,能表示的大小在 0 - 256 之间,所以,超过 256 自然就溢出了。溢出怎么办呢,runtime 维护了一个表
alignas(StripedMap) static uint8_t
SideTableBuf[sizeof(StripedMap)];
static void SideTableInit() {
new (SideTableBuf) StripedMap();
}
static StripedMap& SideTables() {
return *reinterpret_cast*>(SideTableBuf);
}
这个表初始化的比较早,程序刚启动就初始化了。调用过程大概如下:
_objc_init
map_images
map_images_nolock
arr_init
SideTableInit
我们重新回到 rootRetain
函数