OC底層原理01—alloc + init + new原理

iOS--OC底層原理文章匯總

alloc

Object-C 是一門面向?qū)ο笳Z言,對象的設計在iOS編程中用到很多,比如ObjectA * objectA = [[ObjectA alloc]init] ,這就是一個類or對象的基本創(chuàng)建方式。那不禁要問這個alloc在不為人知的背后做了什么呢?今天就來探索一下它
首先,先觀察這樣一段代碼,新建了一個書的類,然后創(chuàng)建它,打印它、以及它的地址

    Book * book1 = [Book alloc];
    Book * book2 = [book1 init];
    Book * book3 = [book1 init];
    
    NSLog(@"%@ - %p - %p",book1,book1,&book1);
    NSLog(@"%@ - %p - %p",book2,book2,&book2);
    NSLog(@"%@ - %p - %p",book3,book3,&book3);

下面是打印結(jié)果

<Book: 0x600003144690> - 0x600003144690 - 0x7ffeeb8db1b8
<Book: 0x600003144690> - 0x600003144690 - 0x7ffeeb8db1b0
<Book: 0x600003144690> - 0x600003144690 - 0x7ffeeb8db1a8

可以看到book1,book2,book3它們打印出來的對象是一樣的,并且地址也是相同的,當取地址符時,打印出三個不一樣的地址。就代碼可以窺見一二,前兩個打印出相同結(jié)果,代表它們都指向了相同的內(nèi)存空間,最后一個取地址符時,相當于一個指針變量,存儲在棧區(qū),指向了開辟空間的對象的指針地址,可以理解為指針的指針,所以三個“變量“打印結(jié)果不一樣。由此我們可以猜測alloc是申請了內(nèi)存空間(實際是如此,我們在學習OC的時候,資料中已經(jīng)說明,但卻沒有講解究竟在底層做了什么),而init則是將內(nèi)存空間的地址返回給指向它的對象,并未對內(nèi)存空間做出修改。下面是簡單示意圖:

其中,一個對象占8個字節(jié),系統(tǒng)會為指向內(nèi)存空間的對象分配相近的指針地址,且其地址為連續(xù)棧地址,按十六進制計算 ,a8 + 8 = b0,b0 + 8 = b8

alloc開辟內(nèi)存空間?

由上面的試驗我們初步猜測alloc是在做著申請開辟內(nèi)存空間的事,那我們就要去代碼底層一探究竟,當我們想要查看alloc定義的時候,蘋果卻沒有讓我們繼續(xù)的入口,它只是簡單給出了這樣一個定義+ (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
我們可以通過這樣的兩個方式

  • 1.查看蘋果關于objc的開源,下載一個相應的版本即可研究它;
  • 2.在對象創(chuàng)建時,給alloc這一行下一個端點,按住鍵盤control+斷點調(diào)試欄的Step into按鈕,則會進入一個新的界面。
    更深一步查看alloc

    會出現(xiàn)類似以下的信息
xxproject`objc_alloc:
->  0x10e747476 <+0>: jmpq   *0x2b9c(%rip)             ; (void *)0x000000010e7474cc

objc_alloc就是我們需要繼續(xù)探索的地方。
為了方便分析,還是下載蘋果對應的源代碼 781,并對其進行工程編譯,參考這里,在工程代碼中搜索objc_alloc,找到NSObject下,會出現(xiàn)

// Calls [cls alloc].
id
objc_alloc(Class cls)
{
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}

進入callAlloc,就會出現(xiàn)下面的代碼

// Call [cls alloc] or [cls allocWithZone:nil], with appropriate 
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
    if (slowpath(checkNil && !cls)) return nil;
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        return _objc_rootAllocWithZone(cls, nil);
    }
#endif

    // No shortcuts available.
    if (allocWithZone) {
        return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
    }
    return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}

_objc_rootAllocWithZone繼續(xù)走會出現(xiàn)

id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
    // allocWithZone under __OBJC2__ ignores the zone parameter
    return _class_createInstanceFromZone(cls, 0, nil,
                                         OBJECT_CONSTRUCT_CALL_BADALLOC);
}

繼續(xù)往下面走就來到了_class_createInstanceFromZone的實現(xiàn)

static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{
    ASSERT(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
    size_t size;
    //  計算開辟內(nèi)存大小
    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        // alloc 開辟內(nèi)存
        obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    if (!zone && fast) {
        // 關聯(lián)cls類和obj指針地址
        obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (fastpath(!hasCxxCtor)) {
        return obj;
    }

    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}

由此得到了alloc的基本流程


alloc執(zhí)行流程

關于fastpath 和 slowpath:

//x很可能為真, fastpath 可理解為 真值判斷
#define fastpath(x) (__builtin_expect(bool(x), 1)) 
//x很可能為假,slowpath 可理解為 假值判斷
#define slowpath(x) (__builtin_expect(bool(x), 0)) 

總結(jié)下來,alloc的步驟有
1.開辟多少內(nèi)存
2.開辟內(nèi)存,返回指針
3.關聯(lián)相應類

init做了什么?

在源碼工程中繼續(xù)查找init,發(fā)現(xiàn)這樣的源碼


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

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

// Replaced by CF (throws an NSException)
+ (id)init {
    return (id)self;
}

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

通過源碼發(fā)現(xiàn)這里的+init方法是工廠設計模式下,以供開發(fā)者重寫的構(gòu)造方法。-init實例方法依然是做了返回自身的操作,進入 _objc_rootInit

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;
}

它返回的就是obj本身。

new又做了什么?

在平常的開發(fā)過程中,我們會不自覺的使用new去新建對象,貌似這樣沒有什么問題,但是到底是不是都沒問題呢?進入new源碼中,new做了一個alloc + init的操作,它一步到位的實現(xiàn)了對象的創(chuàng)建。但該init是NSObject的init,對于未重寫init的類時,使用new是ok的,但如果子類重寫了init,譬如:(id)initWithName:(NSString *)name, 就不能再使用new方法創(chuàng)建對象,否則它直接走父類的init,重寫的init的邏輯不會執(zhí)行。

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

github 地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。