探尋OC對象的本質(zhì)

探尋OC對象的本質(zhì)段多,我們平時編寫的Objective-C代碼,底層實現(xiàn)其實都是C\C++代碼壮吩,如圖所示:


OC代碼的轉(zhuǎn)化過程

OC的對象結(jié)構(gòu)都是通過基礎C\C++的結(jié)構(gòu)體實現(xiàn)的进苍。
我們通過創(chuàng)建OC文件及對象,并將OC文件轉(zhuǎn)化為C++文件來探尋OC對象的本質(zhì)鸭叙。

OC如下代碼

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *objc = [[NSObject alloc] init];
        NSLog(@"objc的內(nèi)存地址:%p",&objc);
    }
    return 0;
}

我們通過命令行將OC的mian.m文件轉(zhuǎn)化為c++文件觉啊。(生成 main.cpp)

clang -rewrite-objc main.m -o main.cpp // 這種方式?jīng)]有指定架構(gòu)例如arm64架構(gòu) 其中cpp代表(c plus plus)

我們可以指定架構(gòu)模式的命令行,使用xcode工具 xcrun沈贝。(生成 main-arm64.cpp )

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 

在main-arm64.cpp 文件中搜索NSObjcet杠人,可以找到NSObjcet_IMPL(IMPL代表 implementation 實現(xiàn))。

NSObject_IMPL內(nèi)部的實現(xiàn)

typedef struct objc_class *Class;
struct NSObject_IMPL {
    Class isa;
};
// isa 本質(zhì)就是一個指向 objc_class 結(jié)構(gòu)體的指針。

NSObjcet的底層實現(xiàn)嗡善,點擊NSObjcet進入發(fā)現(xiàn)NSObject的內(nèi)部實現(xiàn)辑莫。

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

轉(zhuǎn)化為c語言其實就是一個結(jié)構(gòu)體

struct NSObject_IMPL {
    Class isa;
};

一、一個OC對象在內(nèi)存中是如何布局的罩引?

一個OC對象的內(nèi)存布局入下圖所示:


內(nèi)存布局

二各吨、一個NSObject對象占用多少內(nèi)存?

既然 isa 本質(zhì)上就是一個指針蜒程,一個指針在32位環(huán)境下占用4個字節(jié)绅你,64位環(huán)境下占用8個字節(jié)伺帘。一個 NSObject 對象結(jié)構(gòu)體內(nèi)部就包含一個 isa 指針昭躺,那么,我們可以認為一個 NSObject 對象就占用8個字節(jié)么伪嫁? NO NO NO领炫,雖然表面如此,但是實際上并不是张咳。那么一個NSObject對象占多大的內(nèi)存空間呢帝洪?
實際上:
1、系統(tǒng)分配了16個字節(jié)給 NSObject 對象(通過 malloc_size 函數(shù)獲得)
2 脚猾、但 NSObject 對象內(nèi)部只使用了8個字節(jié)的空間(64bit環(huán)境下葱峡,可以通過 class_getInstanceSize 函數(shù)獲得)

NSObject *obj = [[NSObject alloc] init];

// 獲得 NSObject 實例對象的成員變量所占用的大小 >> 8
NSLog(@"%zd", class_getInstanceSize([NSObject class])); // 輸出為8

// 獲得 obj 指針所指向內(nèi)存的大小 >> 16
NSLog(@"%zd", malloc_size((__bridge const void *)(obj))); // 輸出為 16
我們可以通過打斷點。Xcode -> Debug -> Debug Workflow -> View Memory查看:
View Memory

輸入objc的地址


內(nèi)存空間

這個是什么意思呢龙助?其實一個 NSObject 實例對象的大小確實為8個字節(jié)砰奕,但是系統(tǒng)給其分配的內(nèi)存其實是16個字節(jié)。

接下來我們通過objc4源碼來探究下到底是為什么提鸟。

objc源碼: https://opensource.apple.com/tarballs/objc4/

打開下載好的objc4-750 —> objc-class.mm源碼军援,搜索class_getInstanceSize方法:

size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}

我們會發(fā)現(xiàn)這個方法返回值是cls->alignedInstanceSize(),點進去查看如下:

// Class's ivar size rounded up to a pointer-size boundary.
// 返回值成員變量的占用內(nèi)存大小
uint32_t alignedInstanceSize() {
        return word_align(unalignedInstanceSize());
}

