OC源碼 —— alloc, init, new和dealloc

上一篇最后講release的時候說到,在release的最后,當引用計數(shù)減為0的時候就進入了dealloc的過程。這一篇就來講講dealloc和相關(guān)的一些方法。先從dealloc的對頭alloc說起。


alloc

關(guān)于alloc,最常見的用法應(yīng)該算是:

[[XXClass alloc] init];

方法alloc的調(diào)用棧是這樣的:

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

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

static id callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    return class_createInstance(cls, 0);
}

id  class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}

static id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    size_t size = cls->instanceSize(extraBytes);
    id obj = (id)calloc(1, size);
    obj->initInstanceIsa(cls, hasCxxDtor);
    return obj;
}

在深入alloc方法的時候,我發(fā)現(xiàn)alloc方法的調(diào)用棧很深,但是做的事情其實不多,中間很多方法看起來一大段,其實真正執(zhí)行的只有一小部分,所以我在展示源代碼的時候只保留了最常用的代碼。

下面就來分析一下alloc方法最深處的_class_createInstanceFromZone()方法。

  • size_t size = cls->instanceSize(extraBytes);
size_t instanceSize(size_t extraBytes) {
    size_t size = alignedInstanceSize() + extraBytes;
    // CF requires all objects be at least 16 bytes.
    if (size < 16) size = 16;
    return size;
}

uint32_t alignedInstanceSize() {
  return word_align(unalignedInstanceSize());
}

uint32_t unalignedInstanceSize() {
    return data()->ro->instanceSize;
}

static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}
#   define WORD_MASK 7UL

第一步獲取對象的大小,注釋已經(jīng)講的很清楚,對象大小最小為16。并且word_align()方法確保大小是8的倍數(shù),其實就是按字節(jié)對齊。

  • id obj = (id)calloc(1, size);
    懂一點c的人都明白,這是分配一塊大小為size的內(nèi)存,并返回指向起始地址的指針。

  • obj->initInstanceIsa(cls, hasCxxDtor);
    初始化isa,這一部分的源代碼在Runtime源碼 —— 對象、類和isa這篇文章中已經(jīng)講過了,這里就不說了。

  • return obj;
    最后返回初始化好的obj。

總的來說alloc方法還是很簡單的。


init

init方法更簡單:

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

id _objc_rootInit(id obj)
{
    return obj;
}

簡單到什么都沒做。

看到這里,我就奇怪了,這init什么都不做,還要調(diào)用了干什么,只要用一個alloc不就夠了么,我測試了一下:

alloc方法測試.png

只調(diào)用alloc,可以設(shè)置屬性,可以調(diào)用方法,看起來我們習慣的寫法是遠古遺留的產(chǎn)物。


new

現(xiàn)在其實這么寫的已經(jīng)不多了:

[[XXClass alloc] init];

大多數(shù)都用一個new代替了:

[XXClass new];

這個方法也很簡單:

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

其實就是把alloc和init結(jié)合起來了,不過現(xiàn)在init什么事都不做,new和alloc方法其實是一個意思了。


dealloc

自從進入ARC時代,dealloc方法已經(jīng)不常見了,除非有CF對象需要釋放。

本文開始的時候也提到過,在release方法最后,當引用計數(shù)降為0的時候,會調(diào)用dealloc方法釋放內(nèi)存??纯磀ealloc方法究竟做了什么事。

- (void)dealloc {
    _objc_rootDealloc(self);
}

void _objc_rootDealloc(id obj)
{
    obj->rootDealloc();
}

inline void objc_object::rootDealloc()
{
    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

在最后的rootDealloc()方法中,有個if判斷,條件分為5個部分:

  1. isa.nonpointer
    是否是nonpointer,基本都是
  2. !isa.weakly_referenced
    是否被弱引用
  3. !isa.has_assoc
    是否有關(guān)聯(lián)對象
  4. !isa.has_cxx_dtor
    是否有c++析構(gòu)器
  5. !isa.has_sidetable_rc
    引用計數(shù)是否曾經(jīng)溢出過

如果以上判斷條件都為真,那么可以快速釋放對象,這也是為什么在Runtime源碼 —— 對象、類和isa這篇文章中介紹isa結(jié)構(gòu)體相關(guān)字段時提到過的可以快速釋放對象,就是在這里知道的。

那么大多數(shù)情況是什么樣的呢?大多數(shù)情況這個判斷都是false,因為第4點,只需要類中聲明過屬性或者實例變量,那么就需要c++析構(gòu)函數(shù)來釋放這些ivars。所以想要快速釋放,要求還挺高。

那就看看慢速釋放的過程吧:

object_dispose((id)this);

id object_dispose(id obj)
{
    objc_destructInstance(obj);    
    free(obj);
    return nil;
}

void *objc_destructInstance(id obj) 
{
    if (obj) {
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}

釋放對象的時機是在objc_destructInstance()方法調(diào)用之后,這個方法做的事情是銷毀這個對象存在的證據(jù),分成3個部分銷毀:

  • object_cxxDestruct(obj);
  • _object_remove_assocations(obj);
  • obj->clearDeallocating();
part1
object_cxxDestruct(obj);

void object_cxxDestruct(id obj)
{
    object_cxxDestructFromClass(obj, obj->ISA());
}

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) {
            (*dtor)(obj);
        }
    }
}

