美文网首页ios 知识点iOSOC底层相关
Objective-C runtime机制(8)——OC对象从创

Objective-C runtime机制(8)——OC对象从创

作者: 无忘无往 | 来源:发表于2019-04-15 17:03 被阅读23次

    在我们前面的几章中,分析了OC的runtime一些底层的数据结构以及实现机制。今天,我们就从一个OC对象的生命周期的角度,来解析在runtime底层是如何实现的。

    我们创建一个对象(或对象引用)有几种方式?

    Student *student = [[Student alloc] init];
    Student *student2 = [Student new];
    
    __weak Student *weakStudent = [Student new];
    
    NSDictionary *dict = [[NSDictionary alloc] init];
    NSDictionary *autoreleaseDict = [NSDictionary dictionary];
    

    有很多种方式,我们就来依次看一下这些方式的背后实现。

    alloc

    要创建一个对象,第一步就是需要为对象分配内存。在创建内存时,我们会调用alloc方法。查看runtime的NSObject +alloc方法实现:

    + (id)alloc {
        return _objc_rootAlloc(self);
    }
    
    // Base class implementation of +alloc. cls is not nil.
    // Calls [cls allocWithZone:nil].
    id
    _objc_rootAlloc(Class cls)
    {
        return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
    }
    

    alloc方法会将self作为参数传入_objc_rootAlloc(Class cls) 方法中注意,因为alloc是一个类方法,因此此时的self是一个Class类型

    最终该方法会落脚到callAlloc方法。

    static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
    
    static ALWAYS_INLINE id
    callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
    {
        if (slowpath(checkNil && !cls)) return nil;
    
    #if __OBJC2__
        if (fastpath(!cls->ISA()->hasCustomAWZ())) {
            // No alloc/allocWithZone implementation. Go straight to the allocator.
            // fixme store hasCustomAWZ in the non-meta class and 
            // add it to canAllocFast's summary
            if (fastpath(cls->canAllocFast())) { // 如果可以fast alloc,走这里
                // No ctors, raw isa, etc. Go straight to the metal.
                bool dtor = cls->hasCxxDtor();
                id obj = (id)calloc(1, cls->bits.fastInstanceSize()); // 直接调用 calloc方法,申请1块大小为bits.fastInstanceSize()的内存
                if (slowpath(!obj)) return callBadAllocHandler(cls);
                obj->initInstanceIsa(cls, dtor);
                return obj;
            }
            else { // 如果不可以fast alloc,走这里,
                // Has ctor or raw isa or something. Use the slower path.
                id obj = class_createInstance(cls, 0); // (1)需要读取cls 的class_ro_t 中的instanceSize,并使之大于16 byte, Because : CF requires all objects be at least 16 bytes. (2)initInstanceIsa
                if (slowpath(!obj)) return callBadAllocHandler(cls);
                return obj;
            }
        }
    #endif
    
        // No shortcuts available.
        if (allocWithZone) return [cls allocWithZone:nil];
        return [cls alloc];
    }
    

    在callAlloc方法里面,做了三件事:

    1. 调用calloc方法,为类实例分配内存
    2. 调用obj->initInstanceIsa(cls, dtor)方法,初始化obj的isa
    3. 返回obj

    在第一件事中,调用calloc方法,你需要提供需要申请内存的大小。在OC中有两条分支:
    (1)can alloc fast
    (2)can't alloc fast

    对于可以alloc fast的类,应该是经过编译器优化的类。这种类的实例大小直接被放到了bits

    struct class_data_bits_t {
    
        // Values are the FAST_ flags above.
        uintptr_t bits;
        ...
    }
    

    而不需要通过bits找到class_rw_t->class_ro_t->instanceSize。省略了这一条查找路径,而是直接读取位值,其创建实例的速度自然比不能alloc fast的类要快。

    而对于不能alloc fast的类,则会进入第二条路径,代码会通过上面所说的通过bits找到class_rw_t->class_ro_t->instanceSize来确定需要申请内存的大小。

    当申请了对象的内存后,还需要初始化类实例对象的isa成员变量:

     obj->initInstanceIsa(cls, hasCxxDtor);
    
    inline void 
    objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
    {
        assert(!cls->instancesRequireRawIsa());
        assert(hasCxxDtor == cls->hasCxxDtor());
    
        initIsa(cls, true, hasCxxDtor);
    }
    
    inline void 
    objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
    { 
        assert(!isTaggedPointer()); 
        
        if (!nonpointer) { // 如果没有启用isa 优化,则直接将cls赋值给isa.cls,来表明当前object 是哪个类的实例
            isa.cls = cls;
        } else { // 如果启用了isa 优化,则初始化isa的三个内容(1) isa基本的内容,包括nonpointer置1以及设置OC magic vaule (2)置位has_cxx_dtor (3) 记录对象所属类的信息。 通过 newisa.shiftcls = (uintptr_t)cls >> 3;
            assert(!DisableNonpointerIsa);
            assert(!cls->instancesRequireRawIsa());
    
            isa_t newisa(0);
    
    #if SUPPORT_INDEXED_ISA
            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.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;
        }
    }
    

    结合代码注释,以及我们在Objective-C runtime机制(5)——iOS 内存管理中提到的关于isa的描述,应该可以理解isa初始化的逻辑。

    init

    我们再来看一下init方法:

    - (id)init {
        return _objc_rootInit(self);
    }
    
    id _objc_rootInit(id obj)
    {
        // In practice, it will be hard to rely on this function.
        // Many classes do not properly chain -init calls.
        return obj;
    }
    

    实现很简单,就是将自身返回,没有做任何其他操作。

    __strong

    Student *student = [[Student alloc] init];
    Student *student2 = [Student new];
    

    在等号的左边,我们通过allocnew的方式创建了两个OC对象。而在右面,我们通过Student *的方式来引用这些对象。

    在OC中,对对象所有的引用都是有所有权修饰符的,所有权修饰符会告诉编译器,该如何处理对象的引用关系。如果代码中没有显示指明所有权修饰符,则默认为__strong所有权。

    因此上面代码实际是:

    __strong Student *student = [[Student alloc] init];
    __strong Student *student2 = [Student new];
    

    对于new方法,苹果的文档解释为:

    Allocates a new instance of the receiving class, sends it an initmessage, and returns the initialized object.

    其实就是alloc + init 方法的简写。因此,这里的两种创建实例对象的方式可以理解是一个。

    那么,当所有权修饰符是__strong时,runtime是如何管理对象引用的呢?

    runtime会通过 void objc_storeStrong(id *location, id obj) 方法来处理__strong 引用。 这里的location就是引用指针,即Student *student,而obj就是被引用的对象,即Student实例

    void objc_storeStrong(id *location, id obj)
    {
        id prev = *location;
        if (obj == prev) {
            return;
        }
        objc_retain(obj);       //1.  retain obj
        *location = obj;            //2.  将location 指向 obj
        objc_release(prev);   //3. release location之前指向的obj
    }
    
    

    代码逻辑很简单,主要是调用了objc_retain和objc_release两个方法。
    我们分别来看一下它们的实现。

    objc_retain

    id 
    objc_retain(id obj)
    {
        if (!obj) return obj;
        if (obj->isTaggedPointer()) return obj;
        return obj->retain();
    }
    
    inline id 
    objc_object::retain()
    {
        assert(!isTaggedPointer());
    
        if (fastpath(!ISA()->hasCustomRR())) {
            return rootRetain();
        }
    
        return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
    }
    

    可以看到,objc_retain方法最终会调到objc_object类的rootRetain方法:

    ALWAYS_INLINE id 
    objc_object::rootRetain()
    {
        return rootRetain(false, 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)) {  // 如果没有采用isa优化, 则返回sidetable记录的内容, 用slowpath表明这不是一个大概率事件
                ClearExclusive(&isa.bits);
                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;
            }
            // 采用了isa优化,做extra_rc++,同时检查是否extra_rc溢出,若溢出,则extra_rc减半,并将另一半转存至sidetable
            uintptr_t carry;
            newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++
    
            if (slowpath(carry)) { // 有carry值,表示extra_rc 溢出
                // newisa.extra_rc++ overflowed
                if (!handleOverflow) {  // 如果不处理溢出情况,则在这里会递归调用一次,再进来的时候,handleOverflow会被rootRetain_overflow设置为true,从而进入到下面的溢出处理流程
                    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();
                
                // 进行溢出处理:逻辑很简单,先在extra_rc中留一半计数,同时把has_sidetable_rc设置为true,表明借用了sidetable,然后把另一半放到sidetable中
                sideTableLocked = true;
                transcribeToSideTable = true;
                newisa.extra_rc = RC_HALF;
                newisa.has_sidetable_rc = true;
            }
        } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits))); // 将oldisa 替换为 newisa,并赋值给isa.bits(更新isa_t), 如果不成功,do while再试一遍
    
        if (slowpath(transcribeToSideTable)) { //isa的extra_rc溢出,将一半的refer count值放到sidetable中
            // 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;
    }
    

    这一段rootRetain方法,在我们之前的文章Objective-C runtime机制(5)——iOS 内存管理已经做过分析。

    我们就在总结一下rootRetain方法的流程:

    1. 取出当前对象的isa.bits值
    2. isa.bits分别赋值给oldisanewisa
    3. 根据isa_t的标志位newisa.nonpointer,来判断runtime是否只开启了isa优化。
    4. 如果newisa.nonpointer为0,则走老的流程,调用sidetable_retain方法,在SideTable中找到this对应的节点,side table refcntStorage + 1
    5. 如果newisa.nonpointer为1,则在newisa.extra_rc上做引用计数+1操作。同时,需要判断是否计数溢出。
    6. 如果newisa.extra_rc溢出,则进行溢出处理:newisa.extra_rc计数减半,将计数的另一半放到SideTable中。并设置newisa.has_sidetable_rc = true,表明引用计数借用了SideTable
    7. 最后,调用StoreExclusive,更新对象的isa.bits

    <font color=orange>总结:</font>
    __strong引用会使得被引用对象计数+1,同时,会使得之前的饮用对象计数-1。

    __weak

    __weak Student *weakStudent = [Student new];
    

    当使用__weak所有权修饰符来引用对象时?会发生什么呢?

    当weakStudent弱引用Student对象时,会调用objc_initWeak方法。当weakStudent超出其作用域要销毁时,会调用objc_destoryWeak方法。
    我们分别看一下它们的实现:

    /** 
     * Initialize a fresh weak pointer to some object location. 
     * It would be used for code like: 
     *
     * (The nil case) 
     * __weak id weakPtr;
     * (The non-nil case) 
     * NSObject *o = ...;
     * __weak id weakPtr = o;
     * 
     * This function IS NOT thread-safe with respect to concurrent 
     * modifications to the weak variable. (Concurrent weak clear is safe.)
     *
     * @param location Address of __weak ptr. 
     * @param newObj Object ptr. 
     */
    
    // @param location __weak 指针的地址
    // @param newObj 被弱引用的对象指针
    // @return __weak 指针
    
    id
    objc_initWeak(id *location, id newObj)
    {
        if (!newObj) {
            *location = nil;
            return nil;
        }
    
        return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
            (location, (objc_object*)newObj);
    }
    
    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) { // 如果weak ptr之前弱引用过一个obj,则将这个obj所对应的SideTable取出,赋值给oldTable
            oldObj = *location;
            oldTable = &SideTables()[oldObj];
        } else {
            oldTable = nil; // 如果weak ptr之前没有弱引用过一个obj,则oldTable = nil
        }
        if (haveNew) { // 如果weak ptr要weak引用一个新的obj,则将该obj对应的SideTable取出,赋值给newTable
            newTable = &SideTables()[newObj];
        } else {
            newTable = nil; // 如果weak ptr不需要引用一个新obj,则newTable = nil
        }
        
        // 加锁操作,防止多线程中竞争冲突
        SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
    
        // location 应该与 oldObj 保持一致,如果不同,说明当前的 location 已经处理过 oldObj 可是又被其他线程所修改
        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())  // 如果cls还没有初始化,先初始化,再尝试设置weak
            {
                SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
                _class_initialize(_class_getNonMetaClass(cls, (id)newObj));
    
                // If this class is finished with +initialize then we're good.
                // If this class is still running +initialize on this thread 
                // (i.e. +initialize called storeWeak on an instance of itself)
                // then we may proceed but it will appear initializing and 
                // not yet initialized to the check above.
                // Instead set previouslyInitializedClass to recognize it on retry.
                previouslyInitializedClass = cls; // 这里记录一下previouslyInitializedClass, 防止改if分支再次进入
    
                goto retry; // 重新获取一遍newObj,这时的newObj应该已经初始化过了
            }
        }
    
        // Clean up old value, if any.
        if (haveOld) {
            weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); // 如果weak_ptr之前弱引用过别的对象oldObj,则调用weak_unregister_no_lock,在oldObj的weak_entry_t中移除该weak_ptr地址
        }
    
        // Assign new value, if any.
        if (haveNew) { // 如果weak_ptr需要弱引用新的对象newObj
            // (1) 调用weak_register_no_lock方法,将weak ptr的地址记录到newObj对应的weak_entry_t中
            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
            
            // (2) 更新newObj的isa的weakly_referenced bit标志位
            // 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.
            // (3)*location 赋值,也就是将weak ptr直接指向了newObj。可以看到,这里并没有将newObj的引用计数+1
            *location = (id)newObj; // 将weak ptr指向object
        }
        else {
            // No new value. The storage is not changed.
        }
        
        // 解锁,其他线程可以访问oldTable, newTable了
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
    
        return (id)newObj; // 返回newObj,此时的newObj与刚传入时相比,weakly-referenced bit位置1
    }
    

    可以看到,storeWeak函数会根据haveOld参数来决定是否需要处理weak 指针之前弱引用的对象。我们这里的weakStudent是第一次弱引用对象(a fresh weak pointer),因此,haveOld = false。关于haveOld = false的情况,我们稍后分析。

    当haveOld = false时,storeWeak函数做的事情如下:

    1. 取出引用对象对应的SideTable节点SideTable *newTable;
    2. 调用weak_register_no_lock方法,将weak pointer的地址记录到对象对应的weak_entry_t中。
    3. 更新对象isa的weakly_referenced bit标志位,表明该对象被弱引用了。
    4. 将weak pointer指向对象
    5. 返回对象

    关于weak_register_no_lock以及weak相关的数据结构,我们在Objective-C runtime机制(6)——weak引用的底层实现原理有相关探讨,就不再复述。

    下面看另一种情况:

     __weak Son *son = [Son new];
     son = [Son new];
    

    当weakStudent再次指向另一个对象时,则不会调用objc_initWeak方法,而是会调用objc_storeWeak方法:

    /** 
     * This function stores a new value into a __weak variable. It would
     * be used anywhere a __weak variable is the target of an assignment.
     * 
     * @param location The address of the weak pointer itself
     * @param newObj The new object this weak ptr should now point to
     * 
     * @return \e newObj
     */
    id
    objc_storeWeak(id *location, id newObj)
    {
        return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
            (location, (objc_object *)newObj);
    }
    

    其实还是调用了storeWeak方法,只不过DontHaveOld参数换成了DoHaveOld

    当传入DoHaveOld时,storeWeak会进入分支:

      // Clean up old value, if any.
        if (haveOld) {
            weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); // 如果weak_ptr之前弱引用过别的对象oldObj,则调用weak_unregister_no_lock,在oldObj的weak_entry_t中移除该weak_ptr地址
        }
    
    void
    weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                            id *referrer_id)
    {
        objc_object *referent = (objc_object *)referent_id;
        objc_object **referrer = (objc_object **)referrer_id;
    
        weak_entry_t *entry;
    
        if (!referent) return;
    
        if ((entry = weak_entry_for_referent(weak_table, referent))) { // 查找到referent所对应的weak_entry_t
            remove_referrer(entry, referrer);  // 在referent所对应的weak_entry_t的hash数组中,移除referrer
           
            // 移除元素之后, 要检查一下weak_entry_t的hash数组是否已经空了
            bool empty = true;
            if (entry->out_of_line()  &&  entry->num_refs != 0) {
                empty = false;
            }
            else {
                for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                    if (entry->inline_referrers[i]) {
                        empty = false; 
                        break;
                    }
                }
            }
    
            if (empty) { // 如果weak_entry_t的hash数组已经空了,则需要将weak_entry_t从weak_table中移除
                weak_entry_remove(weak_table, entry);
            }
        }
    
        // Do not set *referrer = nil. objc_storeWeak() requires that the 
        // value not change.
    }
    

    weak_unregister_no_lock方法中,将weak pointer的地址从对象的weak_entry_t中移除,同时会判断weak_entry_t是否已经空了,如果空了,则需要把weak_entry_tweak_table中移除。

    <font color=orange>总结:</font>
    __weak引用对象时,会在对象的weak_entry_t中登记该weak pointer的地址(这也就是为什么当对象释放时,weak pointer会被置为nil)。如果weak pointer之前已经弱引用过其他对象,则要先将weak pointer地址从其他对象的weak_entry_t中移除,同时,需要对weak_entry_t进行判空逻辑。

    autorelease

    NSDictionary *dict = [[NSDictionary alloc] init];
    NSDictionary *autoreleaseDict = [NSDictionary dictionary];
    

    当我们创建NSDictionary对象时,有这么两种方式。那么,这两种方式有什么区别呢?

    在ARC时代,若方法名以下列词语开头,则其返回对象归调用者所有(意为需调用者负责释放内存,但对ARC来说,其实并没有手动release的必要)

    • alloc
    • new
    • copy
    • mutableCopy

    而不使用这些词语开头的方法,如[NSDictionary dictionary]
    根据苹果官方文档,当调用[NSDictionary dictionary]时:

    This method is declared primarily for use with mutable subclasses of NSDictionary.
    If you don’t want a temporary object, you can also create an empty dictionary using alloc and init.

    似乎是说,当调用[NSDictionary dictionary]的形式时,会产生一个临时的对象。类似的,还有[NSArray array], [NSData data]

    关于这种形式生成的变量,则表示“方法所返回的对象并不归调用者所有”。在这种情况下,返回的对象会自动释放。

    其实我们可以理解为:当调用dictionary形式生成对象时,NSDictionary对象的引用计数管理,就不需要用户参与了(这在MRC时代有很大的区别,但是对于ARC来说,其实和alloc形式没有太大的区别了)。用[NSDictionary dictionary]其实相当于代码

    [[NSDictionary alloc] init] autorelease];
    

    这里会将NSDictionary对象交给了autorelease pool来管理。

    事实是这样的吗?我们查看[NSDictionary dictionary]的汇编代码(Product->Perform Action->Assemble),可以看到,编译器会调用objc_retainAutoreleasedReturnValue方法。而objc_retainAutoreleasedReturnValue又是什么鬼?这其实是编译器的一个优化,前面我们说[NSDictionary dictionary]会在方法内部为NSDictionary实例调用autorelease,而如果这时候在外面用一个强引用来引用这个NSDictionary对象的话,还是需要调用一个retain,而此时,的autorelease和retain其实是可以相互抵消的。于是,编译器就给了一个优化,不是直接调用autorelease方法,而是调用objc_retainAutoreleasedReturnValue来做这样的判断,如果autorelease后面紧跟了retain,则将autorelease和retain都抵消掉,不再代码里面出现。(详见《Effective Objective-C 2.0》 P126)。

    OK,上面是一些题外话,我们回到autorelease的主题上来。在ARC时代,我们通过如下形式使用autorelease:

    @autorelease {
            // do your code
    }
    

    实质上,编译器会将如上形式的代码转换为:

    objc_autoreleasePoolPush();
    // do your code
    objc_autoreleasePoolPop();
    

    查看它们在runtime中的定义:

    void *
    objc_autoreleasePoolPush(void)
    {
        return AutoreleasePoolPage::push();
    }
    
     static inline void *push() 
        {
            id *dest;
            if (DebugPoolAllocation) {
                // Each autorelease pool starts on a new pool page.
                dest = autoreleaseNewPage(POOL_BOUNDARY);
            } else {
                dest = autoreleaseFast(POOL_BOUNDARY);
            }
            assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
            return dest;
        }
    
     static inline id *autoreleaseFast(id obj)
        {
            AutoreleasePoolPage *page = hotPage();
            if (page && !page->full()) {
                return page->add(obj);
            } else if (page) {
                return autoreleaseFullPage(obj, page);
            } else {
                return autoreleaseNoPage(obj);
            }
        }
    

    可以看到,当push到autorelease时,最终会调用到autoreleaseFast, 在autoreleaseFast中,会首先取出当前线程的hotPage,根据当前hotPage的三种状态:

    1. hot page存在且未满,调用page->add(obj)
    2. hot page存在但已满, 调用autoreleaseFullPage(obj, page)
    3. hot page不存在,调用 autoreleaseNoPage(obj)

    关于这三个方法的实现细节,我们在Objective-C runtime机制(5)——iOS 内存管理有详细的分析。

    当需要pop autorelease pool时,则会调用objc_autoreleasePoolPop()

    void
    objc_autoreleasePoolPop(void *ctxt)
    {
        AutoreleasePoolPage::pop(ctxt);
    }
    
    static inline void pop(void *token) 
        {
            AutoreleasePoolPage *page;
            id *stop;
    
            if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
                // Popping the top-level placeholder pool.
                if (hotPage()) {
                    // Pool was used. Pop its contents normally.
                    // Pool pages remain allocated for re-use as usual.
                    pop(coldPage()->begin());
                } else {
                    // Pool was never used. Clear the placeholder.
                    setHotPage(nil);
                }
                return;
            }
    
            page = pageForPointer(token);
            stop = (id *)token;
            if (*stop != POOL_BOUNDARY) {
                if (stop == page->begin()  &&  !page->parent) {
                    // Start of coldest page may correctly not be POOL_BOUNDARY:
                    // 1. top-level pool is popped, leaving the cold page in place
                    // 2. an object is autoreleased with no pool
                } else {
                    // 这是为了兼容旧的SDK,看来在新的SDK里面,token 可能的取值只有两个:(1)POOL_BOUNDARY, (2)page->begin() && !page->parent也就是第一个page
                    // Error. For bincompat purposes this is not
                    // fatal in executables built with old SDKs.
                    return badPop(token);
                }
            }
    
            if (PrintPoolHiwat) printHiwat();
    
            page->releaseUntil(stop);  // 对token之前的object,每一个都调用objc_release方法
    
            // memory: delete empty children
            if (DebugPoolAllocation  &&  page->empty()) {
                // special case: delete everything during page-per-pool debugging
                AutoreleasePoolPage *parent = page->parent;
                page->kill();
                setHotPage(parent);
            } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) {
                // special case: delete everything for pop(top) 
                // when debugging missing autorelease pools
                page->kill();
                setHotPage(nil);
            } 
            else if (page->child) {
                // hysteresis: keep one empty child if page is more than half full
                if (page->lessThanHalfFull()) {
                    page->child->kill();
                }
                else if (page->child->child) {
                    page->child->child->kill();
                }
            }
        }
    

    在Pop中,会根据传入的token,调用 page->releaseUntil(stop) 方法,对每一个存储于page上的object调用objc_release(obj)方法。

    之后,还会根据当前page的状态:page->lessThanHalfFull()或其他,来决定其child的处理方式:

    1. 如果当前page存储的object已经不满半页,则讲page的child释放
    2. 如果当前page存储的object仍满半页,则保留一个空的child,并且将空child之后的所有child都释放掉。

    retain count

    当我们需要获取对象的引用计数时,在ARC下可以调用如下方法:

    CFGetRetainCount((__bridge CFTypeRef)(obj))
    

    这是CF的方法调用,而在runtime中,我们可以调用NSObject的方法:

    - (NSUInteger)retainCount OBJC_ARC_UNAVAILABLE;
    

    通过注释,可以知道在ARC环境下,该方法是不可用的,但是不影响我们了解它的具体实现。

    - (NSUInteger)retainCount {
        return ((id)self)->rootRetainCount();
    }
    

    方法里面讲self转为id类型,即objc_object类型,然后调用objc_objectrootRetainCount()方法。

    inline uintptr_t 
    objc_object::rootRetainCount()
    {
        //case 1: 如果是tagged pointer,则直接返回this,因为tagged pointer是不需要引用计数的
        if (isTaggedPointer()) return (uintptr_t)this;
    
        // 将objcet对应的sidetable上锁
        sidetable_lock();
        isa_t bits = LoadExclusive(&isa.bits);
        ClearExclusive(&isa.bits);
        // case 2: 如果采用了优化的isa指针
        if (bits.nonpointer) {
            uintptr_t rc = 1 + bits.extra_rc; // 先读取isa.extra_rc
            if (bits.has_sidetable_rc) { // 如果使用了sideTable来存储retain count, 还需要读取sidetable中的数据
                rc += sidetable_getExtraRC_nolock(); // 总引用计数= rc + sidetable
            }
            sidetable_unlock();
            return rc;
        }
        // case 3:如果没采用优化的isa指针,则直接返回sidetable中的值
        sidetable_unlock();
        return sidetable_retainCount();
    }
    

    获取retain count的方法很简单:

    1. 判断object是否使用了isa优化
    2. 如果使用了isa优化,先取出1 + bits.extra_rc
    3. 再判断是否需要读取side talbe( if (bits.has_sidetable_rc)
    4. 如果需要,则加上side table 中存储的retain count
    5. 如果没有使用isa优化,则直接读取side table 中的retain count,并加1,作为引用计数。

    还有一种特殊的情况是,如果object pointer是tagged pointer,则不参与任何操作。

    release

    当object需要引用计数减一时,会调用release方法。

    objc_object::rootRelease()
    {
        return rootRelease(true, false);
    }
    
    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)) { // 慢路径 : 如果没有开启isa优化,则到sidetable中引用计数减一
                ClearExclusive(&isa.bits); // 空方法
                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)) { // 如果下溢出,则goto underflow
                // don't ClearExclusive()
                goto underflow;
            }
        } while (slowpath(!StoreReleaseExclusive(&isa.bits, 
                                                 oldisa.bits, newisa.bits))); // 修改isa bits(如果不成功,则进入while循环,再试一把,直到成功为止)
    
        if (slowpath(sideTableLocked)) sidetable_unlock();
        return false;  // 如果没有溢出,则在这里就会返回false(表明引用计数不等于0,没有dealloc)
    
        // 只有isa.extra_rc  -1 下溢出后,才会进入下面的代码。下溢出有两种情况:
        // 1. borrow from side table . isa.extra_rc 有从side table存储。这是假溢出,只需要将side table中的RC_HALF移回到isa.extra_rc即可。并返回false
        // 2. deallocate。 这种情况是真下溢出。此时isa.extra_rc < 0,且没有newisa.has_sidetable_rc 没有想side table 借位。说明object引用计数==0,(1) 设置newisa.deallocating = true;
        //  (2)触发object 的dealloc方法, (3)并返回true,表明对象deallocation
        //
            // 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();
            //
            //    __sync_synchronize();
            //    if (performDealloc) {
            //        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
            //    }
            //    return true;
        //
        //
        ////////////////////////////////////////////////////////////////////////////////////////////////////////
     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)) {  // 如果借用了 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); // ClearExclusive 是一个空函数
                sidetable_lock();
                sideTableLocked = true;
                // Need to start over to avoid a race against 
                // the nonpointer -> raw pointer transition.
                goto retry;
            }
    
            // 如果extra_rc 减1后,其值carryout(小于0),则要处理side table,如果之前有在side talbe中借位RC_HALF,则把这RC_HALF在拿回来到extrc_rc中,并保留side table剩下的值
            // 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); // 如果更新 isa extra_rc 失败,则把side table中的数再放回去 (好尴尬),然后再试一把
                    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();
    
        __sync_synchronize();
        if (performDealloc) {
            ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
        }
        return true;
    }
    

    这里的逻辑主要有两块:

    1. 如果没有使用isa.extra_rc作引用计数,则调用sidetable_release,该方法会到side table中做计数减一,同时,会check 计数是否为0,如果为0,则调用对象的dealloc方法。
    2. 如果使用了isa.extra_rc作引用计数,则在isa.extra_rc中做引用计数减一,同时需要判断是否下溢出(carry > 0)
     newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
    

    这里要注意处理下溢出的逻辑:

    1. 首先,下溢出是针对isa.extra_rc来说的。也就是启用了isa优化引用计数才会走到 underflow: 代码段。
    2. 造成isa.extra_rc下溢出其实有两个原因:borrow from side table or deallocate。要注意对这两个下溢出原因的不同处理。

    dealloc

    当对象引用计数为0时,会调用对象的dealloc方法,这在上面的release方法中,是通过

    ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    

    来调用的。

    我们来看一下NSObjectdealloc方法是怎样实现的:

    - (void)dealloc {
        _objc_rootDealloc(self);
    }
    
    void _objc_rootDealloc(id obj)
    {
        assert(obj);
        obj->rootDealloc();
    }
    
    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))
        {
            // 如果没有weak引用 & 没有关联对象 & 没有c++析构 & 没有side table借位
            // 就直接free 
            assert(!sidetable_present());
            free(this);
        } 
        else {
            object_dispose((id)this);
        }
    }
    
    id 
    object_dispose(id obj)
    {
        if (!obj) return nil;
    
        objc_destructInstance(obj);    // step 1. 先调用runtime的objc_destructInstance
        free(obj);  // step 2. free 掉这个obj
    
        return nil;
    }
    
    /***********************************************************************
    * objc_destructInstance
    * Destroys an instance without freeing memory. 
    * Calls C++ destructors.
    * Calls ARC ivar cleanup.
    * Removes associative references.
    * Returns `obj`. Does nothing if `obj` is 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); // 调用C++析构函数
            if (assoc) _object_remove_assocations(obj); // 移除所有的关联对象,并将其自身从Association Manager的map中移除
            obj->clearDeallocating(); // 清理相关的引用
        }
    
        return obj;
    }
    

    在对象dealloc的过程中,会根据当前对象isa_t的各个标志位,来做对应的清理工作,清理完毕后,会调用free(obj)来释放内存。

    清理工作会在objc_destructInstance方法中进行,主要包括:

    • 如果有C++析构函数,调用C++析构
    • 如果有关联对象,调用_object_remove_assocations(obj)将关联在该对象的对象移除
    • 调用obj->clearDeallocating()方法,主要是(1)将weak 引用置为nil,并在weak_table_t中删除对象节点。(2)如果有side table计数借位,则side table中对应的节点移除

    总结

    本篇文章从[[NSObject alloc] init]方法说起,讲解了alloc,init背后的实现逻辑,以及OC中的所有权修饰符__strong, __weak。并讲述了autoreleasepool的背后实现。同时,分析了retain 和 release引用计数相关函数。

    最终,我们分析了对象dealloc所做的清理工作。

    相关文章

      网友评论

        本文标题:Objective-C runtime机制(8)——OC对象从创

        本文链接:https://www.haomeiwen.com/subject/dicpwqtx.html