我們繼續(xù)看下 malloc_size称勋,由于蘋果部分源碼不公開胸哥,不過不影響今天討論內(nèi)容,我們先 malloc.h 文件中函數(shù)聲明:

/* Returns size of given ptr */
// 注釋意思:返回分配給指針的占用內(nèi)存大小
extern size_t malloc_size(const void *ptr);

總結(jié):通過閱讀源碼赡鲜,發(fā)現(xiàn)一個 NSObject 對象空厌,系統(tǒng)給其分配的空間為 16 個字節(jié),只不過其真正利用起來的只有 8 個字節(jié)银酬。

真的是分配 16 個字節(jié)么蝇庭?
NSObject *obj = [[NSObject alloc] init];

上面這行代碼,可以發(fā)現(xiàn)捡硅,創(chuàng)建一個新的實例對象哮内,分為兩步:

  • alloc:分配一塊內(nèi)存空間
  • init:初始化

所以,我們想探究實質(zhì)的話可以從 alloc 方法往里面查看,從 alloc 開始搜索的話太多了北发,我們直接從 allocWithZone 開始查看纹因,感興趣的同學可以從 alloc 開始進行查看。

NSObject.mm

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

allocWithZone 調(diào)用的是: _objc_rootAllocWithZone

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

_objc_rootAllocWithZone 分配內(nèi)存空間其實是: class_createInstance

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

繼續(xù)點擊進去查看:

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

我們發(fā)現(xiàn)琳拨,最后調(diào)用 C 語言底層的 calloc 分配內(nèi)存函數(shù)瞭恰,我們發(fā)現(xiàn)傳入了一個 size 參數(shù), size 通過 cls 的 instanceSize 函數(shù)獲得狱庇。

    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) {
        //如果是 NSObject 惊畏,下面這行代碼相當于 size_t size = 8;
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }

通過注釋和代碼可以發(fā)現(xiàn),CF:CoreFoundation密任,硬性規(guī)定颜启,返回 size 最小為16
這是為什么呢浪讳,因為蘋果設計 CF 框架缰盏,包括我們自己設計一套框架,為了我們的框架能夠更好的運行淹遵,肯定會做出一些規(guī)定口猜、約束,這樣就可以理解了透揣。
至于 word_align济炎,涉及到 內(nèi)存對齊 概念,下面的的章節(jié)也會提到一些辐真,但不會涉及太深须尚,感興趣的同學可以 Google 相關文檔。

三拆祈、一個自定義類的對象占用多少內(nèi)存恨闪?

我們可以總結(jié)內(nèi)存對齊為兩個原則:

  • 原則 1. 前面的地址必須是后面的地址整數(shù)倍,不是就補齊。
  • 原則 2. 整個Struct的地址必須是最大字節(jié)的整數(shù)倍放坏。

講到這里咙咽,相信很多小伙伴還是有很多疑問的。剛才只講了NSObject相關知識淤年。我們平常開發(fā)中肯定不會只用NSObject對象钧敞,基本上都是我們自定義自己的對象,接下來麸粮,來通過兩個復雜一點的例子來進行講解溉苛。

(1)自定義一個 Student 類繼承 NSObject :
#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface Student : NSObject{
    @public
    int _no;
    int _age;
}
@end

@implementation Student

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Student *stu = [[Student alloc] init];
              stu -> _no = 4;
              stu -> _age = 5;
              
        NSLog(@"%@",stu);   //<student: 0x102974240>
        NSLog(@"%zd",class_getInstanceSize([Student class]));  //16
    }
    return 0;
}
@end

按照上述步驟同樣生成c++文件。并查找Student弄诲,我們發(fā)現(xiàn)Student_IMPL 整數(shù)

struct Student_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _no;
    int _age;
};

根據(jù)上文通過 NSObject 實例對象講解的鋪墊愚战,Student 實例對象的本質(zhì)以及其在內(nèi)存中布局如下圖所示:


內(nèi)存布局

內(nèi)存布局

從圖中最下面把實例對象 stu 強轉(zhuǎn)成結(jié)構(gòu)體類型 stu2娇唯,通過結(jié)構(gòu)體可以正常進行訪問,也從另一角度證明 stu 底層結(jié)構(gòu)確實為 Student_IMPL 結(jié)構(gòu)體類型寂玲。當然也可以從 View Memory 或者 LLDB 進行證明塔插。

