iOS面试题+基础知识内存

内存管理:引用计数器源码分析

2020-07-15  本文已影响0人  嗯o哼

一、什么是引用计数器

每个对象都维护了自己的引用计数器,
它表示了,当前有多少个对象引用了它。
一旦有对象引用该对象地址,那么它的引用计数器就加1,移除一个引用,引用计数器就减一,当引用计数器为0 的时候,该对象就会被销毁,回收内存。

二、引用计数器的操作

在MRC的时候,需要手动管理内存。
当使用alloc、new或者copy创建一个对象时,对象的引用计数器默认就是1
发送retain消息后 引用计数器 + 1
发送release消息后 引用计数器 -1
发送retainCount消息,可以查看当前引用计数器的值
在ARC中,编译器已经自动为我们进行管理,不需要我们使用retain、release操作

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 只要创建一个对象默认引用计数器的值就是1
        Person *p = [[Person alloc] init];
        NSLog(@"retainCount = %lu", [p retainCount]); // 1

        // 只要给对象发送一个retain消息, 对象的引用计数器就会+1
        [p retain];

        NSLog(@"retainCount = %lu", [p retainCount]); // 2
        // 通过指针变量p,给p指向的对象发送一条release消息
        // 只要对象接收到release消息, 引用计数器就会-1
        // 只要一个对象的引用计数器为0, 系统就会释放对象

        [p release];
        // 需要注意的是: release并不代表销毁\回收对象, 仅仅是计数器-1
        NSLog(@"retainCount = %lu", [p retainCount]); // 1

        [p release]; // 0
        NSLog(@"--------");
    }
//    [p setAge:20];    // 此时对象已经被释放
    return 0;
}

三、引用计数器的存储

64位系统之后,对象的isa指针已经被优化过,isa是一个共用体
不同的变量,存在同一个单元里。通过位运算&不同的掩码值,可以获取到不同位置的值

union isa_t { // union 共用体
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};
// ISA_BITFIELD 宏定义中的内容有以下内容
      uintptr_t nonpointer        : 1; 
      uintptr_t has_assoc         : 1;
      uintptr_t has_cxx_dtor      : 1;
      uintptr_t shiftcls          : 33; 
      uintptr_t magic             : 6; 
      uintptr_t weakly_referenced : 1; 
      uintptr_t deallocating      : 1; 
      uintptr_t has_sidetable_rc  : 1;
      uintptr_t extra_rc          : 19

合并到一起isa 的数据结构就是

union isa_t { // union 共用体
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;
    struct {
      //nonpointer 是否是优化过的指针 0表示普通的指针,存储的是内存地址 
      //1.表示优化过,使用位域存储更多的信息
      uintptr_t nonpointer        : 1;
      // has_assoc 是否设置过关联对象,如果没有释放时更快
      uintptr_t has_assoc         : 1;
      // has_cxx_dtor 是否有C++的析构函数,如果没有释放的更快
      uintptr_t has_cxx_dtor      : 1;
      //shiftcls 真正存储这Class、Meta-class对象的内存地址信息
      uintptr_t shiftcls          : 33; 
      //magic 用于在调试时分辨对象是否未完成初始化
      uintptr_t magic             : 6; 
      //weakly_referenced 是否有被弱引用指向过,如果没有释放更快
      uintptr_t weakly_referenced : 1; 
      //deallocating 对象是否正在释放
      uintptr_t deallocating      : 1; 
      //has_sidetable_rc 引用计数器是否过大,无法存储在isa中,如果为1表示引用计数器会存储在一个叫SideTable的类的属性中
      uintptr_t has_sidetable_rc  : 1;
      // extra_rc 里面存储的值是引用计数器的值-1
      uintptr_t extra_rc          : 19
    };

由此可见,引用计数器的值可分为两个地方存储

1.isa中的extra_rc,isa存储的数量有限
2.一个叫SideTable的类中

四、SideTable结构

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts; // 通过这个散列表存储引用计数器的值
    weak_table_t weak_table; // 弱引用表
}

五、源码分析验证

查看retainCount的源码,分析引用计数器的存储

// retainCount方法
- (NSUInteger)retainCount {
    return ((id)self)->rootRetainCount();
}
// 内部的主要逻辑
objc_object::rootRetainCount()
{
// 首先判断是否是TaggedPointer,如果是,直接返回
    if (isTaggedPointer()) return (uintptr_t)this;
// 如果不是
    sidetable_lock();
// 获取isa信息
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
// 判断isa 是否是优化过的指针
    if (bits.nonpointer) { // 如果是优化过的指针
        uintptr_t rc = 1 + bits.extra_rc; // 获取extra_rc的值 + 1
        if (bits.has_sidetable_rc) { // 判断引用计数器是否存储在sidetable中
// 如果是,获取sidetable中存储的值 +isa中的值
            rc += sidetable_getExtraRC_nolock(); 
        }
        sidetable_unlock();
// 返回 引用计数器的值
        return rc;
    }
    sidetable_unlock();
    return sidetable_retainCount();
}

