iOS探索 類的結構分析

歡迎閱讀iOS探索系列(按序閱讀食用效果更加)html

寫在前面

iOS探索系列前面幾篇講到了對象的初始化、對象的內存分佈、對象的isa初始化以及指向分析,本文就來說講實例出實例對象的類對象——類c++

1、類的本質

1.類的本質

objc源碼下準備代碼面試

#import <objc/runtime.h>

@interface FXPerson : NSObject
@end
@implementation FXPerson
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        FXPerson *p = [FXPerson alloc];
        Class cls = object_getClass(p);
    }
    return 0;
}
複製代碼

利用clang將OC文件輸出cpp文件(彷彿打開了新世界的大門) 緩存

發現類在底層用 Class接收

而後開始大海撈針(開天眼模式)找到了Class的定義bash

typedef struct objc_class *Class;
複製代碼

想要找到objc_class就搜不到了,可是總以爲它似曾類似,或許能在objc源碼找到靈感app

在源碼中搜索代碼的經驗 "objc_class :"、 "objc_class {"less

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    
    class_rw_t *data() { 
        return bits.data();
    }
    ...
}
複製代碼

objc_class繼承於objc_objectide

struct objc_object {
private:
    isa_t isa;
    
public:
    ...
}
複製代碼

結論:類的本質是objc_class類型的結構體,objc_class繼承於objc_object,因此知足萬物皆對象post

之後不要再在面試的時候回答一句 萬物皆對象 就完事了學習

2.objc_object和NSObject的關係

等等,爲何繼承objc_object就知足萬物皆對象了???

看過NSObject的定義就知道了

OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
OBJC_ROOT_CLASS
OBJC_EXPORT
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
複製代碼

仔細比較的話就能看出NSObjectobjc_object有着說不清道不明的關係

  • 其實NSObjectobjc_object的仿寫,和objc_object的定義是同樣的,在底層會編譯成objc_object
  • 同理NSObject類是OC版本的objc_class

3.Class isa??

isa明明是isa_t類型的,爲何註釋了一句Class ISA

  • 萬物皆對象,用繼承於objc_object的Class接收是沒問題的
  • 強轉,方便isa走位時返回類的類型

2、類的結構

objc_class的定義能夠得出,類有4個屬性:isa、superclass、cache、bits

1.Class ISA

不但實例對象中有isa指針類對象中也有isa指針關聯着元類

Class自己就是一個指針,佔用8字節

2.Class superclass

顧名思義就是類的父類(通常爲NSObject)superclass是Class類型,因此佔用8字節

3.cache_t cache

雖然對這個屬性比較陌生(詳見後文),可是cache在英文中的意思是緩存

cache_t是一個結構體,內存長度有全部元素決定:_buckets是一個指針,佔用8字節;mask_t是個int類型,_mask佔用4字節;_occupied佔用4字節

=>cache_t佔用16字節

typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits

struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;

public:
    ...
};
複製代碼

4.class_data_bits_t bits

又是一個陌生的屬性,可是蘋果工程師仍是蠻友好的,這一看就是存數據的地方

struct class_data_bits_t {

    // Values are the FAST_ flags above.
    uintptr_t bits;
private:
    bool getBit(uintptr_t bit) {
        return bits & bit;
    }
    ...
public:

    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    ...
};
複製代碼

那麼問題來了,類的屬性方法都去哪兒了?是在cache仍是在bits? 其實前文中有提到一丟丟——objc_class中有個class_rw_t *data()方法

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    ...
}
複製代碼

3、類的屬性方法

爲FxPerson添加nickname成員屬性、nationality屬性變量、walk類方法、fly實例方法

@interface FXPerson : NSObject {
    NSString *nickname;
}
@property (nonatomic, copy) NSString *nationality;
+ (void)walk;
- (void)fly;
@end

@implementation FXPerson
+ (void)walk {}
- (void)fly {}
@end
複製代碼

1.類的屬性

x/4gx cls打印當前類結構

bits恰好是類的內存首地址+isa、superclass、cache的內存長度

=> 0x100001560+32字節 = 0x100001580

po打印不出來,那就類型強轉打印輸出bits的內存地址

根據class_rw_t *data() { return bits.data(); }打印bits.data()

再往裏面探索進去(這比西天取經還難)

天吶!怎麼只有一個nationality了?難道我FXPerson類只配擁有「國籍」不配擁有「姓名」?讓我靜靜... 既然此路不通,那麼換條路走走(心裏很不情願)

在一頓猛如虎的操做(開天眼)以後,發現了class_rw_t有個屬性class_ro_t

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};
複製代碼