內(nèi)存布局這樣畫可能理解更清楚:

內(nèi)存布局

我們知道sutdent對象中,包含一個isa指針拓哟,一個int類型的_no成員變量想许,和一個int類型的_age成員變量,同樣isa指針8個字節(jié)断序,_age成員變量4個字節(jié)流纹,_no成員變量4個字節(jié),剛好滿足原則1和原則2违诗,所以student對象占據(jù)的內(nèi)存空間也是16個字節(jié)。
student實際占用內(nèi)存為16字節(jié)挚币,系統(tǒng)分配的內(nèi)存也是16字節(jié)郊楣。

(2)舉一反三燃逻,當 Person 繼承 NSObject,Student 繼承 Person 的情況,一個 Person 對象吼拥,一個 Student 對象占用多少內(nèi)存空間?

Student: Person: NSObject:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>

struct NSObject_IMPL {
    Class isa;
};

struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age;
};

struct Student_IMPL {
    struct Person_IMPL Person_IVARS;
    int _no;
};
@interface Person: NSObject {
    @public
    int _age;
}
@end

@implementation Person
@end

@interface Student: Person {
    @public
    int _no;
}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Student *stu = [[Student alloc] init];
        stu->_no = 5;
        NSLog(@"student:%zd, %zd", class_getInstanceSize([Student class]), malloc_size((__bridge const void*)stu));

        Person *per = [[Person alloc] init];
        per->_age = 4;
        NSLog(@"person:%zd, %zd", class_getInstanceSize([Person class]), malloc_size((__bridge const void*)per));
    }
    return 0;
}
內(nèi)存布局

我們先來分析下 Person 實例對象占用多少內(nèi)存空間:

  • struct NSObject_IMPL NSObject_IVARS;即 isa 占用8個字節(jié),int _age; 占4個字節(jié),那么 Person 實例對象占 8 + 4 = 12 個字節(jié)么存璃,錯洒扎,上文中也有提到胡诗,一個 OC 對象至少占用 16 個字節(jié),所以 Person 實例對象占用 16 個字節(jié)匪蝙。

  • 從另外一個角度,其實還有 內(nèi)存對齊 這個概念,就算是沒有 OC對象 至少占用 16 個字節(jié)這個規(guī)定帚称, Person_IMPL 也占用 16 個字節(jié),內(nèi)存對齊有一條規(guī)定:結(jié)構(gòu)體的大小比必須是最大成員大小的倍數(shù)秽澳。

內(nèi)存對齊還有很多規(guī)定肝集,屬于計算機知識范疇,感興趣的同學可以自行 Google仍劈。

我們再來分析下 Student 實例對象占用多少內(nèi)存空間:

  • struct Person_IMPL Person_IVARS:占用 16 個字節(jié)寡壮,int _no:占用 4 個字節(jié)这溅,16 + 4 = 20组民,而且剛講了內(nèi)存對齊規(guī)定結(jié)構(gòu)體大小必須是最大成員變量大小的倍數(shù),那么悲靴, Student_IMPL 占用 16 * 2 = 32 個字節(jié)么臭胜?錯!結(jié)果為 16 * 1 = 16 個字節(jié)癞尚。
  • 為什么呢庇楞?因為 Person_IMPL 雖然分配16個字節(jié),但是實際變量只占用了 12 個字節(jié)否纬,還有 4 個字節(jié)空出來了吕晌,我們偉大的 iOS 系統(tǒng)會這么傻,白白浪費這 4 個字節(jié)的空間么临燃,當然不會睛驳,**所以,int _no膜廊;其實被放到了 Person_IMPL 空余的 4 個字節(jié)空間當中乏沸。

malloc_size 我們已經(jīng)沒有太多疑問了,但是可能對 class_getInstanceSize 還存在疑問爪瓜,class_getInstanceSize 返回 ivar size蹬跃,即成員變量 size,那么上文 Person instance size 為什么不返回 12 呢铆铆?

size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}

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

通過源碼可以發(fā)現(xiàn)蝶缀,class_getInstanceSize 實際返回的其實也是 word_align(unalignedInstanceSize()); 內(nèi)存對齊過的大小。

四薄货、OC對象的分類

