在MRC时代,系统判定一个对象是否销毁是根据这个对象的引用计数器来判断的。我们要知道一个对象在内存中内存是如何被管理的,还是需要了解一下MRC。设置Build Setting中的Objective-C Automatic Reference Counting为NO,开启手动管理内存。
引用计数存放位置
开始本篇源码分析前,我们需要了解引用计数是如何存放的,在上一篇NONPOINTER_ISA和散列表中,我们有说到,NONPOINTER_ISA里面有一位extra_rc用来表示该对象的引用计数,并且当引用计数大于10时,则需要额外再用到sidetable_rc,即散列表里,sideTable里有一个RefcountMap来存放引用计数。
ReatinCount源码解析
从一个问题来开始:看如下代码:
NSObject * obj = [NSObject alloc];
NSLog(@"obj.retainCount = %ld",obj.retainCount);
obj的引用计数是多少?NSLog会输出几?想回答这个问题,我们可以来分析一下retainCount的源码:
- (NSUInteger)retainCount {
return ((id)self)->rootRetainCount();
}
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this; //taggedPointer没有引用计数这个说法
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
if (bits.nonpointer) { //判断是nonPointer类型的isa的话
uintptr_t rc = 1 + bits.extra_rc; //先从extra_rc中获取
if (bits.has_sidetable_rc) { //如果sideTable也有的话
rc += sidetable_getExtraRC_nolock(); //加上从sideTable中获取的
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
可以看到,在从extra_rc中获取引用计数时,做了个+1的操作,即retainCount返回的值是1 + 计数表总计。
运行来看一下Log信息:
2019-04-11 10:13:50.014200+0800 debug-objc[24348:3063630] obj.retainCount = 1
所以,可以得出结论,alloc的对象其真实的引用计数是0,而retainCount为1。
Alloc
走进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)
{
if (slowpath(checkNil && !cls)) return nil; //异常判断
if (fastpath(!cls->ISA()->hasCustomAWZ())) { //是否有自定义的AllocWithZone,有的话就跳过系统的alloc
if (fastpath(cls->canAllocFast())) { //是否可以AllocFast
//** 来看一下canAllocFast
bool canAllocFast() {
assert(!isFuture());
return bits.canAllocFast();
}
bool canAllocFast() {
return false; //居然直接返回false,what a f**k! 所以我们可以直接去看else里的代码
}
*/
bool dtor = cls->hasCxxDtor();
id obj = (id)calloc(1, cls->bits.fastInstanceSize());
if (slowpath(!obj)) return callBadAllocHandler(cls);
obj->initInstanceIsa(cls, dtor);
return obj;
}
else {
id obj = class_createInstance(cls, 0); //调用创建对象
if (slowpath(!obj)) return callBadAllocHandler(cls); //一些错误处理
return obj;
}
}
if (allocWithZone) return [cls allocWithZone:nil];
return [cls alloc];
//跳入class_createInstance
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)
{
if (!cls) return nil;
assert(cls->isRealized());
//判断有c++的构造和析构
bool hasCxxCtor = cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
//判断是否可以创建Nonpointer类型
bool fast = cls->canAllocNonpointer();
//获取需要分配的size
size_t size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (!zone && fast) { //zone传进来是nil 而fast一般也都是真 所以几乎都会进这里
obj = (id)calloc(1, size); //开辟内存
if (!obj) return nil;
obj->initInstanceIsa(cls, hasCxxDtor); //初始化
}
else {
if (zone) {
obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (!obj) return nil;
obj->initIsa(cls);
}
if (cxxConstruct && hasCxxCtor) {
obj = _objc_constructOrFree(obj, cls);
}
return obj;
}
}
为了避免贴的代码太长,这里做一下分隔,接着跳入initInstanceIsa源码:
inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
assert(!cls->instancesRequireRawIsa());
assert(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer()); //taggedPointer判断
if (!nonpointer) { //不是nonpointer的话,直接将cls赋给isa.cls,这里的isa就只是一个单纯的指针
isa.cls = cls;
} else { //一般进入else
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
isa_t newisa(0); //初始化一个所有位为 0 的指针 isa_t
/**
__ARM_ARCH_7K__表示的应该是Apple Watch 相关的一些 所以iOS中SUPPORT_INDEXED_ISA会是0
#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif
*/
#if SUPPORT_INDEXED_ISA
assert(cls->classArrayIndex() > 0);
ISA_MAGIC_VALUE
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else //iOS中会走到这里
//isa_t的bits字段初始化为ISA_MAGIC_VALUE,ISA_MAGIC_VALUE是一个16进制的值,将其转换为二进制后,会发现ISA_MAGIC_VALUE是对nonpointer和magic做初始化。(见下一段代码)
newisa.bits = ISA_MAGIC_VALUE;
然后将是否有 C++ 析构函数标记
newisa.has_cxx_dtor = hasCxxDtor;
最后将位移(shift)后的 cls 存入 shiftcls(cls右移3位存到shiftcls中,从isa_t的结构体中也可以看到低3位都是用来存储其他信息的(nonpointer、has_assoc、has_cxx_dtor),既然可以右移三位,那就代表类地址的低三位全部都是0,否则就出错了,补0的作用应该是为了字节对齐。)。
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
isa = newisa;
}
}
我们来看一下ISA_MAGIC_VALUE对isa的初始化做了什么:
# if __arm64__
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# elif __x86_64__
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
将其转为2进制分别如下:
image.png image.png
按照NONPOINTER_ISA和散列表里的每一位的含义去对应上,可以看到其实是对magic 和 nonpointer 进行初始化。
Retain
- (id)retain {
return ((id)self)->rootRetain();
}
id objc_object::rootRetain()
{
return rootRetain(false, false);
}
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();
}
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
uintptr_t carry; //创建一个临时变量,表示是否有溢出
/**
# define RC_ONE (1ULL<<56) 1向左移56位 然后加进bits 其实就是操作bits位域里的extra_rc++
*/
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);
if (slowpath(carry)) { //如果发生了溢出,即 > 10,需要用到sideTable
if (!handleOverflow) {
ClearExclusive(&isa.bits); //清理旧值
return rootRetain_overflow(tryRetain);
}
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true; //锁住sideTable
transcribeToSideTable = true; //是否需要移动到sideTable
newisa.extra_rc = RC_HALF; //新的extra_rc设置为RC_HALF,另外的一半放进sideTable
newisa.has_sidetable_rc = true;//将是否用到了sideTable置为true
}
}
//判断是否处理完成,不完成的话就一直进行bits的处理
while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
if (slowpath(transcribeToSideTable)) {
// 另外的一半放进sideTable
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
//添加到sideTable
bool
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
SideTable& table = SideTables()[this];
//从sideTable的refCountMap中获取旧值
size_t& refcntStorage = table.refcnts[this];
size_t oldRefcnt = refcntStorage;
if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;
uintptr_t carry;
/** delta_rc就是传进来的RC_HALF,注意为什么要左移两位,因为refcountMap的前两位不是表示引用计数的,而是分别表示
是否有弱引用SIDE_TABLE_WEAKLY_REFERENCED,
是否正在析构SIDE_TABLE_DEALLOCATING
*/
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;
}
}
总结一下,retain其实就是对引用计数进行了+1操作,如果extra_rc未溢出,那么就直接extra_rc++,如果extra_rc溢出了,就取出一半的值放到sideTable里。
release
- (oneway void)release {
((id)self)->rootRelease();
}
ALWAYS_INLINE bool
objc_object::rootRelease()
{
return rootRelease(true, false);
}
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
if (isTaggedPointer()) return false;
bool sideTableLocked = false;
isa_t oldisa;
isa_t newisa;
retry:
do {
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return sidetable_release(performDealloc);
}
uintptr_t carry;
//对extra_rc做减操作
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (slowpath(carry)) {
//如果减溢出了,说明extra_rc已经减到了0,就走到underflow
goto underflow;
}
}
//做while循环,直到release处理完成
while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
underflow:
newisa = oldisa;
if (slowpath(newisa.has_sidetable_rc)) { //如果散列表存在,就操作散列表,否则就开始dealloc
if (!handleUnderflow) {
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}
if (!sideTableLocked) {
ClearExclusive(&isa.bits);
sidetable_lock();
sideTableLocked = true;
// Need to start over to avoid a race against
// the nonpointer -> raw pointer transition.
goto retry;
}
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
if (borrowed > 0) {
newisa.extra_rc = borrowed - 1; // redo the original decrement too
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
if (!stored) {
isa_t oldisa2 = LoadExclusive(&isa.bits);
isa_t newisa2 = oldisa2;
if (newisa2.nonpointer) {
uintptr_t overflow;
newisa2.bits =
addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
if (!overflow) {
stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits,
newisa2.bits);
}
}
}
if (!stored) {
// Inline update failed.
// Put the retains back in the side table.
sidetable_addExtraRC_nolock(borrowed);
goto retry;
}
sidetable_unlock();
return false;
}
else {
// Side table is empty after all. Fall-through to the dealloc path.
}
}
// 开始析构
if (slowpath(newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return overrelease_error();
}
newisa.deallocating = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
if (slowpath(sideTableLocked)) sidetable_unlock();
__sync_synchronize();
if (performDealloc) { //发送dealloc方法,所以引用计数到0的时候会走dealloc方法
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return true;
}
总结一下,release的流程跟retain类似,不过一个是加,一个是减。并且release触发了引用计数为0,那么就直接开始触发dealloc消息。
为什么这里retain和release都是RC_HALF呢,我们知道,retain会先往extra_rc里加,如果溢出再往sideTable里添加,同样,release也是这样,先从extra_rc里减一,然后从sideTable里取值再减一,为了避免多次操作sideTable,因为sideTable有锁,所以取中间值。
strong
strong的源码非常简单,会调用objc_retain来处理新值,调用objc_release释放旧值。
void
objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
property中的unsafe_unretained
property中设置属性时,会调用set方法,runtime中set方法的底层源码是这样的:
static ALWAYS_INLINE
void _object_setIvar(id obj, Ivar ivar, id value, bool assumeStrong)
{
if (!obj || !ivar || obj->isTaggedPointer()) return;
ptrdiff_t offset;
objc_ivar_memory_management_t memoryManagement;
_class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement);
if (memoryManagement == objc_ivar_memoryUnknown) {
//如果是UnKnow,那么设置为strong
if (assumeStrong) memoryManagement = objc_ivar_memoryStrong;
else memoryManagement = objc_ivar_memoryUnretained;
}
id *location = (id *)((char *)obj + offset);
switch (memoryManagement) {
case objc_ivar_memoryWeak: objc_storeWeak(location, value); break;
case objc_ivar_memoryStrong: objc_storeStrong(location, value); break;
case objc_ivar_memoryUnretained: *location = value; break;
case objc_ivar_memoryUnknown: _objc_fatal("impossible");
}
}
switch (memoryManagement) 判断中,可以看到,除了unretained,别的都是通过指针传递,而unretained是通过值传递,所以使用unretained会产生内存泄露。
@property (unsafe_unretained,nonatomic) NSObject * obj; //会产生野指针,发生内存泄漏。
网友评论