清空控制檯輸出ro,跟class_ro_t的結構類型一摸同樣

打印robaseProperties

天吶?這個成員變量nickname是迷路了嗎?等等!多是本身迷路了!

嘗試了一下打印ivars找到了nickname,可是爲何count = 2呢?

ivar_list_t繼承entsize_list_tt,然後者又有個get(uint32_t i)方法

struct ivar_list_t : entsize_list_tt<ivar_t, ivar_list_t, 0> {
    bool containsIvar(Ivar ivar) const {
        return (ivar >= (Ivar)&*begin()  &&  ivar < (Ivar)&*end());
    }
};

struct entsize_list_tt {
    ...
    Element& get(uint32_t i) const { 
        assert(i < count);
        return getOrEnd(i);
    }
    ...
}
複製代碼

如願拿到了nicknamenationality,可是這個nationality長得有點不同

仔細想一想這不就是編譯器會在底層自動將屬性變量生成一個成員變量_nationality(_前綴+屬性變量)

2.類的方法

打印robaseMethodList

系統在底層添加了一個c++的.cxx_destruct方法,同時編譯器還在底層幫屬性變量生成了一個setter方法getter方法

可是FXPerson的walk類方法又被吃了...

無奈之下只能開大招(開天眼)在元類的datarobaseMethodList找到了

其實也很好理解:類方法能夠理解成元類對象實例方法,所以存在元類

3.結論

  • 成員變量存放在ivar
  • 屬性存放在property,同時也會存一份在ivar,並生成settergetter方法
  • 對象方法存放在裏面
  • 類方法存放在元類裏面

4.API驗證

利用底層開放的API能夠驗證以上結論

void testObjc_copyIvar_copyProperies(Class cls) {
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList(cls, &count);
    for (unsigned int i=0; i < count; i++) {
        Ivar const ivar = ivars[i];
        //獲取實例變量名
        const char*cName = ivar_getName(ivar);
        NSString *ivarName = [NSString stringWithUTF8String:cName];
        NSLog(@"class_copyIvarList:%@",ivarName);
    }
    free(ivars);

    unsigned int pCount = 0;
    objc_property_t *properties = class_copyPropertyList(cls, &pCount);
    for (unsigned int i=0; i < pCount; i++) {
        objc_property_t const property = properties[i];
        //獲取屬性名
        NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
        //獲取屬性值
        NSLog(@"class_copyProperiesList:%@",propertyName);
    }
    free(properties);
}

void testObjc_copyMethodList(Class cls) {
    unsigned int count = 0;
    Method *methods = class_copyMethodList(cls, &count);
    for (unsigned int i=0; i < count; i++) {
        Method const method = methods[i];
        //獲取方法名
        NSString *key = NSStringFromSelector(method_getName(method));
        
        NSLog(@"Method, name: %@", key);
    }
    free(methods);
}

void testInstanceMethod_classToMetaclass(Class cls) {
    const char *className = class_getName(cls);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(cls, @selector(walk));
    Method method2 = class_getInstanceMethod(metaClass, @selector(fly));

    Method method3 = class_getInstanceMethod(cls, @selector(walk));
    Method method4 = class_getInstanceMethod(metaClass, @selector(fly));
    
    NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
    NSLog(@"%s",__func__);
}

void testClassMethod_classToMetaclass(Class cls) {
    const char *className = class_getName(cls);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(cls, @selector(walk));
    Method method2 = class_getClassMethod(metaClass, @selector(fly));

    Method method3 = class_getClassMethod(cls, @selector(walk));
    Method method4 = class_getClassMethod(metaClass, @selector(fly));
    
    NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
    NSLog(@"%s",__func__);
}

void testIMP_classToMetaclass(Class cls) {
    const char *className = class_getName(cls);
    Class metaClass = objc_getMetaClass(className);

    IMP imp1 = class_getMethodImplementation(cls, @selector(walk));
    IMP imp2 = class_getMethodImplementation(metaClass, @selector(fly));

    IMP imp3 = class_getMethodImplementation(cls, @selector(walk));
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(fly));

    NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
    NSLog(@"%s",__func__);
}
複製代碼

5.clang編譯驗證

其實直接用clang編譯也能看出蹊蹺

關於v@:這個玩意,在蘋果開發者文檔上也有介紹

4、寫在後面

雖然一直碰壁,可是本文最後仍是找到了想要的答案,有的時候你離成功只差一步之遙,也有的時候是你選錯了研究方向。學習是如此,人生亦是如此,道阻且長,且行且珍惜。幸運的是有前人替咱們踩坑,咱們能夠吸收前人的教訓少走彎路

相關文章
相關標籤/搜索