Objective-C中的對象翁都,簡稱OC對象,主要可以分為 3 種:

  • instance對象(實例對象)
  • class對象(類對象)
  • meta-class對象(元類對象)
(1) instance對象(實例對象)

instance對象就是通過類alloc出來的對象谅猾,每次調(diào)用alloc都會產(chǎn)生新的instance對象柄慰。

NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
instance對象

object1和object2都是NSObject的instace對象(實例對象),但他們是不同的兩個對象税娜,并且分別占據(jù)著兩塊不同的內(nèi)存坐搔。
instance對象在內(nèi)存中存儲的信息包括:isa指針、其他成員變量敬矩。

(2) class對象(類對象)

我們通過class方法或runtime方法得到一個class對象概行。class對象也就是類對象

// instance 對象,實例對象
NSObject *object = [[NSObject alloc] init];

// class 對象谤绳,類對象      
Class objectClass1 = [object class];
Class objectClass2 = [NSObject class];
// class 方法返回的一直是class對象占锯,類對象
// Class objectClass2 = [[[[NSObject class] class] class] class];
Class objectClass3 = object_getClass(object); // runtime

// 0x100444830 0x7fffa5ab9140 0x7fffa5ab9140 0x7fffa5ab9140
NSLog(@"%p %p %p %p", object, objectClass1, objectClass2, objectClass3);
類對象

1袒哥、objectClass1~objectClass3都是NSObject的class對象(類對象)。
2消略、每一個類在內(nèi)存中有且只有一個class對象堡称。可以通過打印內(nèi)存地址證明艺演。
3却紧、class對象在內(nèi)存中存儲的信息主要包括:
isa指針、superclass指針胎撤、類的屬性信息(@property)晓殊、類的對象方法信息(instance method)、類的協(xié)議信息(protocol)伤提、類的成員變量信息(ivar巫俺,成員變量的值時存儲在實例對象中的,因為只有當我們創(chuàng)建實例對象的時候才為成員變賦值肿男。但是成員變量叫什么名字介汹,是什么類型,只需要有一份就可以了;所以存儲在class對象中舶沛。)嘹承。

(3)meta-class對象(元類對象)
// class 對象,類對象
Class objectClass = [object class];

// meta-class 對象如庭,元類對象
// 將類對象當做參數(shù)傳入叹卷,獲得元類對象;將實例對象當做參數(shù)傳入,獲得的是類對象
Class metaClass1 = object_getClass([object class]);
Class metaClass2 = object_getClass([NSObject class]);
Class metaClass3 = object_getClass(object_getClass(object));

// 0x7fffa5ab90f0 0x7fffa5ab90f0 0x7fffa5ab90f0 0 1
NSLog(@"%p %p %p %d %d", metaClass1, metaClass2, metaClass3, class_isMetaClass(objectClass), class_isMetaClass(metaClass1));
元類對象

1坪它、objectMetaClass是NSObject的meta-class對象(元類對象)骤竹。
2、每個類在內(nèi)存中有且只有一個meta-class對象哟楷。
3瘤载、meta-class對象和class對象的內(nèi)存結(jié)構(gòu)是一樣的,但是用途不一樣卖擅,在內(nèi)存中存儲的信息主要包括:isa指針、superclass指針墨技、類的類方法的信息(class method)惩阶。
4、meta-class對象和class對象的內(nèi)存結(jié)構(gòu)是一樣的扣汪,所以meta-class中也有類的屬性信息断楷,類的對象方法信息等成員變量,但是其中的值可能是空的崭别。

注意:為什么說 meta-class 對象和 class 對象結(jié)構(gòu)一樣冬筒,但是圖上畫的卻不一樣呢恐锣,因為圖上只是將比較重要的一些東西摘了出來,方便理解舞痰。其實本質(zhì)是土榴,class對象類方法信息存儲的可能是null空的,meta-class內(nèi)部屬性信息响牛、對象方法信息玷禽、協(xié)議信息、成員變量信息存儲的可能是null空的呀打。

objc_getClass矢赁、object_getClass方法區(qū)別?

本文和上篇文章有用到這幾個方法贬丛,這幾個方法有什么區(qū)別呢撩银?

Class objc_getClass(const char *aClassName)
{
    if (!aClassName) return Nil;
    // NO unconnected, YES class handler
    return look_up_class(aClassName, NO, YES);
}

