iOS Objective-C底層 part2:born

以下內(nèi)容以至少你已經(jīng)理解OC內(nèi)萬(wàn)物皆對(duì)象的概念為基礎(chǔ),當(dāng)然你還得有一份可以跑得objc源碼

1. Obj before born

在我們還沒(méi)有書寫代碼創(chuàng)建對(duì)象時(shí),內(nèi)存內(nèi)已經(jīng)滿是對(duì)象(類,元類)了.

2. Obj 誕生 alloc

2.1 申請(qǐng)堆空間
//C code
typedef struct{
    char name[21];
    char age;
}CustomStruct;

typedef CustomStruct * CustomStructPointer;

int main(int argc, const char * argv[]) {
    CustomStructPointer stu = (CustomStructPointer)malloc(sizeof(CustomStruct));
    stu->age = 10;
    strcpy(stu->name, "pogong");
    printf("stack address %p\n",&stu);
    printf("heap address %p\n",stu);
    free(stu);
    return 0;
}

打印:
stack address 0x7fff5fbff708
heap address 0x100403ff0
C_memory_map.jpeg
//OC code

//PGCustomClass.h
@interface PGCustomClass : NSObject
@property(nonatomic,copy)NSString * name;
@property(nonatomic,assign)int age;
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        PGCustomClass * obj = [[PGCustomClass alloc]init];
        NSLog(@"stack address %p",&obj);
        NSLog(@"heap address %p",obj);
    }
    return 0;
}

打印:
stack address 0x7fff5fbff728
heap address 0x101a02bc0
OC_memory_map.jpeg

以上是C語(yǔ)言的一個(gè)棧上的結(jié)構(gòu)體指針指向堆上的結(jié)構(gòu)體實(shí)例的代碼+內(nèi)存示意圖和OC的一個(gè)棧上的對(duì)象指針指向堆上的對(duì)象實(shí)例的代碼+內(nèi)存示意圖.
因?yàn)镺C的對(duì)象說(shuō)到底還是個(gè)結(jié)構(gòu)體實(shí)例,所以O(shè)C的對(duì)象生成的結(jié)果和C語(yǔ)言生成結(jié)構(gòu)體指針指向結(jié)構(gòu)體實(shí)例的結(jié)果是一樣的.當(dāng)然OC的對(duì)象生成過(guò)程會(huì)比較復(fù)雜,因?yàn)镺C可是優(yōu)雅的動(dòng)態(tài)語(yǔ)言誒!以下就是曲折的誕生過(guò)程:

alloc調(diào)用堆棧.png

alloc像內(nèi)的調(diào)用棧大概如上圖所示,看代碼的捋很久,也不需要全都記住,主要知道幾個(gè)關(guān)鍵參數(shù),關(guān)鍵條件和關(guān)鍵實(shí)現(xiàn)就可以了.

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())) {
            // No ctors, raw isa, etc. Go straight to the metal.
            bool dtor = cls->hasCxxDtor();
            id obj = (id)calloc(1, cls->bits.fastInstanceSize());
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            obj->initInstanceIsa(cls, dtor);
            return obj;
        }
        else {
            // Has ctor or raw isa or something. Use the slower path.
            id obj = class_createInstance(cls, 0);
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            return obj;
        }
    }
#endif
    // No shortcuts available.
    if (allocWithZone) return [cls allocWithZone:nil];
    return [cls alloc];
}

1.hasCustomAWZ存在于類的元類中,標(biāo)識(shí)這個(gè)類有沒(méi)有復(fù)寫alloc/allocWithZone:;
2.canAllocFast是否支持快速創(chuàng)建.

可以看出最終都調(diào)用了_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());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();

    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) {
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if (!obj) return nil;

        // Use raw pointer isa on the assumption that they might be 
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

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

    return obj;
}

幾大判斷條件:
hasCxxCtor:類及父類是否有自己的構(gòu)造函數(shù);
hasCxxDtor:類及父類是否有自己的析構(gòu)函數(shù)(這個(gè)條件在后面講對(duì)象dealloc的時(shí)候也會(huì)說(shuō)到,與對(duì)象是否有實(shí)例變量有關(guān),這條件會(huì)記錄在對(duì)象的isa內(nèi));
fast:類是否用了是優(yōu)化的isa;

