-
兄弟们,最近实在是太忙了。不过~我又回来继续探索了。
-
内存管理这个名词,我相信所有的iOS工程师都听说过,也是大多数兄弟们,面试最头疼的,今天!小谷带大家走一波源码。希望对大家有所帮助。
-
关于内存管理,大家都会想到,ARC/MRC、retain、release、dealloc、autorelease。今天就浅谈一波。不对的地方,我在查源码找找,哈哈~
作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:196800191,加群密码:112233,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!
ARC/MRC (我就不多说了)
ARC(Automatic Reference Count ) :自动的引用计数
其实ARC 是LLVM和runtime 结合的产物。
不让开发者主动调用dealloc/relesae/retain。
并且引进了,weak和strong,
1. retain 和 release
- 大家对于retain和release一定不陌生。我当定义一个属性的时候就会产生,setter和getter方法。赋值的操作就是新值的retain和旧值的release。源码如下:
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
今天我们研究下他是如何retain的。
1.1. 探究retain
首先,我们点进方法看看:
__attribute__((aligned(16), flatten, noinline))
id
objc_retain(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
我不太知道怎么把关键字设置高亮,兄弟们对不起了。
- 这个判断是:如果是isTaggedPointer就返回。否则就进行retain操作
- 那这是不是说明了:isTaggedPointer类型,不会进行内存管理,会自动释放
- 那我们就简单的看下这个isTaggedPointer
1.1.1. 拓展isTaggedPointer类型
- taggedpointer就是大家常说的小对象,我们来看下他的实现:
// 左移63位,就只是保留了最高位
# define _OBJC_TAG_MASK (1UL<<63)
inline bool
objc_object::isTaggedPointer()
{
return _objc_isTaggedPointer(this);
}
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
- 所以判断小对象就是判断最高位。
这个不是内存管理的重点,主要是遇到了,就提一嘴,taggedpointer类型是不进行内存管理的。不会retain和release。一般的小对象是NSNumber、NSData和小于11位的NSString
1.2. retain的实现
我们看一下retain的实现:下面是我追踪源码的过程:
- 这样我们就找到了retain 源码的实现(我在里面已经加了注释(虽然我知道兄弟们肯定可以看懂。。))
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
//isa 的原因是要在extra_rc存值(如果有不了解isa结构的兄弟可以看下我的博客)
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
//slowpath就是小几率走的意思。
//不是nonpoint_isa
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);//清理bits(我看了下实现,好像啥都没干)
if (rawISA()->isMetaClass()) return (id)this;//如果是元类,返回
if (!tryRetain && sideTableLocked) sidetable_unlock();//如果散列表锁了,解锁。(里面的实现兄弟们可以自己探究了)
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)) {//如果是dealloc了,(这个一会我会在说)
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();//解锁
return nil;//返回nil
}
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++ (这个英文注释不是我写的,哈哈)
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if (!handleOverflow) {//查看是否溢出了(就是满了没有)
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);//没有满的话,就retain操作
}
// Leave half of the retain counts inline and
// prepare to copy the other half to the side table.
//如果满了,就会把extra_rc的一半放入散列表
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
if (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
//这个是中间有调用的,其实就是又调用回来了。
NEVER_INLINE id
objc_object::rootRetain_overflow(bool tryRetain)
{
return rootRetain(tryRetain, true);
}
我在里面加了一点注释,其实这个大家可能有些有疑惑,不知道散列表是什么。听起来高端大气,其实就是一张用来存储的表
1.3. retain 总结
我来根据上述代码总结下。(我知道兄弟们都看的懂,不过我比较笨 😆)
-
进行retain操作时,先判断是否是taggedpointer类型。是的话继续retain操作,taggedpointer不受内存管理
-
进行retain时首先操作的是isa中的extra_rc,(如果不明白isa结构可以看下我的博客isa结构分析)
-
进行retain时会判断是否dealloc,如果被释放掉就返回nil
-
进行retain操作会使 extra_rc++, 如果溢出的话,会把一半存在散列表中
1.4. release实现
- 大家看完retain之后,release就相对简单了很多,其实就是个反向操作
我把源码贴出来啦(这个比较长,但操作差不多):
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 (rawISA()->isMetaClass()) return false;
if (sideTableLocked) sidetable_unlock();
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--
if (slowpath(carry)) {
// don't ClearExclusive()
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.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
newisa = oldisa;
if (slowpath(newisa.has_sidetable_rc)) {
if (!handleUnderflow) {
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}
// Transfer retain count from side table to inline storage.
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.
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.
if (borrowed > 0) {
// Side table retain count decreased.
// Try to add them to the inline count.
newisa.extra_rc = borrowed - 1; // redo the original decrement too
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);
}
}
}
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.
}
}
// Really deallocate.
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;
}
这个就留给兄弟们看了,我就不做注释污染了(其实就是isa中的extra_rc进行减减的操作)
2. dealloc
- 在retain和release代码中,它会判断对象是否dealloc
- 我们浅谈一波。(其实就是个析构~)
- 首先大家可以思考下,假如我要释放了,我需要怎么办?
- 我贴源码了啊。
inline void
objc_object::rootDealloc()
{
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);
}
}
- 是否是isa.nonpointer,只有是这才会有释放这回事哈
- 是否有弱引用表
- 是否有关联对象
- 是否有C++的析构,就是哪个cxx_dtor (这个也在isa结构分析的博客中有提到)
- 是否有散列表
其实就是这5个条件判断
否则就会调用object_dispose(其实也是发现有以上判断就处理的操作)
代码给兄弟们奉上~
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;
}
好了兄弟们,就先到这了~ 希望可以给大家带来帮助。洗完了我继续回去加班~😔
原文作者:小谷先森
原文地址:https://juejin.cn/post/6899420127050072077