Class object_getClass(id obj)
{
    // 如果傳入instance對象,返回class對象
    // 如果傳入class對象豺憔,返回meta-class對象
    // 如果傳入meta-class對象蜒蕾,返回NSObject的meta-class對象
    if (obj) return obj->getIsa();
    else return Nil;
}

從源碼進行分析,objc_getClass傳入?yún)?shù)為字符串焕阿,根據(jù)字符串去Map中取出類對象并返回咪啡。 object_getClass傳入?yún)?shù)為 id,并且返回值是通過 getIsa 獲得暮屡,說明返回 isa 指向的類型(即:傳入instance對象撤摸,返回類對象;傳入class對象褒纲,返回meta-class對象准夷;傳入meta-class對象,返回NSObject的meta-class對象)莺掠。

五衫嵌、isa指針

我們發(fā)現(xiàn),OC對象不管是instance對象彻秆、類對象還是meta-class都有一個isa指針楔绞,那么,isa指針都指向哪里呢唇兑,起到了什么作用酒朵。

我們都知道,OC對象調(diào)用方法是通過消息機制實現(xiàn)的扎附,通過上面的總結(jié)我們也知道了實例方法存放在class對象中蔫耽,類方法存放在meta-class對象中,那么對象是怎么查找到方法并實現(xiàn)調(diào)用呢留夜?

這個時候就需要isa指針了匙铡,instance對象的isa指針指向class對象图甜,class對象的isa指向meta-class對象,通過isa指針鳖眼,instance對象黑毅、class對象、meta-class對象就可以串起來了具帮,方法調(diào)用博肋、以及各種作用就都可以實現(xiàn)了。

(1)當對象調(diào)用實例方法的時候蜂厅,我們上面講到匪凡,實例方法信息是存儲在class類對象中的,那么要想找到實例方法掘猿,就必須找到class類對象病游,那么此時isa的作用就來了。
[stu studentMethod];

instance對象的isa指向class對象稠通,當調(diào)用對象方法時衬衬,通過instance對象的isa找到class對象,最后找到對象方法的實現(xiàn)進行調(diào)用改橘。

(2)當類對象調(diào)用類方法的時候滋尉,同上,類方法是存儲在meta-class元類對象中的飞主。那么要找到類方法狮惜,就需要找到meta-class元類對象,而class類對象的isa指針就指向元類對象碌识。
[Student studentClassMethod];

class的isa指向meta-class
當調(diào)用類方法時碾篡,通過class的isa找到meta-class,最后找到類方法的實現(xiàn)進行調(diào)用筏餐。

isa指針指向
(3)當對象調(diào)用其父類對象方法的時候开泽,又是怎么找到父類對象方法的呢?魁瞪,此時就需要使用到class類對象superclass指針穆律。
[stu personMethod];
[stu init];

當Student的instance對象要調(diào)用Person的對象方法時,會先通過isa找到Student的class佩番,然后通過superclass找到Person的class众旗,最后找到對象方法的實現(xiàn)進行調(diào)用,同樣如果Person發(fā)現(xiàn)自己沒有響應的對象方法趟畏,又會通過Person的superclass指針找到NSObject的class對象,去尋找響應的方法滩租。

對象調(diào)用父類對象方法

(4)當類對象調(diào)用父類的類方法時赋秀,就需要先通過isa指針找到meta-class利朵,然后通過superclass去尋找響應的方法。
[Student personClassMethod];
[Student load];

當Student的class要調(diào)用Person的類方法時猎莲,會先通過isa找到Student的meta-class绍弟,然后通過superclass找到Person的meta-class,最后找到類方法的實現(xiàn)進行調(diào)用著洼。

注意:isa指針并不是直接指向?qū)ο蟮刂分嫡燎玻€需要邏輯與上一個掩碼 ISA_MASK,這個了解下就行身笤,如果不了解的話可以直接理解為isa直接指向class對象豹悬、meta-class對象。

最后附上這張經(jīng)典的isa指向圖液荸,經(jīng)過上面的分析我們在來看這張圖瞻佛,就顯得清晰明了很多。
superClass && isa指向圖
總結(jié)如下:

