这里只是我对iOS内存管理方面的一些关键知识点的在线笔记,帮忙记忆,未对任何知识点进行深入的分析和探究。
TaggedPointer
这是苹果为了优化内存,对于一些NSString、NSNumber、NSDate等类型的对象进行了优化。在一个8字节的指针里面即存储对象类型,也直接存储其数据值,并且TaggedPointer类型的对象直接存储在常量区中。另外ios10之后苹果底层对taggedPointer的指针地址信息进行了混淆,所以如果直接在lldb下打印时看到真实的信息,需要异或tagPointer的maskt值都能得到真实的内存地址。
NSString *str1 = [NSString stringWithFormat:@"a"];
NSString *str2 = [NSString stringWithFormat:@"b"];
NSLog(@"%p-%@",str1,str1);
NSLog(@"%p-%@",str2,str2);
NSLog(@"0x%lx",_objc_decodeTaggedPointer_(str2));
uintptr_t
_objc_decodeTaggedPointer_(id ptr)
{
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK
#define _OBJC_TAG_MASK (1UL<<63)
2020-11-26 22:39:59.225938+0800 002---taggedPointer[51257:7382935] 0xad65eafc838e1526-a
2020-11-26 22:39:59.938400+0800 002---taggedPointer[51257:7382935] 0xad65eafc838e1516-b
2020-11-26 22:40:00.493712+0800 002---taggedPointer[51257:7382935] 0xa000000000000621
retain操作
当向一个对象发送retain消息时,其内部有以下关键逻辑:
1.先判断对象是否是nonpointer(进行过指针优化的,即把8字节指针64位分成不同的区域,用作不同的功能使用,例如拿出其中的8位来存储引用计数的值等)
2.如果不是nonpointer(未进行指针优化的,一个8字节指针就是纯的内存地址)那么将直接操作全局的SideTable,找到其对应的对象进行加1操作,但每次都操作SideTable都需要开解锁,性能有点低
3.考虑多线程情况,还要判断是否正在释放,如果正在释放中,那根本不需要retain了,直接返回即可
4.到此步骤了,肯定是nonpointer的,这时先对extra_rc进行加1操作,但是extra_rc只占8字节中的8位而已,有可能存储满,所以当这个值存储满足就把满extra_rc的一半存储在sideTable中,这样下次再需要进行+1操作的时候可以继续操作extra_rc,不需要读取sideTable并开锁了,提升了性能。
struct SideTable {
spinlock_t slock; //访问sideTable需要开解锁
RefcountMap refcnts;//引用计数表
weak_table_t weak_table;//弱引用表
}
release操作
当向一个对象发送release消息时,其内部有以下关键逻辑:
1.先判断对象是否是nonpointer,
2.如果不是nonpointer,那么也是直接操作sideTable,进行减一操作,然后直接返回
3.如果是nonpointer,先对extra_rc减1,如果减完了,那么看一下SideTable里面有没有之前存进来,有的话就拿出来放到extra_rc中,所以SideTable中的值只相当于备用存储用。经过此时的减1操作后,如果发现引用计数为0了,直接会向对象发送dealloc消息,进入释放流程。
dealloc流程
如果对象的deallc方法被调用了,那么需要判断它是否有弱引用关系、关联对象、c的析构、引用计数是否在sideTable也有值。如果都没有直接free当前对象即可,否则需要把清空弱引用表、关联对象表、调用c的析构、把对象从sideTable中移除。
inline void
objc_object::rootDealloc()
{
// free()
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
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();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
ASSERT(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}
弱引用
SideTable中有一张弱引用表weakTable_t,然后该表中存储了所有对象与其弱引用指向的关系。当对象释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
struct weak_table_t {
// 保存了所有指向指定对象的 weak 指针
weak_entry_t *weak_entries;
// 存储空间
size_t num_entries;
// 参与判断引用计数辅助量
uintptr_t mask;
// hash key 最大偏移值
uintptr_t max_hash_displacement;
};
typedef objc_object ** weak_referrer_t;
struct weak_entry_t {
DisguisedPtrobjc_object> referent;
union {
struct {
weak_referrer_t *referrers;//二维 objc_object
uintptr_t out_of_line : 1;
uintptr_t num_refs : PTR_MINUS_1;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// out_of_line=0 is LSB of one of these (don't care which)
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
}
}
自动释放池
自动释放池有几个关键的知识点:
1.objc_autoreleasePoolPush、objc_autoreleasePoolPop 被@autoreleasepool包裹的代码块其实是被这两个方法包裹着。
2.objc_autoreleasePoolPush调用会往page表中压入一个哨兵对象,objc_autoreleasePoolPop调用会向遍历所有pages以及每个page中所有对象,向它们发送release消息。
3.AutoreleasePoolPage对象其实是一个双向链表,并且内部维护了一个存储对象的数组,这个数组有大小限制,当达到4k后开新的page出来,然后存储到新的page中。
4.autoreleasepool只会对应一个线程,每个线程可能会对应多个autoreleasepool,比如autoreleasepool嵌套的情况。
网友评论