OC內存管理--對象的生成與銷燬

博客連接OC內存管理--對象的生成與銷燬bash

在iOS開發中了,咱們天天都會使用+ alloc- init這兩個方進行對象的初始化。咱們也這知道整個對象的初始化過程其實就是開闢一塊內存空間,而且初始化isa_t結構體的過程ide

alloc的實現

+ (id)alloc {
    return _objc_rootAlloc(self);
}

id _objc_rootAlloc(Class cls) {
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
複製代碼

整個過程其實就是NSObjectcallAlloc方法的實現。函數

callAlloc

/*
 cls:CustomClass
 checkNil:是否檢查Cls
 allocWithZone:是否分配到指定空間,默認爲false,內部會對其進行優化
*/
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false) {
    //沒有class或則checkNil爲YES,返回空
    if (slowpath(checkNil && !cls)) return nil;

//確保只有Objective-C 2.0語言的文件所引用
#if __OBJC2__
    //判斷class有沒有默認的allocWithZone方法
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        // class能夠快速分配
        if (fastpath(cls->canAllocFast())) {
            //hasCxxDtor();是C++析構函數,判斷是否有析構函數
            bool dtor = cls->hasCxxDtor();
            //申請class的內存空間
            id obj = (id)calloc(1, cls->bits.fastInstanceSize());
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            //初始化isa指針
            obj->initInstanceIsa(cls, dtor);
            return obj;
        }
        else {
            //使用class_createInstance建立class
            id obj = class_createInstance(cls, 0);
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            return obj;
        }
    }
#endif
    
    //說明有默認的allocWithZone的方法,調用allocWithZone方法
    if (allocWithZone) return [cls allocWithZone:nil];
    return [cls alloc];
}
複製代碼

__OBJC2__下當前類有沒有默認的allocWithZone方法是經過hasCustomAWZ()函數判斷的。YES表明有則會調用[cls allocWithZone:nil]方法。NO表明沒有,這時候會根據當前類是否能夠快速分配,NO的話調用class_createInstance函數;YES則分配內存並初始化isa。源碼分析

allocWithZone

+ (id)allocWithZone:(struct _NSZone *)zone {
    return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}

id _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone) {
    id obj;

#if __OBJC2__
    // allocWithZone under __OBJC2__ ignores the zone parameter
    (void)zone;
    obj = class_createInstance(cls, 0);
#else
    if (!zone) {
        obj = class_createInstance(cls, 0);
    }
    else {
        obj = class_createInstanceFromZone(cls, 0, zone);
    }
#endif
    if (slowpath(!obj)) obj = callBadAllocHandler(cls);
    return obj;
}
複製代碼

allocWithZone函數的本質是調用_objc_rootAllocWithZone函數。優化

_objc_rootAllocWithZone的邏輯分爲兩種狀況:ui

  1. 先判斷是不是__OBJC2__,若是是則調用class_createInstance
  2. 判斷zone是否爲空,若是爲空調用class_createInstance,若是不爲空,調用class_createInstanceFromZone
//class_createInstance
id class_createInstance(Class cls, size_t extraBytes) {
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}

//class_createInstanceFromZone
id class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone) {
    return _class_createInstanceFromZone(cls, extraBytes, zone);
}
複製代碼

class_createInstanceclass_createInstanceFromZone的本質都是調用_class_createInstanceFromZonethis

另外經過前面的源代碼咱們能夠發現:用alloc方式建立,只要當前類有allocWithZone方法,最終必定是調用class_createInstanceatom

_class_createInstanceFromZone

static __attribute__((always_inline)) 
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil) {
    if (!cls) return nil;

    assert(cls->isRealized());

    bool hasCxxCtor = cls->hasCxxCtor();//構造函數
    bool hasCxxDtor = cls->hasCxxDtor();//析構函數
    bool fast = cls->canAllocNonpointer(); //是對isa的類型的區分,若是一個類不能使用isa_t類型的isa的話,fast就爲false,可是在Objective-C 2.0中,大部分類都是支持的
    //在分配內存以前,須要知道對象在內存中的大小,也就是instanceSize的做用。對象必須大於等於16字節。
    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) {
        //分配內存空間
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        //初始化isa指針
        obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
        //此時的fast 爲 false
        //在C語言中,malloc表示在內存的動態存儲區中分配一塊長度爲「size」字節的連續區域,返回該區域的首地址;calloc表示在內存的動態存儲區中分配n塊長度爲「size」字節的連續區域,返回首地址。
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if (!obj) return nil;

        //初始化isa指針
        obj->initIsa(cls);
    }

    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    return obj;
}
複製代碼

初始化isa