canAllocNonpointer and SUPPORT_NONPOINTER_ISA
兩個(gè)都帶nonpointer,
SUPPORT_NONPOINTER_ISA是來(lái)標(biāo)識(shí)當(dāng)前平臺(tái)是否支持優(yōu)化的isa,但即使平臺(tái)支持,具體到某一個(gè)類卻是不一定的,要具體的去驗(yàn)證類的元類的信息.不過(guò)可以放心大多系統(tǒng)的類的isa都是支持優(yōu)化的,我們自定義的類的isa也是支持優(yōu)化的.
canAllocNonpointer則是具體標(biāo)記某個(gè)類是否支持優(yōu)化的isa.
在閱讀源碼時(shí)還有會(huì)各種帶nonpointer字樣的針對(duì)優(yōu)化isa的標(biāo)記,除SUPPORT_NONPOINTER_ISA外,全是針對(duì)某個(gè)類而言的.
優(yōu)化的isa是什么?接下來(lái)會(huì)說(shuō).

zone:老版本中要先去看看zone是否有空間,在OBJC2下巩梢,忽略zone參數(shù).

size:這由外圍傳入,size存儲(chǔ)在類的元類內(nèi)

// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() {
    assert(isRealized());
    return data()->ro->instanceSize;
}

// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() {
    return word_align(unalignedInstanceSize());
}

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

size_t size = cls->instanceSize(extraBytes);    

data()->ro->instanceSize;上方的注解May be unaligned depending on class's ivars..類實(shí)例的instanceSize取決于類的中成員變量的個(gè)數(shù):

instanceSize.png

再看看最后的調(diào)用:calloc或者malloc_zone_calloc就和C語(yǔ)言在堆中申請(qǐng)空間如出一轍了.

2.2 isa init
  • isa沒(méi)那么簡(jiǎn)單,因?yàn)閮?yōu)化了

在part1內(nèi)已經(jīng)提過(guò)了多遍的isa,當(dāng)然只要知道OC內(nèi)萬(wàn)物皆對(duì)象,也肯定知道類實(shí)例->類->元類用isa串聯(lián)起來(lái)的關(guān)系:

simple_isa.png

但具體到真實(shí)的應(yīng)用場(chǎng)景下,isa的串聯(lián)會(huì)比上圖描繪更復(fù)雜更具體一些,特別是在64位系統(tǒng)上.所有用了64位系統(tǒng)的電子產(chǎn)品都沒(méi)有用全64位來(lái)表示地址.
因?yàn)檫@不現(xiàn)實(shí):32位==>4G內(nèi)存,64位==>你算算看.

64位不全拿來(lái)表示地址,這就給64位的isa留下了很大的優(yōu)化空間(32位時(shí)對(duì)象的isa只是指向類而已).
我們截取類實(shí)例到類的過(guò)程來(lái)說(shuō)明優(yōu)化的isa

instance_point_to_class_simple.png

先找到關(guān)于id的定義:

typedef struct objc_object *id;

然后objc_object又是什么:

struct objc_object {
private:
    isa_t isa;
}

然后再看isa_t是什么(這里只看arm64的):

union isa_t 
{
Class cls;
uintptr_t bits;
struct {
        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;
    };
}

這個(gè)union isa_t新奇了,聯(lián)合體少見吧!更奇怪的是聯(lián)合體還嵌套了結(jié)構(gòu)體,有不明白的請(qǐng)戳.
簡(jiǎn)單的說(shuō)就是:Class cls+uintptr_t bits+struct{......}共用一塊64位的內(nèi)存空間,當(dāng)然只有一個(gè)有效,在SUPPORT_NONPOINTER_ISA為1的情況下,仍然有一些類不支持優(yōu)化的isa,所以這樣的union isa_t就支持多用:

Class cls->為未優(yōu)化版的isa指向一個(gè)類

uintptr_t bit+struct{......}
uintptr_t bit用于對(duì)64位統(tǒng)一賦值,
struct{......}做細(xì)化讀取與細(xì)化賦值

