一,alloc
YSHStudent *baseStu = [YSHStudent alloc];
YSHStudent *stu1 = [baseStu init];
YSHStudent *stu2 = [baseStu init];
YSHStudent *stu3 = [baseStu init];
YSHStudent *newStu = [YSHStudent alloc];
NSLog(@"%@---------%p---------%p",baseStu,baseStu,&baseStu);
NSLog(@"%@---------%p---------%p",stu1,stu1,&stu1);
NSLog(@"%@---------%p---------%p",stu2,stu2,&stu2);
NSLog(@"%@---------%p---------%p",stu3,stu3,&stu3);
NSLog(@"%@---------%p---------%p",newStu,newStu,&newStu);
2021-06-06 10:57:53.766217+0800 alloc[27048:1427279] <YSHStudent: 0x600001f68770>---------0x600001f68770---------0x7ffeeeb8a500
2021-06-06 10:57:53.766808+0800 alloc[27048:1427279] <YSHStudent: 0x600001f68770>---------0x600001f68770---------0x7ffeeeb8a4f8
2021-06-06 10:57:53.767075+0800 alloc[27048:1427279] <YSHStudent: 0x600001f68770>---------0x600001f68770---------0x7ffeeeb8a4f0
2021-06-06 10:57:53.767250+0800 alloc[27048:1427279] <YSHStudent: 0x600001f68780>---------0x600001f68780---------0x7ffeeeb8a4e8
baseStu和stu1、stu2、stu3的对象地址是一模一样的,但是指针地址却不同;
newStu和baseStu、stu1、stu2、stu3不仅指针地址不同,而且对象地址也是不同的;
alloc的主要目的是开辟内存,关联isa和cls类,来看一看具体的底层原理
alloc
+ (id)alloc {
return _objc_rootAlloc(self);
}
_objc_rootAlloc
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
callAlloc
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__ //是否有可用的编译器优化
//slowpath 大概率为假
//fastpath 大概率为真
//此处去掉slowpath和fastpath对代码逻辑没有丝毫影响,应该只是告诉编译器对代码优化,提升编译效率。
if (slowpath(checkNil && !cls)) return nil;
//判断一个类是否有自定义的 +allocWithZone 实现,没有则走到if里面的实现
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));
}
_objc_rootAllocWithZone
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
// allocWithZone under __OBJC2__ ignores the zone parameter
//这里没有什么好说的,直接进入第5步
return _class_createInstanceFromZone(cls, 0, nil,
OBJECT_CONSTRUCT_CALL_BADALLOC);
}
_class_createInstanceFromZone
主要做了3件事
cls->instanceSize(extraBytes)
;-------计算内存空间(id)calloc(1, size);
-------开辟内存空间,返回内存地址指针obj->initInstanceIsa(cls, hasCxxDtor);
-------关联isa和类
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;
}
if (!zone && fast) {
//将cls类和isa关联
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);
}
instanceSize
inline size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
fastInstanceSize
size_t fastInstanceSize(size_t extra) const
{
ASSERT(hasFastInstanceSize(extra));
if (__builtin_constant_p(extra) && extra == 0) {
return _flags & FAST_CACHE_ALLOC_MASK16;
} else {
size_t size = _flags & FAST_CACHE_ALLOC_MASK;
// remove the FAST_CACHE_ALLOC_DELTA16 that was added
// by setFastInstanceSize
//16字节对齐
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
}
我们发现计算内存时是按照16字节对齐的,这样做的原因是什么?
- 我们需要知道CPU在读取数据时,是以字节块为单位进行读取的,如果频繁读取没有对齐的数据,会严重加大CPU的开销,降低效率
- 为什么16字节对齐而不是8字节对齐,我们都知道在一个对象中,第一个属性isa占8字节,如果只有8字节的话,不预留空间,可能造成这个对象的isa和另一个对象的isa紧挨着,容易造成访问混乱。同时一个对象也不会只有isa一个属性
由此可见:16字节对齐后,可以加快CPU读取速度,同时访问也会更加安全。
calloc
calloc:申请内存,返回地址指针。通过instanceSize方法计算的内存大小,想内存中申请大小为size的内存,并赋值给obj。所以obj是指向内存地址的指针。
initInstanceIsa
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
initIsa
// objc-config.h
// Define SUPPORT_INDEXED_ISA=1 on platforms that store the class in the isa
// field as an index into a class table.
// Note, keep this in sync with any .s files which also define it.
// Be sure to edit objc-abi.h as well.
#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif
// objc-object.h
inline void
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 // 对于 64 位系统,该值为 0
ASSERT(cls->classArrayIndex() > 0);
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
newisa.bits = ISA_MAGIC_VALUE; // 将 isa 的 bits 赋值为 ISA_MAGIC_VALUE
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
initInstanceIsa
主要是初始化一个isa指针,初始化isa指针上的has_cxx_dtor,indexcls,magic,nonpointer,shiftcls(存储对象的Class、Meta-Class对象的内存地址信息)
注意这里没有初始化isa的extra_rc,也就是没有将引用计数加一。也就是所在allocate是没有将引用计数加1。在ARC下,对象的引用计数有编译器自动完成,在alloc对象并将它赋值给强应用的对象,ARC会自动插入该对象的retain
方法,将引用计数加一。
总结alloc主要有3个核心步骤:
- 计算内存大小
instanceSize
- 申请内存空间
calloc
- 关联isa和cls类
initInstanceIsa
retain
retain的作用是把对象的引用计数加一
ALWAYS_INLINE id
objc_object::rootRetain()
{
return rootRetain(false, false);
}
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
// 如果是 tagged pointer,直接返回 this
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false; // 是否需要将引用计数存储在 sideTable 中
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
// 获取 isa
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
// 如果 isa 不是 nonpointer(优化指针)
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return (id)this;
if (!tryRetain && sideTableLocked) sidetable_unlock();
// tryRetain == false,调用 sidetable_retain
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; // 用于判断 isa 的 extra_rc 是否溢出,这里指上溢,即存满
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
// 如果 extra_rc 上溢
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
// 如果 handleOverflow == false,调用 rootRetain_overflow
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.
// 保留一半的引用计数在 extra_rc 中
// 准备把另一半引用计数存储到 Sidetable 中
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true; // 设置 transcribeToSideTable 为 true
newisa.extra_rc = RC_HALF; // 设置 extra_rc 的值为 RC_HALF # define RC_HALF (1ULL<<18)
newisa.has_sidetable_rc = true; // 设置 has_sidetable_rc 为 true
}
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits))); // 保存更新后的 isa.bits
// 如果需要将溢出的引用计数存储到 sidetable 中
if (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
// 将 RC_HALF 个引用计数存储到 Sidetable 中
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
- 对于NSTaggedPointer类型,直接返回,不参与引用计数计算,因为NSTaggedPointer对象的值直接存储在了指针中,不必在堆上为其分配内存,这点在objc_msgSend也有体现,首先会判断LNilOrTagged。
- 获取isa指针,判断isa是不是non pointer(未优化的isa,只存储类和元类对象的地址),如果不是就调用sidetable_retain经过两次的哈希查找得到对象的引用计数表,将引用计数+1,如果不是则执行下面的内容。
- 将
isa
中的extra_rc +1,如果溢出,就将extra_rc的RC_HALF转移到sidetable
中存储,extra_rc是19位,而RC_HALF宏是(1ULL<<18),实际上相等于进行了 +1 操作。
这里提到了sidetable,前面讲weak时也用到了。
struct SideTable {
// 保证原子操作的自旋锁
spinlock_t slock;
// 引用计数的 hash 表
RefcountMap refcnts;
// weak 引用全局 hash 表
weak_table_t weak_table;
}
这里用到了refcnts,用来存储OC对象的引用计数的 hash表(仅在未开启isa优化或在isa优化情况下isa_t的引用计数溢出时才会用到)。
sidetable_retain
id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this]; // 获取 SideTable
table.lock();
size_t& refcntStorage = table.refcnts[this]; // 获取 refcnt
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) { // 如果获取到了,且未溢出
refcntStorage += SIDE_TABLE_RC_ONE; // 将引用计数加 1
}
table.unlock();
return (id)this;
}
release
当我们不在需要使用或持有对象时,需要调用一下release方法。release方法会讲对象的引用计数-1.
objc_object::rootRelease
ALWAYS_INLINE bool
objc_object::rootRelease()
{
return rootRelease(true, false);
}
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
// 如果是 tagged pointer,直接返回 false
if (isTaggedPointer()) return false;
bool sideTableLocked = false;
isa_t oldisa;
isa_t newisa;
retry:
do {
// 获取 isa
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
// 如果 isa 不是 nonpointer
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return false;
if (sideTableLocked) sidetable_unlock();
// 调用 sidetable_release
return sidetable_release(performDealloc);
}
// don't check newisa.fast_rr; we already called any RR overrides
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
// 如果发现溢出的情况,这里是下溢,指 extra_rc 中的引用计数已经为 0 了
if (slowpath(carry)) {
// don't ClearExclusive()
// 执行 underflow 处理下溢
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits))); // 保存更新后的 isa.bits
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
underflow:
// newisa.extra_rc-- underflowed: borrow from side table or deallocate
// abandon newisa to undo the decrement
// extra_rc-- 下溢,从 sidetable 借用或者 dealloc 对象
newisa = oldisa;
// 如果 isa 的 has_sidetable_rc 字段值为 1
if (slowpath(newisa.has_sidetable_rc)) {
// 如果 handleUnderflow == false,调用 rootRelease_underflow
if (!handleUnderflow) {
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}
// Transfer retain count from side table to inline storage.
// 将引用计数从 sidetable 中转到 extra_rc 中存储
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;
}
// Try to remove some retain counts from the side table.
// 尝试从 sidetable 中删除(借出)一些引用计数,传入 RC_HALF
// borrowed 为 sidetable 实际删除(借出)的引用计数
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
// To avoid races, has_sidetable_rc must remain set
// even if the side table count is now zero.
// 为了避免竞争,has_sidetable_rc 必须保持设置
// 即使 sidetable 中的引用计数现在是 0
if (borrowed > 0) { // 如果 borrowed > 0
// Side table retain count decreased.
// Try to add them to the inline count.
// 将它进行 -1,赋值给 extra_rc
newisa.extra_rc = borrowed - 1; // redo the original decrement too
// 存储更改后的 isa.bits
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
// 如果存储失败,立刻重试一次
if (!stored) {
// Inline update failed.
// Try it again right now. This prevents livelock on LL/SC
// architectures where the side table access itself may have
// dropped the reservation.
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);
}
}
}
// 如果还是存储失败,把引用计数再重新保存到 sidetable 中
if (!stored) {
// Inline update failed.
// Put the retains back in the side table.
sidetable_addExtraRC_nolock(borrowed);
goto retry;
}
// Decrement successful after borrowing from side table.
// This decrement cannot be the deallocating decrement - the side
// table lock and has_sidetable_rc bit ensure that if everyone
// else tried to -release while we worked, the last one would block.
sidetable_unlock();
return false;
}
else {
// Side table is empty after all. Fall-through to the dealloc path.
}
}
// 如果引用计数为 0,dealloc 对象
// Really deallocate.
// 如果当前 newisa 处于 deallocating 状态,保证对象只会 dealloc 一次
if (slowpath(newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
// 调用 overrelease_error
return overrelease_error();
// does not actually return
}
// 设置 newisa 为 deallocating 状态
newisa.deallocating = true;
// 如果存储失败,继续重试
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
if (slowpath(sideTableLocked)) sidetable_unlock();
__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
// 如果 performDealloc == true,给对象发送一条 dealloc 消息
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return true;
}
sidetable_release
// rdar://20206767
// return uintptr_t instead of bool so that the various raw-isa
// -release paths all return zero in eax
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
// 获取 SideTable
SideTable& table = SideTables()[this];
bool do_dealloc = false; // 标识是否需要执行 dealloc 方法
table.lock();
auto it = table.refcnts.try_emplace(this, SIDE_TABLE_DEALLOCATING);
// 获取 refcnts
auto &refcnt = it.first->second;
if (it.second) {
do_dealloc = true;
// 如果对象处于 deallocating 状态
} else if (refcnt < SIDE_TABLE_DEALLOCATING) {
// SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
do_dealloc = true;
refcnt |= SIDE_TABLE_DEALLOCATING;
// 如果引用计数有值
} else if (! (refcnt & SIDE_TABLE_RC_PINNED)) {
// 引用计数 -1
refcnt -= SIDE_TABLE_RC_ONE;
}
table.unlock();
// 如果符合判断条件,dealloc 对象
if (do_dealloc && performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return do_dealloc;
}
具体流程:
isa
不是(isa指针上的)nonpointer
,就对Sidetable
中的引用计数减1,如果引用计数为0,就dealloc
对象isa
如果是nonpointer
,就讲isa
中的extra_rc
储存的引用计数减1,如果下溢,引用计数已经减为0,判断has_sidetable_rc
(isa指针上的)是否为true即是否使用sideTable
存粗引用计数。- 如果有,就在
sidetable
申请RC_HALF
个引用计数(如果不足有多少就申请多少),将实际申请的引用计数减1后存储在extra_rc
. - 如果
extra_rc
中的引用计数为0,且has_sidetable_rc
为false
或sidetable
中的引用计数也为0,那就dealloc
对象。
内存管理方法 | 具体实现 |
---|---|
alloc | 经过一系列的函数调用栈,最终通过C语言的calloc来申请内存空间,并初始化对象的isa指针,但并没有设置对象的引用计数值为1 |
init | 基类的init方法啥也没有干,只是将alloc的对象返回。我们可以重写init方法来对alloc创建的实例对象做一些初始化的操作 |
new | new方法很简单,只是嵌套了alloc和init方法 |
copy,mutableCopy | 调用了copyWithZone 和mutableWith方法 |
retaincount | 如果isa不是nonpointer,引用计数值 = SideTable中的引用计数表中储存的值 + 对象本身的引用计数 1;如果isa是nonpointer,引用计数值 = isa中的extra_rc存储的值 + SideTable中的引用计数表中储存的值+ 对象本身的引用计数 1| |
retain | 如果isa不是nonpointer,就对Sidetable中的引用计数+ 1; 如果isa是nonpointer,就对Sidetable中的引用计数+ 1,如果溢出,就将extra_rc中的RC_HALF(extra_rc满值的一半)个引用计数转移到sidetable中存储 |
release | isa不是nonpointer,就对Sidetable中的引用计数减1,如果引用计数为0,就dealloc对象;isa如果是nonpointer,就讲isa中的extra_rc储存的引用计数减1,如果下溢,引用计数已经减为0,判断has_sidetable_rc(isa指针上的)是否使用sideTable存储引用计数。如果有,就在sidetable申请RC_HALF个引用计数(如果不足有多少就申请多少),将实际申请的引用计数 - 1后存储在extra_rc中。如果extra_rc中引用计数为 0 且has_sidetable_rc为false或者Sidetable中的引用计数也为 0 了,那就dealloc对象 |
dealloc | 判断销毁对象前有没有需要处理的东西(如弱引用,关联对象,C++析构函数,sidetable的引用计数表;如果没有就直接调用free函数销毁对象;如果有就先调用object_dispose做一些释放对象前处理(置弱引用指针为nil,移除关联对象,object_cxxDestruct,在sideTable的引用计数表中擦出引用计数等)最后再用free函数销毁对象 |
weak指针置为nil的过程 | 当一个对象被销毁时,在dealloc方法内部经过一系列的函数调用,通过两次哈希查找,第一次根据对象的地址找到它所在的sidetable,第二次根据对象的地址在sidetable的weak_table找到它的弱引用表。便利弱引用地址,将指针对象地址的weak变量全部置为nil |
添加weak | 经过一系列的函数调用栈。最终在weak_register_no_locck()函数当中,通过哈希查找当前对象的弱引用表,并添加弱引用变量;如果不存在,就创建一个弱引用表,在添加进去 |