@qidiandasheng
2022-08-07T21:39:48.000000Z
字数 10287
阅读 1375
iOS实战
我们下载源码编译查看(objc4-781),alloc
调用顺序如下,而 new
相当于调用 alloc
后再调用 init
:
//第一步:
+ (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 __OBJC2__
if (slowpath(checkNil && !cls)) return nil;
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
return _objc_rootAllocWithZone(cls, nil);
}
#endif
// No shortcuts available.
if (allocWithZone) {
return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
}
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}
//第四步:
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
// allocWithZone under __OBJC2__ ignores the zone parameter
return _class_createInstanceFromZone(cls, 0, nil,
OBJECT_CONSTRUCT_CALL_BADALLOC);
}
第五步:
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
int construct_flags = OBJECT_CONSTRUCT_NONE,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
ASSERT(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
//要开辟多少内存
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
//怎么去申请内存
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
// 3:将当前的类和指针地址绑定在一起
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (fastpath(!hasCxxCtor)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
最重要的就是第五步:
size = cls->instanceSize(extraBytes);
计算要开辟的内存大小,这里使用了16字节内存对齐计算要开辟的内存空间obj = (id)calloc(1, size);
通过calloc申请内存,并赋值给obj,因此 obj是指向内存地址的指针obj->initInstanceIsa(cls, hasCxxDtor);
通过initInstanceIsa,将当前的类和指针地址关联在一起,也就是给objc_object
结构体设置isa指针的值:
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
isa = newisa;
}
}
我们知道通过retain
会使引用计数加一,那我们接下去看一看retain
的调用过程:
//第一步:
- (id)retain {
return _objc_rootRetain(self);
}
//第二步:
objc_object::rootRetain()
{
return rootRetain(false, false);
}
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;
.
.
.
uintptr_t carry;
// extra_rc加1
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
// carry为1表示溢出
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if (!handleOverflow) {
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);
}
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
//转存到SideTable位True
transcribeToSideTable = true;
//更新值为RC_HALF,RC_HALF=(1ULL<<7)=0b10000000
newisa.extra_rc = RC_HALF;
//isa中存储sidetable中有引用计数
newisa.has_sidetable_rc = true;
}
} 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;
}
我们可以看到newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);
其实就是把isa公用体里extra_rc
的值加一。当extra_rc
值为255时,如果再加1,则carry
为1,表示溢出。(extra_rc
在arm64中占8位,最大值可表示255)
我们这里把代码改为MRC环境下,手动进行retain。
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSObject *objc1 = [[NSObject alloc] init];
for (int i=0; i<512; i++) {
[objc1 retain];
}
}
return 0;
}
在循环处设置i==255的断点,同时在newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);
处加上断点,这时候extra_rc
无法存储就会溢出,然后设置isa的值newisa.extra_rc = RC_HALF(128);
和newisa.has_sidetable_rc = true;
:
然后再进入i==256,这时候在extra_rc
减半的基础上再加1,extra_rc==129
:
所以这里每次溢出的时候就会存储一半的引用计数到sidetable
中。
我们使用如下代码,并且在objc_object::rootRetain()
处加上断点:
int main(int argc, const char * argv[]) {
@autoreleasepool {
//1.创建对象,引用计数为1
NSObject *objc1 = [[NSObject alloc] init];
//2.引用计数加1
NSObject *objc2 = objc1;
printf("retain count = %ld\n",CFGetRetainCount((__bridge CFTypeRef)(objc1)));
NSLog(@"Hello, World! %@ - %@",objc1,objc2);
}
return 0;
}
//输出
retain count = 2
我们发现在第一步alloc、init
时并没有调用objc_object::rootRetain()
方法,在第二步把objc2
指向objc1
时才调用了objc_object::rootRetain()
使引用计数加1。但我们看到输出时引用计数值为2,这是为什么呢?
我们看一下获取引用计数的调用方法即可,CFGetRetainCount
最后调用的是objc-object.h
源码中的objc_object::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) {
//引用计数值为extra_rc+1
uintptr_t rc = 1 + bits.extra_rc;
//如果之前retain时extra_rc溢出,
//则从sidetable中取溢出的引用计数和rc的值相加即为完整的引用计数
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
我们发现extra_rc
值确实为1,只是这里永远会有一个extra_rc+1
的操作,也就是说你创建对象(alloc
)之后其实引用计数就是为1的,没必要再调用retain
了,直接在获取引用计数的地方加一个常数1即可。
也就是说retainCount
有三部分组成:
1
extra_rc
中存储的值
sidetable_getExtraRC_nolock
返回的值
调用步骤:
//第一步:
- (void)dealloc {
_objc_rootDealloc(self);
}
void
_objc_rootDealloc(id obj)
{
ASSERT(obj);
obj->rootDealloc();
}
//第二步:
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer && // 是否是优化过的isa
!isa.weakly_referenced && // 不包含或者不曾经包含weak指针
!isa.has_assoc && // 没有关联对象
!isa.has_cxx_dtor && // 没有c++析构方法
!isa.has_sidetable_rc)) // 引用计数没有超出上限的时候可以快速释放,rootRetain(bool tryRetain, bool handleOverflow) 中设置为true
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
我们从第二步可以看到满足一定条件下,对象指针会直接free释放掉,实际很多情况下都会走object_dispose((id)this)
函数。这一步主要就是释放成员变量,移除关联对象,设置弱引用指针为nil:
//第三步:
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();
// 对象拥有成员变量时编译器会自动插入.cxx_desctruct方法用于自动释放。
if (cxx) object_cxxDestruct(obj);
// 移除关联对象
if (assoc) _object_remove_assocations(obj);
// weak->nil
obj->clearDeallocating();
}
return obj;
}
//释放成员变量,沿着继承链逐层向上搜寻SEL_cxx_destruct这个selector,找到函数实现(void (*)(id)(函数指针)并执行。
void object_cxxDestruct(id obj)
{
if (!obj) return;
if (obj->isTaggedPointer()) return;
object_cxxDestructFromClass(obj, obj->ISA());
}
static void object_cxxDestructFromClass(id obj, Class cls)
{
void (*dtor)(id);
// Call cls's dtor first, then superclasses's dtors.
for ( ; cls; cls = cls->superclass) {
if (!cls->hasCxxDtor()) return;
dtor = (void(*)(id))
lookupMethodInClassAndLoadCache(cls, SEL_cxx_destruct);
if (dtor != (void(*)(id))_objc_msgForward_impcache) {
if (PrintCxxCtors) {
_objc_inform("CXX: calling C++ destructors for class %s",
cls->nameForLogging());
}
(*dtor)(obj);
}
}
}
// weak->nil
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());
}
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
ASSERT(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
// 根据对象地址从SideTables中取出SideTable
SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
// 从SideTable中取出weak_table,根据weak_table获取弱引用数组,然后一个个置为nil
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}
//第一步:
- (oneway void)release {
_objc_rootRelease(self);
}
//第二步:
_objc_rootRelease(id obj)
{
ASSERT(obj);
obj->rootRelease();
}
//第三步:
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;
.
.
.
uintptr_t carry;
// extra_rc中的值减1
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
// 如果减1后小于0,则carry为1,执行下溢操作
if (slowpath(carry)) {
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
underflow:
newisa = oldisa;
// 如果sidetable中有保存引用计数,则从SideTable 借位
if (slowpath(newisa.has_sidetable_rc)) {
if (!handleUnderflow) {
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}
。
。
。
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
if (borrowed > 0) {
newisa.extra_rc = borrowed - 1;
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) {
sidetable_addExtraRC_nolock(borrowed);
goto retry;
}
sidetable_unlock();
return false;
}
else {
// Side table 也是空值,则进入下面的dealloc的操作
}
}
// 上面引用计数真的减少到零之后,则执行dealloc的操作
if (slowpath(newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return overrelease_error();
// does not actually return
}
newisa.deallocating = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
if (slowpath(sideTableLocked)) sidetable_unlock();
__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return true;
}
extra_rc
减1,如果没有下溢则更新isa的extra_rc
为新值has_sidetable_rc==True
则调用 sidetable_subExtraRC_nolock
,成功借位之后,我们会重新设置 newisa
的值 newisa.extra_rc = borrowed - 1
并更新 isa。has_sidetable_rc==False
或者没有借到位的话,就说明没有任何的变量引用了当前对象(即 retainCount = 0
),就需要向它发送 dealloc
消息了。Objective-C 引用计数原理
iOS 引用计数 retainCount、retain、release 源码分析+注释+实验
ARC下,Dealloc还需要注意什么?
黑箱中的 retain 和 release