請(qǐng)注意這聯(lián)合體內(nèi)結(jié)構(gòu)體內(nèi)的這個(gè)字段shiftcls,shiftcls=shift class,短的類地址.union isa_t共計(jì)64位,shiftcls占33位.這就是一個(gè)操作系統(tǒng)地址變量?jī)?yōu)化的細(xì)節(jié).在64位iPhone上只拿33位表示地址的,也就是說(shuō)這的shiftcls就存儲(chǔ)了類實(shí)例歸屬的類的地址.如圖:

instance_point_to_class_real.png

當(dāng)然類對(duì)象指向元類對(duì)象也是一樣的道理.
除了shiftcls之外,isa_t內(nèi)的各個(gè)字段均有用處,這些也就是64位的isa具體優(yōu)化的地方:

nonpointer:1->表示使用優(yōu)化的isa指針
has_assoc:1->是否包含關(guān)聯(lián)對(duì)象
has_cxx_dtor:1->是否包含析構(gòu)函數(shù)
shiftcls:33->類的指針
magic:6->固定值,用于判斷是否完成初始化
weakly_referenced:1->對(duì)象是否指向一個(gè)弱引用對(duì)象
deallocating:1->對(duì)象是否正在銷毀
has_sidetable_rc:1->在extra_rc存儲(chǔ)引用計(jì)數(shù)將要溢出的時(shí)候,借助sidetable(散列表)存儲(chǔ)引用計(jì)數(shù),has_sidetable_rc設(shè)置成1
extra_rc:19->存儲(chǔ)引用計(jì)數(shù)

后面章節(jié)的文章會(huì)細(xì)說(shuō)關(guān)于這些字段所實(shí)現(xiàn)和優(yōu)化的功能.

  • 初始化對(duì)象的isa

初始化對(duì)象的isa要么initInstanceIsa->initIsa,要么直接調(diào)用initIsa->initIsa.

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

SUPPORT_NONPOINTER_ISA前面已經(jīng)說(shuō)過(guò),而SUPPORT_INDEXED_ISA 為 1是另外一種優(yōu)化,用isa內(nèi)indexcls存儲(chǔ)著類在類列表內(nèi)的索引,這個(gè)用在watch上,手機(jī)和電腦上沒(méi)有這么用.

所以再看objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)的實(shí)現(xiàn)就簡(jiǎn)單多了.

不支持nonpointer的,isa.cls = cls;

支持nonpointer的,
對(duì)newisa.bits賦值,即對(duì)isa的64位統(tǒng)一初始化賦值,(統(tǒng)一初始化賦值)
newisa.has_cxx_dtor記錄傳入的has_cxx_dtor,(細(xì)化賦值)
newisa.shiftcls記錄下cls的地址.(細(xì)化賦值)

newisa.shiftcls = (uintptr_t)cls >> 3;(為什么右移3位?)
拿手機(jī)舉例子:shiftcls:33;(shiftcls會(huì)分配到33位),在64位的手機(jī)上拿33位保存類的地址,但因?yàn)槲粚?duì)齊的緣故,所有地址都是8的倍數(shù),所有地址書寫的成二進(jìn)制數(shù)最后3位全是0,所以才如上見到:cls >> 3;(消除了3個(gè)沒(méi)有影響的0)

3. Obj 裝扮 init

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

在沒(méi)有復(fù)寫init方法的情況下,init的實(shí)現(xiàn)特別簡(jiǎn)單.

- (instancetype)init
{
    self = [super init];
    if (self) {
        _name = @"pogong";
        _age = 28;
    }
    return self;
}

復(fù)寫init的情況下能做的也只是對(duì)類實(shí)例成員變量的初始化裝扮.

init.png

當(dāng)然這樣的工作不在init內(nèi)部也能完成.

born.png

4. Obj 怪胎 Tagged Pointer

事情是是要從32位系統(tǒng)轉(zhuǎn)向64系統(tǒng)說(shuō)起.
32位系統(tǒng)下:

NSNumber * num = [[NSNumber alloc]initWithInt:1];