對isa娇钱、superclass總結(jié):

  • instance的isa指向class
  • class的isa指向meta-class
  • meta-class的isa指向基類的meta-class伤柄,基類的isa指向自己
  • class的superclass指向父類的class,如果沒有父類文搂,superclass指針為nil
  • meta-class的superclass指向父類的meta-class适刀,基類的meta-class的superclass指向基類的class
  • instance調(diào)用對象方法的軌跡,isa找到class煤蹭,方法不存在笔喉,就通過superclass找父類。
  • class調(diào)用類方法的軌跡疯兼,isa找meta-class然遏,方法不存在,就通過superclass找父類吧彪。

如何證明isa指針的指向真的如上面所說待侵?

我們通過如下代碼證明:

NSObject *object = [[NSObject alloc] init];
Class objectClass = [NSObject class];
Class objectMetaClass = object_getClass([NSObject class]);
        
NSLog(@"%p %p %p", object, objectClass, objectMetaClass);

打斷點并通過控制臺打印相應對象的isa指針:
打印object的isa指針和objectClass的地址

我們發(fā)現(xiàn)object->isa與objectClass的地址不同,這是因為從64bit開始姨裸,isa需要進行一次位運算秧倾,才能計算出真實地址。而位運算的值我們可以通過下載objc源代碼找到傀缩。

ISA_MASK

我們通過位運算進行驗證那先。
isa通過位運算計算出正確的地址

我們發(fā)現(xiàn),object-isa指針地址0x001dffff96537141經(jīng)過同0x00007ffffffffff8位運算赡艰,得出objectClass的地址0x00007fff96537140

接著我們來驗證class對象的isa指針是否同樣需要位運算計算出meta-class對象的地址售淡。

當我們以同樣的方式打印objectClass->isa指針時,發(fā)現(xiàn)無法打印:
p/x objectClass->isa

同時也發(fā)現(xiàn)左邊objectClass對象中并沒有isa指針。我們來到Class內(nèi)部看一下:

typedef struct objc_class *Class;

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

相信了解過isa指針的同學對objc_class結(jié)構(gòu)體內(nèi)的內(nèi)容很熟悉了揖闸,今天這里不深入研究揍堕,我們只看第一個對象是一個isa指針,為了拿到isa指針的地址汤纸,我們自己創(chuàng)建一個同樣的結(jié)構(gòu)體并通過強制轉(zhuǎn)化拿到isa指針衩茸。

struct xx_cc_objc_class{
    Class isa;
};

Class objectClass = [NSObject class];
struct xx_cc_objc_class *objectClass2 = (__bridge struct xx_cc_objc_class *)(objectClass);

此時我們重新驗證一下:


objectClass2->isa

確實,objectClass2的isa指針經(jīng)過位運算之后的地址是meta-class的地址贮泞。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載楞慈,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。
  • 序言:七十年代末啃擦,一起剝皮案震驚了整個濱河市囊蓝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌议惰,老刑警劉巖慎颗,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異言询,居然都是意外死亡俯萎,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進店門运杭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來夫啊,“玉大人,你說我怎么就攤上這事辆憔∑裁校” “怎么了?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵虱咧,是天一觀的道長熊榛。 經(jīng)常有香客問我,道長腕巡,這世上最難降的妖魔是什么玄坦? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮绘沉,結(jié)果婚禮上煎楣,老公的妹妹穿的比我還像新娘。我一直安慰自己车伞,他們只是感情好择懂,可當我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著另玖,像睡著了一般困曙。 火紅的嫁衣襯著肌膚如雪表伦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天赂弓,我揣著相機與錄音绑榴,去河邊找鬼哪轿。 笑死盈魁,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的窃诉。 我是一名探鬼主播杨耙,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼飘痛!你這毒婦竟也來了珊膜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤宣脉,失蹤者是張志新(化名)和其女友劉穎车柠,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體塑猖,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡竹祷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了羊苟。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片塑陵。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蜡励,靈堂內(nèi)的尸體忽然破棺而出令花,到底是詐尸還是另有隱情,我是刑警寧澤凉倚,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布兼都,位于F島的核電站,受9級特大地震影響稽寒,放射性物質(zhì)發(fā)生泄漏扮碧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一瓦胎、第九天 我趴在偏房一處隱蔽的房頂上張望芬萍。 院中可真熱鬧,春花似錦搔啊、人聲如沸柬祠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽漫蛔。三九已至嗜愈,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間莽龟,已是汗流浹背蠕嫁。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留毯盈,地道東北人剃毒。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像搂赋,于是被迫代替她去往敵國和親赘阀。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,652評論 2 354