由此可见,引用计数器的值

1.保存在isa的extra_rc中,extra_rc的值为引用计数器的值-1
2.如果引用计数器的值过大,无法继续存储,剩余的值存储在SideTable中

具体在SideTable中的哪个属性中呢,继续分析代码
sidetable_getExtraRC_nolock的实现

objc_object::sidetable_getExtraRC_nolock()
{
    assert(isa.nonpointer);
    SideTable& table = SideTables()[this];
// 获取SideTable中的refcnts,是一个遍历器
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) return 0; // 如果到最后没有查询到数据,返回0
// 判断里面的second通过位运算,计算出数值并返回
    else return it->second >> SIDE_TABLE_RC_SHIFT;
}

因此,可以确定,引用计数器剩余部分的值,是存储在SideTable中的refcnts的散列表中,通过一个位运算可以获得

retain方法的内部实现

//首先
- (id)retain {
    return ((id)self)->rootRetain();
}
objc_object::rootRetain()
{
    return rootRetain(false, false);
}
// 具体的实现内容
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    // 判断是否是TaggedPointer,如果是直接返回它本身,说明taggedpointer不会被引用
    if (isTaggedPointer()) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;
    isa_t oldisa;
    isa_t newisa;

    do {
        transcribeToSideTable = false;
        // 获取没有retain前的isa
        oldisa = LoadExclusive(&isa.bits);
        // 将新的isa赋值
        newisa = oldisa;
        // 是否是优化过的指针,如果不是,
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits); //此方法内部没有做任何处理
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            // 进入sidetable 中,将引用计数器+1
            else return sidetable_retain();
        }
        
        // 后面的代码执行的都是优化过的isa指针的处理方法
        
        if (slowpath(tryRetain && newisa.deallocating)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            return nil;
        }
        // uintptr_t 是unsigned int的别名
        uintptr_t carry;
        // 新的isa的内容中extra_rc进行 ++的操作
        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 
            // 准备复制另一半到表中
            // 获取sidetable
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            newisa.extra_rc = RC_HALF;//  RC_HALF = 1ULL<<18
            newisa.has_sidetable_rc = true;
        }
        // 判断isa中extra_rc是否能继续存储
    } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));

    // 向sidetable中存储引用计数器
    if (slowpath(transcribeToSideTable)) {
        // 向sidetable中存储引用计数器
        sidetable_addExtraRC_nolock(RC_HALF);
    }

    if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    return (id)this;
}

关于release的操作大同小异

- (oneway void)release {
    ((id)self)->rootRelease();
}
objc_object::rootRelease()
{
    return rootRelease(true, false);
}
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
    // 判断TaggedPointer
    if (isTaggedPointer()) return false;

    bool sideTableLocked = false;

    isa_t oldisa;
    isa_t newisa;

 retry:
    do {
        // 获取isa信息
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        // 判读是否是优化过的isa,如果不是,直接sidetable_release
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (sideTableLocked) sidetable_unlock();
            return sidetable_release(performDealloc);
        }
        // 如果是优化过的isa,将extra_rc--操作
        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;
......
}
//sidetable_release 中会判断对应的引用计数器是否为0,如果是会对对象发送一个dealloc的消息,进行销毁对象

dealloc的调用流程

- (void)dealloc {
    _objc_rootDealloc(self);
}
void _objc_rootDealloc(id obj)
{
    assert(obj);

    obj->rootDealloc();
}
inline void
objc_object::rootDealloc()
{
    // 判断是否是TaggedPointer,如果是直接返回
    if (isTaggedPointer()) return;  // fixme necessary?

    // 如果是优化过的isa & 没有弱引用 & 没有C++的析构函数 & 没有关联对象 & 引用计数器没有保存到sidetable中,直接释放对象
    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    }
    // 如果上面的判断不成立,调用object_dispose方法,进行移除清空上述内容
    else {
        object_dispose((id)this);
    }
}
//object_dispose方法内部又调用objc_destructInstance方法
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.
       // 判断是否具有C++析构函数
        bool cxx = obj->hasCxxDtor();
        // 是否具有关联对象
        bool assoc = obj->hasAssociatedObjects();

        //如果有析构函数,
        if (cxx) object_cxxDestruct(obj);
        //如果有关联对象,移除关联对象
        if (assoc) _object_remove_assocations(obj);
        // 移除弱引用表中的数据和引用计数器
        obj->clearDeallocating();
    }

    return obj;
}

总结:

1.iOS中通过引用计数器管理维护对象是否会被释放
2.引用计数器会被存储在isa中的extra_rc中,如果值过大会存储到一个sidetable中refcnts属性中

上一篇下一篇

猜你喜欢

热点阅读