iOS底层原理(35)-内存管理(中)

617 阅读5分钟

散列表结构分析

SideTable

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;//被__weak修改的对象会加到这里

    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }

    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }
    ......
};

weak_table

image.png

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) {
        uintptr_t rc = 1 + bits.extra_rc;
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}

代码探索 - 弱引用表

NSObject *object = [[NSObject alloc] init];
NSLog(@"%zd - %@",(long)CFGetRetainCount((__bridge  CFTypeRef)(object)),object);
__weak typeof(id) weakObject = object;
NSLog(@"%zd - %@",(long)CFGetRetainCount((__bridge  CFTypeRef)(object)),object);
NSLog(@"%zd - %@",(long)CFGetRetainCount((__bridge  CFTypeRef)(weakObject)),weakObject);
    
NSObject *o1 = object;
NSLog(@"%zd - %@",(long)CFGetRetainCount((__bridge  CFTypeRef)(o1)),o1);
    
/**
 1 - <NSObject: 0x6000026a4230>
 1 - <NSObject: 0x6000026a4230>
 2 - <NSObject: 0x6000026a4230>
 2 - <NSObject: 0x6000026a4230>
 */

从上面可以得出结论,使用弱引用的时候,不会对原来对象的引用计数发生改变. 引出的问题是:为什么weakobject 打印是2?

接下来探索,在__weak typeof(id) weakObject = object;位置打断点,进入汇编调试,可以看到进入了objc_initWeak

image.png

objc_initWeak

id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

//这里的location 是当前传进来的weakSelf的指针地址
    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

可以得到结论,在出现__weak的地方,会进入objc_initWeak。这一点可以通过探索github上的llvm-project,如下图:

Screen Shot 2022-04-13 at 16.54.57.png

Screen Shot 2022-04-13 at 16.57.49.png

storeWeak

template <HaveOld haveOld, HaveNew haveNew,
          CrashIfDeallocating crashIfDeallocating>
static id 
storeWeak(id *location, objc_object *newObj)
{
    ASSERT(haveOld  ||  haveNew);
    if (!haveNew) ASSERT(newObj == nil);

    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;

    // Acquire locks for old and new values.
    // Order by lock address to prevent lock ordering problems. 
    // Retry if the old value changes underneath us.
 retry:
    if (haveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }

    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no 
    // weakly-referenced object has an un-+initialized isa.
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            class_initialize(cls, (id)newObj);

            previouslyInitializedClass = cls;

            goto retry;
        }
    }

    // Clean up old value, if any.
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // Set is-weakly-referenced bit in refcount table.
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj;
}

weak_register_no_lock

id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, bool crashIfDeallocating)
{
    objc_object *referent = (objc_object *)referent_id;//外界的被弱引用的对象,即object
    objc_object **referrer = (objc_object **)referrer_id;//创建的弱引用对象,即weakObject

    if (!referent  ||  referent->isTaggedPointer()) return referent_id;

    // ensure that the referenced object is viable
    bool deallocating;
    if (!referent->ISA()->hasCustomRR()) {
        deallocating = referent->rootIsDeallocating();
    }
    else {
        BOOL (*allowsWeakReference)(objc_object *, SEL) = 
            (BOOL(*)(objc_object *, SEL))
            object_getMethodImplementation((id)referent, 
                                           @selector(allowsWeakReference));
        if ((IMP)allowsWeakReference == _objc_msgForward) {
            return nil;
        }
        deallocating =
            ! (*allowsWeakReference)(referent, @selector(allowsWeakReference));
    }

    if (deallocating) {
        if (crashIfDeallocating) {
            _objc_fatal("Cannot form weak reference to instance (%p) of "
                        "class %s. It is possible that this object was "
                        "over-released, or is in the process of deallocation.",
                        (void*)referent, object_getClassName((id)referent));
        } else {
            return nil;
        }
    }

    // now remember it and where it is being stored
    weak_entry_t *entry;
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer);
    } 
    else {
        weak_entry_t new_entry(referent, referrer);
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);
    }

    // Do not set *referrer. objc_storeWeak() requires that the 
    // value not change.

    return referent_id;
}

weak_entry_t

struct weak_entry_t {
    DisguisedPtr<objc_object> referent;//将要被弱引用的对象,即object
    union {
        struct {
            weak_referrer_t *referrers;//针对将要被弱引用对象生成的弱引用对象,即weakObject
            uintptr_t        out_of_line_ness : 2;
            uintptr_t        num_refs : PTR_MINUS_2;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };

    bool out_of_line() {
        return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
    }

    weak_entry_t& operator=(const weak_entry_t& other) {
        memcpy(this, &other, sizeof(other));
        return *this;
    }

    weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
        : referent(newReferent)
    {
        inline_referrers[0] = newReferrer;
        for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
            inline_referrers[i] = nil;
        }
    }
};

小结

1、首先我们知道有一个 SideTable

2、得到SideTableweakTable弱引用表

3、创建一个 weak_entry_t

4、把referent加入到weak_table的数组inline_referers

5、将weak_table进行扩容

6、把new_entry加入到weak_table

代码探索 - 弱引用的引用计数

