Objc源码之对象创建alloc和init
Objc源码之initialize实现
Objc源码之Load方法实现
Objc源码之NSObject和isa
Objc源码之引用计数实现
objc源码之Method消息发送
前言
我们都知道OC的内存管理是引用计数,可是对象的引用计数是存储在哪里的呢?今天我们就从源码入手,来揭开这个谜底,下面我们就从对象创建开始说起。
一、从alloc和retainCount引用计数
首先我们看下retainCount方法,这个是获取引用计数的方法:
- (NSUInteger)retainCount {
return ((id)self)->rootRetainCount();
}
inline uintptr_t objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
if (bits.nonpointer) {
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
看到这里,我们会发现引用计数包含三部分:
1. isTaggedPointer,是否是Tagged Pointer类型
2. nonpointer 是否是优化的isa指针。
3. sidetable_retainCount 其它类型
1.isTaggedPointer的情况
Tagged Pointer是苹果在64位系统之后,用来优化内存的。
1.Tagged Pointer专门用来存储小的对象,例如NSString、NSNumber、NSDate等;
2.Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要malloc和free;
3.在内存读取上有着3倍的效率,创建时比以前快106倍。
具体可以看看这个iOS Tagged Pointer (源码阅读必备知识) - 掘金
总结就是isTaggedPointer情况下引用计数返回的是对象本身。
2.nonpointer优化的情况
nonpointer表示是否开启指针优化,
- 0表示isa_t没有开启指针优化,不使用isa_t中定义的结构体。
- 1表示isa_t开启指针优化,不能直接访问objc_object的isa成员变量 ,isa中包含了类信息、对象的引用计数等信息。
要了解nonpointer,首先看alloc是怎么创建一个对象,其中nonpointer
和extra_rc
都是走嗯么赋值的,下面是简化的alloc函数的调用过程:
+ (id)alloc {
return _objc_rootAlloc(self);
}
id _objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
...
id obj = class_createInstance(cls, 0);
return obj;
...
}
id class_createInstance(Class cls, size_t extraBytes)
{
return _class_createInstanceFromZone(cls, extraBytes, nil);
}
static __attribute__((always_inline)) id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
...
obj = (id)calloc(1, size);
if (!obj) return nil;
obj->initInstanceIsa(cls, hasCxxDtor);
return obj;
}
inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
initIsa(cls, true, hasCxxDtor);
}
inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
...
newisa.bits = ISA_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
isa = newisa;
}
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \
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
我们总结一下调用过程alloc
->_objc_rootAlloc
->callAlloc
->class_createInstance
->_class_createInstanceFromZone
->initIsa
。
关于引用计数的部分,在最后初始化isa指针中initIsa
,下面我们看下initIsa
中的具体过程:
newisa.bits = ISA_MAGIC_VALUE;
在给bits
赋值的时候,通过ISA_MAGIC_VALUE这个宏来赋值的,arm64下这个宏的值是0x000001a000000001ULL
,通过ISA_BITFIELD,我们来看下具体含义:
# define ISA_BITFIELD \
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
extra_rc
代表的就是对象的引用计数,它包含19位,nonpointer只包含一位,下面我们看下arm64下ISA_MAGIC_VALUE:
紫色部分是
extra_rc
的值,我们看到
extra_rc
的值是0,我们知道,刚创建的对象,应用计数应该是1,但是这里怎么是0,那是因为
extra_rc
保存的是引用计数-1的值,在获取的时候,会进行+1.在arm64下nonpointer默认是1,默认开启isa指针优化的。
在nonpointer
情况下,我们可以看到rootRetainCount
代码包含两部分:
if (bits.nonpointer) {
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
第一部分就是上面说的extra_rc
,获取到extra_rc
以后,在又会判断has_sidetable_rc
的值。
has_sidetable_rc
是用来做什么的?
has_sidetable_rc
是用来判断extra_rc
是否存储不下引用计数数量的,如果引用计数大于extra_rc
的最大存储数量,那么就会超出的引用计数存储到SideTables
,下面看下源码:
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;
}
了解weak原理的话,应该会熟悉SideTables
,这也是weak对象存储的地方,上面的代码是取的SideTables
的refcnts
中以对象为key的值,这里面refcnts
是一个哈希表。
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
}
typedef objc::DenseMap,size_t,true> RefcountMap;
总结:如果extra_rc能够存储下引用计数,就只使用extra_rc,如果存储不下,就会使用SideTables进行辅助存储。
3.sidetable_retainCount的情况
sidetable_retainCount是在没有开启指针优化的情况,对象的引用计数直接存储在SideTable中。
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. Tagged Pointer情况,直接返回对象指针
2. 优化的isa指针,也就是nonpointer为1时,通过extra_rc和SideTables一起来管理引用计数。
3.未开启指针优化,也就是nonpointer为0时,直接使用SideTables来管理引用计数 。
参考:
objc4-750源码
OC内存管理--引用计数器
【译】采用Tagged Pointer的字符串
从 NSObject 的初始化了解 isa.md
isa详解