棧上4個(gè)字節(jié)的對(duì)象指針指向堆上8個(gè)字節(jié)(存儲(chǔ)isa4個(gè)字節(jié)+存儲(chǔ)值4個(gè)字節(jié))的對(duì)象實(shí)例,共計(jì)12個(gè)字節(jié).

64位系統(tǒng)下:

NSNumber * num = [[NSNumber alloc]initWithInt:1];

棧上8個(gè)字節(jié)的對(duì)象指針指向堆上16個(gè)字節(jié)(存儲(chǔ)isa8個(gè)字節(jié)+存儲(chǔ)值8個(gè)字節(jié))的對(duì)象實(shí)例,共計(jì)24個(gè)字節(jié).
保存一個(gè)int要用8個(gè)字節(jié),包裝成對(duì)象要24字節(jié),有點(diǎn)太浪費(fèi)了.
所以Tagged Pointer應(yīng)運(yùn)而生,

NSNumber *number1 = @1;
NSNumber *number2 = @2;
NSNumber *number3 = @3;
NSNumber *numberFFFF = @(0xFFFF);

NSLog(@"number1 pointer is %p", number1);
NSLog(@"number2 pointer is %p", number2);
NSLog(@"number3 pointer is %p", number3);
NSLog(@"numberffff pointer is %p", numberFFFF);

打印:
number1 pointer is 0xb000000000000012
number2 pointer is 0xb000000000000022
number3 pointer is 0xb000000000000032
numberffff pointer is 0xb0000000000ffff2

我們前面已經(jīng)講過(guò),因?yàn)?4位系統(tǒng)上8位對(duì)齊,16進(jìn)制打印出的地址最后一位不是8就是0(2進(jìn)制打印后三位全是0),而這里最后一位是2,很怪異,這就是對(duì)Tagged Pointer的標(biāo)記.再將標(biāo)記位的前面的數(shù)值和對(duì)象本身的值進(jìn)行比較一模一樣.
Tagged Pointer就是將Tagged Pointer的標(biāo)記混在一塊64位的內(nèi)存內(nèi).看上去是對(duì)象,但卻沒(méi)有isa(一個(gè)沒(méi)有靈魂的對(duì)象==>Tagged Pointer).但索性現(xiàn)在的isa也不能直接被調(diào)用,所以不會(huì)造成什么不便.
棧上8個(gè)字節(jié)的對(duì)象指針指向堆上8個(gè)字節(jié)(Tagged Pointer)的對(duì)象實(shí)例,共計(jì)16個(gè)字節(jié).
Tagged Pointer的引入,節(jié)約了64位系統(tǒng)的內(nèi)存,提高了運(yùn)行效率.
NSNumber外,NSDate,NSString都應(yīng)用到Tagged Pointer.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末琳钉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖零截,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡盐茎,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門徙赢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)字柠,“玉大人,你說(shuō)我怎么就攤上這事狡赐∫ひ担” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵枕屉,是天一觀的道長(zhǎng)常柄。 經(jīng)常有香客問(wèn)我,道長(zhǎng)搀擂,這世上最難降的妖魔是什么西潘? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮哨颂,結(jié)果婚禮上喷市,老公的妹妹穿的比我還像新娘。我一直安慰自己威恼,他們只是感情好品姓,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著沃测,像睡著了一般缭黔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蒂破,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天馏谨,我揣著相機(jī)與錄音,去河邊找鬼附迷。 笑死惧互,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的喇伯。 我是一名探鬼主播喊儡,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼稻据!你這毒婦竟也來(lái)了艾猜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎匆赃,沒(méi)想到半個(gè)月后淤毛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡算柳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年低淡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瞬项。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蔗蹋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出囱淋,到底是詐尸還是另有隱情猪杭,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布绎橘,位于F島的核電站胁孙,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏称鳞。R本人自食惡果不足惜涮较,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望冈止。 院中可真熱鬧狂票,春花似錦、人聲如沸熙暴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)周霉。三九已至掂器,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間俱箱,已是汗流浹背国瓮。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留狞谱,地道東北人乃摹。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像跟衅,于是被迫代替她去往敵國(guó)和親孵睬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容