NSObject *object = [[NSObject alloc] init];
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge  CFTypeRef)(object)),object,&object);
__weak typeof(id) weakObject = object;
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge  CFTypeRef)(object)),object,&object);
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge  CFTypeRef)(weakObject)),weakObject,&weakObject);
    
NSObject *object1 = object;
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge  CFTypeRef)(object)),object,&object);
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge  CFTypeRef)(object1)),object1,&object1);

/**
1 - <NSObject: 0x600000448090> - 0x7ff7bfc0e338
1 - <NSObject: 0x600000448090> - 0x7ff7bfc0e338
2 - <NSObject: 0x600000448090> - 0x7ff7bfc0e330
2 - <NSObject: 0x600000448090> - 0x7ff7bfc0e338
2 - <NSObject: 0x600000448090> - 0x7ff7bfc0e318
*/

image.png

探索弱引用表留下的问题依然没有解决,这时候接着分析,在NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObject)),weakObject,&weakObject);\打断点,进入下面的方法: image.png

那么查看 objc_loadWeakRetained

id
objc_loadWeakRetained(id *location)
{
    id obj;
    id result;
    Class cls;

    SideTable *table;
    
 retry:
    // fixme std::atomic this load
    obj = *location;//weakObject
    if (!obj) return nil;
    if (obj->isTaggedPointer()) return obj;
    
    table = &SideTables()[obj];
    
    table->lock();
    if (*location != obj) {
        table->unlock();
        goto retry;
    }
    
    result = obj;

    cls = obj->ISA();
    if (! cls->hasCustomRR()) {
        // Fast case. We know +initialize is complete because
        // default-RR can never be set before then.
        ASSERT(cls->isInitialized());
        
        //rootTryRetain
        if (! obj->rootTryRetain()) {
            result = nil;
        }
    }
    else {
        // Slow case. We must check for +initialize and call it outside
        // the lock if necessary in order to avoid deadlocks.
        if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
            BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
                class_getMethodImplementation(cls, @selector(retainWeakReference));
            if ((IMP)tryRetain == _objc_msgForward) {
                result = nil;
            }
            else if (! (*tryRetain)(obj, @selector(retainWeakReference))) {
                result = nil;
            }
        }
        else {
            table->unlock();
            class_initialize(cls, obj);
            goto retry;
        }
    }
        
    table->unlock();
    return result;
}

rootTryRetain => rootRetain

ALWAYS_INLINE **bool** 
objc_object::rootTryRetain()
{
    **return** rootRetain(**true**, **false**) ? **true** : **false**;
}

ALWAYS_INLINE 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 (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)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            return 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);
            }
            // Leave half of the retain counts inline and 
            // prepare to copy the other half to the side table.
            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;
}

经过上面代码的探索,可以得知,获取弱引用的引用计数的时候会依次走如下方法: CFGetRetainCount -> objc_loadWeakRetained -> rootTryRetain -> rootRetain,在rootRetain中会对引用计数加1,因此上面的引用计数为2的问题就有答案了。注意这次引用计数+1是临时的,当CFGetRetainCount执行完毕,引用计数就-1.

弱引用表的管理与非弱引用表的管理是分开的。

辅助理解的图

image.png

上面两个断点依次会执行的内容:

image.png

image.png

关于timer的强引用

下面这句代码会造成循环引用

self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

原因如下:

self -> timer -> self
runloop -> timer

扩展: 官方文档对于timerWithTimeInterval方法的target 参数解释:

The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to this object until it (the timer) is invalidated.

如何解决这种循环引用呢?

首先想到使用 weak,因此尝试如下:

__weak typeof(self) weakSelf = self;
self.timer = [NSTimer timerWithTimeInterval:1 target:weakSelf selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

发现还是没有解决强引用的问题,原因如下:

self-> timer - weak -> self
runloop -> timer -> weak -> self 
这里对weak会进行强引用。

而之前block与这个不同,block对weak是弱引用
self -> block -> weak -> self

解决方案一

- (void)didMoveToParentViewController:(UIViewController *)parent{
    // 无论push 进来 还是 pop 出去 正常跑
    // 就算继续push 到下一层 pop 回去还是继续
    if (parent == nil) {
       [self.timer invalidate];
        self.timer = nil;
        NSLog(@"timer 走了");
    }
}

或者

- (void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    // push 到下一层返回就不走了!!!
    [self.timer invalidate];
    self.timer = nil;
    NSLog(@"timer 走了");
}

解决方案二

中介者模式 - 不方便使用 self 换其他对象

self.target = [[NSObject alloc] init];
class_addMethod([NSObject class], @selector(fireHome), (IMP)fireHomeObjc, "v@:");
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.target selector:@selector(fireHome) userInfo:nil repeats:YES];

void fireHomeObjc(id obj){
   NSLog(@"%s -- %@",__func__,obj);
}

在中介者基础上进行优化:

self.timerWapper = [[LGTimerWapper alloc] lg_initWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];

关于LGTimerWapper的源码,参考链接 LGTimerWapper

解决方案三

proxy 虚基类的方式

self.proxy = [LGProxy proxyWithTransformObject:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(fireHome) userInfo:nil repeats:YES];