_class_createInstanceFromZone中不光開闢了內存空間,還初始化了isa。初始化isa的方法有initInstanceIsainitIsa,可是本質都是調用initIsa(Class cls, bool nonpointer, bool hasCxxDtor)spa

inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    assert(!isTaggedPointer()); 
    
    if (!nonpointer) {
        isa.cls = cls; //obj->initIsa(cls)
    } else {
        //obj->initInstanceIsa(cls, hasCxxDtor);
        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; } } 複製代碼

根據《OC引用計數器的原理》,如今再看一下初始化isa的方法。這個方法的意思是首先判斷是否開啓指針優化。指針

沒有開啓指針優化的話訪問 objc_objectisa會直接返回isa_t結構中的cls變量,cls變量會指向對象所屬的類的結構。

開啓指針優化的話經過newisa(0)函數初始化一個isa,並根據SUPPORT_INDEXED_ISA分別設置對應的值。iOS設備的話這個值是0,因此執行else的代碼。

到這裏alloc的實現過程已經結束了,根據上面的源碼分析,用一張圖表示上述過程:

image

這裏可能會有個疑問,既然alloc將分配內存空間和初始化isa的事情都作了,那麼init的做用是什麼呢?

init

- (id)init {
    return _objc_rootInit(self);
}

id _objc_rootInit(id obj) {
    return obj;
}
複製代碼

init的做用就是返回當前對象。這裏有個問題既然init只是返回當前對象,爲何要畫蛇添足呢?

Apple給出的註釋:

In practice, it will be hard to rely on this function. Many classes do not properly chain -init calls.

意思是在實踐中,很難依靠這個功能。許多類沒有正確連接init調用。因此這個函數極可能不被調用。也許是歷史遺留問題吧。

new

+ (id)new {
    return [callAlloc(self, false/*checkNil*/) init];
}
複製代碼

因此說UIView *view = [UIView new];UIView *view = [[UIView alloc]init];是同樣的。

dealloc

分析了對象的生成,咱們如今看一下對象是如何被銷燬的。dealloc的實現以下:

- (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))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}
複製代碼

rootDealloc分爲三種狀況:

  1. 若是是TaggedPointer,直接return;
  2. 進行一些關於isa的條件判斷,若是知足就釋放分配的內存控件;
  3. 調用object_dispose函數,這是最重要的;

objc_destructInstance

咱們先看object_dispose函數的源碼:

id object_dispose(id obj) {
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}
複製代碼

作了兩件事情:

  1. 調用objc_destructInstance函數
  2. 釋放分配的內存空間

objc_destructInstance的實現以下:

/***********************************************************************
* 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);//調用析構函數
        if (assoc) _object_remove_assocations(obj);//刪除關聯對象
        obj->clearDeallocating();//清空引用計數表並清除弱引用表
    }

    return obj;
}
複製代碼

objc_destructInstance作了三件事情:

  1. 執行object_cxxDestruct調用析構函數
  2. 執行_object_remove_assocations刪除關聯對象
  3. 執行clearDeallocating清空引用計數表並清除弱引用表,將全部weak引用指nil(這也解釋了爲何使用weak能自動置空)

object_cxxDestruct

在源碼中object_cxxDestruct的實現由object_cxxDestructFromClass完成。

static void object_cxxDestructFromClass(id obj, Class cls)
{
    void (*dtor)(id);

    // Call cls's dtor first, then superclasses's dtors.

    for ( ; cls; cls = cls->superclass) {
        if (!cls->hasCxxDtor()) return; 
        dtor = (void(*)(id))
            lookupMethodInClassAndLoadCache(cls, SEL_cxx_destruct);
        if (dtor != (void(*)(id))_objc_msgForward_impcache) {
            if (PrintCxxCtors) {
                _objc_inform("CXX: calling C++ destructors for class %s", 
                             cls->nameForLogging());
            }
            (*dtor)(obj);
        }
    }
}
複製代碼

這段代碼的意思就是沿着繼承鏈逐層向上搜尋SEL_cxx_destruct這個selector,找到函數實現(void (*)(id)(函數指針)並執行。說白了就是找析構函數,並執行析構函數。

析構函數中書如何處理成員變量的?

  1. 對於strong來講執行objc_storeStrong(&ivar, nil)release舊對象,ivar賦新值nil;
  2. 對於weak來講執行objc_destroyWeak(&ivar)消除對象weak表中的ivar地址。

關於這個函數Sunnyxx ARC下dealloc過程及.cxx_destruct的探究中也有提到。

用一張圖表示dealloc的流程:

image

至於dealloc的調用時機,是跟引用計數器相關的。

相關文章
相關標籤/搜索