學過c++的都知道析構(gòu)函數(shù)從子類開始,逐層向上調(diào)用。這個方法其實就做了這個事情,有一點奇怪的就是,在我們的類中,并沒有聲明過任何析構(gòu)函數(shù),那么查找到的析構(gòu)函數(shù)究竟是什么呢?

用代碼測試一下,先在類中增加一個屬性,這樣就能進入這個方法了:

// ZNObject.h
@interface ZNObject : NSObject
@property (nonatomic, copy) NSString *name;
@end

// ZNObject.m
@implementation ZNObject
- (void)dealloc {
    NSLog(@"znobject dealloc");
}

接著添加這些代碼用來測試dealloc的過程:

// ViewController.m
@interface ViewController() 
@property (nonatomic, strong) ZNObject *obj;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.obj = [ZNObject new];
}

- (void)viewDidAppear {
    self.obj = nil;
}

挺愚蠢的,其實加個大括號就可以了。

在self.obj = nil;這行先加個斷點,進入斷點之后,在object_cxxDestructFromClass()方法中添加一個斷點,運行進入斷點如下圖:

尋找c++析構(gòu)函數(shù).png

先看左側(cè)的調(diào)用棧,當我將self.obj設(shè)為nil的時候,就進入了release的過程。這里引用計數(shù)為0,所以繼續(xù)進入了dealloc的過程。先調(diào)用了ZNObject類中的dealloc,打印出了log,然后就進入了本節(jié)dealloc的流程。

看看dtor的內(nèi)容,析構(gòu)函數(shù)是一個叫做.cxx_destruct的方法,但是這個方法找不到實現(xiàn)的代碼,怎么才能看到這個方法做了什么呢?

換個思路試試看,c++的析構(gòu)函數(shù)是在對象有ivar的時候才會被調(diào)用,調(diào)用的目的就是為了釋放這些ivar,那么我們只需要觀察ivar的變化情況就可以了。

調(diào)整一下測試的代碼,先給屬性賦個值,再運行:

觀察name屬性的變化.png

添加一個watchpoint,當name變化的時候,會自動進入斷點,直接運行程序:

.cxx_destruct內(nèi)部.png

可以看到name的銷毀是在objc_storeStrong方法中進行的,這個方法被.cxx_destroy調(diào)用。遺憾的是,依然不能窺探到.cxx_destroy究竟做了些什么事。

講了這么多,其實就說明了ivar釋放的過程,下面進入第二步。

part2
_object_remove_assocations(obj);

void _object_remove_assocations(id object) {
    vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        if (associations.size() == 0) return;
        disguised_ptr_t disguised_object = DISGUISE(object);
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {
            // copy all of the associations that need to be removed.
            ObjectAssociationMap *refs = i->second;
            for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
                elements.push_back(j->second);
            }
            // remove the secondary table.
            delete refs;
            associations.erase(i);
        }
    }
    // the calls to releaseValue() happen outside of the lock.
    for_each(elements.begin(), elements.end(), ReleaseValue());
}

關(guān)于關(guān)聯(lián)對象,最常見的應(yīng)用應(yīng)該就是在category中聲明屬性。這一塊這里就不展開了,后面談Associated Object的時候再說吧。

這里只要知道這一塊做的事情就是移除對象的associated object,更直接一點就是如果有這樣的代碼:

objc_setAssociatedObject(self.obj, @selector(obj), self.obj2, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

在self.obj對象dealloc的時候,就會進入_object_remove_assocations(obj)方法。

part3
obj->clearDeallocating();

inline void objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // Slow path for non-pointer isa with weak refs and/or side table data.
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}

在這一節(jié)最開始提到了一共5個判斷條件,part1對應(yīng)的是4和part2對應(yīng)的是3,其他3個條件就都在這個方法里啦。

第一個if判斷對應(yīng)1,因為基本都是nonpointer,所以直接看else中的內(nèi)容。else中對應(yīng)了2和5兩個條件,看看clearDeallocating_slow()是怎么做的:

void objc_object::clearDeallocating_slow()
{
    SideTable& table = SideTables()[this];
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
}

這里再次出現(xiàn)了SideTables,在上一篇講release的時候有提到過,SideTable存儲了溢出的引用計數(shù),也與弱引用相關(guān),這里其實就是對這兩部分進行處理。關(guān)于SideTable的具體內(nèi)容就不展開了。這里只需要知道:

  • 如果有對此對象的弱引用,那么把所有的弱引用都置為nil。weak對象設(shè)為nil原來就是在這里進行的。
  • 如果引用計數(shù)曾經(jīng)溢出過,那么SideTable中就存儲過相關(guān)信息,當然在這個時間點,引用計數(shù)的值肯定是為0的,但是即使是0也不放過,還要把曾經(jīng)存在的痕跡抹除掉。感覺好殘忍呀。。。

到這里就完成了dealloc的全部過程。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容