iOS底層系列10 -- 類的結(jié)構(gòu)分析

元類

  • 實例對象是由類生產(chǎn)出來的凌彬,例如YYPerson這是一個類,通過YYPerson類我們可以調(diào)用其alloc方法循衰,創(chuàng)建出一個實例對象person铲敛,YYPerson *person = [[YYPerson alloc]init],可以說類就像一個工廠一樣会钝, 工廠可以生產(chǎn)出成千上萬的產(chǎn)品伐蒋,這些產(chǎn)品的屬性和行為都是一樣,這些產(chǎn)品在編程的世界里就是所謂的實例對象迁酸,可以這么說生產(chǎn)實例對象的工廠我們稱之為類先鱼,實例對象的isa指針指向生產(chǎn)實例對象的類
  • 正所謂在面向?qū)ο蟮木幊淌澜缋锛轺蓿f物皆對象焙畔,所以生產(chǎn)實例對象的類,其本質(zhì)也是一個對象全蝶,那么生產(chǎn)類對象的工廠我們稱之為元類闹蒜,類對象的isa指針指向生產(chǎn)類對象的類寺枉,也就是所謂的元類
  • 元類是系統(tǒng)生成的绷落,其定義和創(chuàng)建都是由編譯器完成姥闪;
  • 元類 是描述 類對象 的類,每個類都有一個獨一無二的元類用來 存儲類方法的相關(guān)信息砌烁;
  • 元類本身是沒有名稱的筐喳,由于與類相關(guān)聯(lián),所以使用了和類名一樣的名稱函喉;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        //實例對象
        NSObject *obj1 = [[NSObject alloc]init];
        NSObject *obj2 = [[NSObject alloc]init];
        NSLog(@"實例對象 -- %p -- %p",obj1,obj2);
        
        //類對象
        Class objClass1 = [obj1 class];
        Class objClass2 = [obj2 class];
        Class objClass3 = object_getClass(obj1);
        Class objClass4 = object_getClass(obj2);
        Class objClass5 = [NSObject class];
        NSLog(@"類對象 -- %p -- %p -- %p -- %p -- %p",objClass1,objClass2,objClass3,objClass4,objClass5);
        
        //元類對象
        Class metaClass = object_getClass(objClass1);
        NSLog(@"元類對象 -- %p",metaClass);

        //判斷類 是否是元類對象
        bool isMetaClass = class_isMetaClass(metaClass);
        NSLog(@"isMetaClass = %d",isMetaClass);
    }
    return 0;
}
  • 調(diào)試結(jié)果如下:
Snip20210624_11.png
  • 可以看出避归,不同的實例對象占據(jù)不同的內(nèi)存空間類對象在內(nèi)存中只占用一份內(nèi)存空間管呵,元類對象在內(nèi)存中也只占用一份內(nèi)存空間梳毙;
  • object_getClass()函數(shù),參數(shù)傳入實例對象捐下,返回的是類對象账锹;
  • object_getClass()函數(shù),參數(shù)傳入類對象坷襟,返回的是元類對象奸柬;
  • class_isMetaClass()函數(shù),判斷類 是否是元類對象婴程;
  • 下面我們通過代碼來驗證一下上面所闡述的內(nèi)容:
Snip20210210_137.png
  • YYPerson繼承自NSObject廓奕;
  • YYStudent繼承自YYPerson;
  • 當代碼執(zhí)行到斷點處停下档叔,進行LLDB命令調(diào)試桌粉,結(jié)果如下所示:
Snip20210210_138.png
  • p/x person 讀取實例對象person在內(nèi)存中首地址;
  • p/x 0x001d800100002375 & 0x00007ffffffffff8ULL 將實例對象person的isa的值與isa的掩碼0x00007ffffffffff8ULL做位與運算得到Y(jié)YPerson類的地址值0x0000000100002370蹲蒲,其本質(zhì)就是YYPerson類對象番甩;
  • x/4gx 0x0000000100002370 獲取YYPerson類對象在內(nèi)存中信息數(shù)據(jù);其中0x0000000100002348就是YYPerson類對象的isa指針的值届搁;
  • p/x 0x0000000100002348 & 0x00007ffffffffff8ULL 將YYPerson類對象的isa與isa的掩碼0x00007ffffffffff8ULL做位與運算得到Y(jié)YPerson元類的地址值,即0x0000000100002348窍育,其本質(zhì)還是YYPerson卡睦,這就證實了上面所闡述的元類本身是沒有名稱的,由于與類相關(guān)聯(lián)漱抓,所以使用了同類名一樣的名稱表锻;

如何在LLDB調(diào)試控制臺獲取實例對象person的類對象YYPerson的內(nèi)存地址?

  • 第一種方式:p/x [person class] 以16進制打印YYPerson類對象的內(nèi)存地址乞娄;
  • 第二種方式:p/x [YYPerson class] 以16進制打印YYPerson類對象的內(nèi)存地址瞬逊;
  • 第三種方式:p/x object_getClass(person) 以16進制打印YYPerson類對象的內(nèi)存地址显歧;
  • 第四種方式:x/4gx person 首先讀取實例對象person在內(nèi)存中數(shù)據(jù),前8個字節(jié)是isa指針的值确镊,然后 p/x isa的值 & isa掩碼值士骤,得到的就是YYPerson類對象的內(nèi)存地址;
  • p/x aaa 獲取的是aaa的內(nèi)存地址蕾域;
  • x/4gx aaa 獲取的是aaa內(nèi)存地址中的數(shù)據(jù)內(nèi)容拷肌;

isa指針的指向

由上面的內(nèi)容我們知道實例對象的isa指向類對象類對象的isa指向元類對象旨巷,那元類對象的isa指向哪里巨缘?會這樣沒有終點的一直指向下去么?

Snip20210210_140.png
  • 通過上面的LLDB調(diào)試分析可以得出下面的結(jié)論:
    • 實例對象 的 isa 指向 類對象采呐;
    • 類對象 的 isa 指向 其元類對象若锁;
    • 元類對象 的 isa 指向 根元類,即NSObject斧吐;
    • 根元類 的 isa 指向 它自己本身又固;

通過類Class所創(chuàng)建的實例對象在內(nèi)存中可以成千上萬,那么類對象在內(nèi)存中占幾份会通?

#import <Foundation/Foundation.h>
#import "YYPerson.h"
#import "YYStudent.h"
#import <objc/runtime.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        YYPerson *person = [[YYPerson alloc]init];
        person.name = @"liyanyan";
        
        Class class1 = [YYPerson class];
        Class class2 = [YYPerson alloc].class;
        Class class3 = object_getClass(person);
        NSLog(@"\nclass1 = %p\nclass2 = %p\nclass3 = %p", class1, class2, class3);
    }
    return 0;
}
  • 控制臺的打印結(jié)果如下:
Snip20210210_142.png
  • 可以看出類對象在內(nèi)存中只占有一份.

引用官方一張 關(guān)于 isa走向 與 類繼承關(guān)系圖

Snip20210210_143.png
isa走向(虛線部分)
  • 實例對象(Instance)的isa指向類對象class口予;
  • 類對象的isa指向元類對象meta class;
  • 元類對象的isa指向根元類 root meta class(NSObject)涕侈;
  • 根元類對象的isa指向自己本身(NSObject)沪停;
類的繼承superClass的走向(實線部分)
  • 類對象之間的繼承關(guān)系:
    • 子類的SuperClass指向其父類Superclass;
    • 父類的SuperClass指向根類RootClass裳涛,這里的根類就是NSOject木张;
    • 根類的SuperClass指向nil,可以理解成無中生有端三;
  • 元類對象之間也存在繼承關(guān)系:
    • 子類的元類的SuperClass指向父類的元類Superclass(meta)舷礼;
    • 父類的元類的SuperClass指向根元類RootClass(metal);
    • 根元類的SuperClass指向根類RootClass 也就是NSObject郊闯;
    • 根類的SuperClass指向nil妻献,可以理解成無中生有;
  • 實例對象之間沒有繼承關(guān)系团赁,類與元類之間才有繼承關(guān)系育拨;
  • 通過例子來實際闡述上面的關(guān)系圖,YYStudent與YYPerson:
Snip20210213_5.png
  • isa的走向鏈:
    • student的走向鏈:student子類實例對象 --> YYStudent子類 --> YYStudent子類的元類 --> NSObject根元類 --> NSObject根元類自身
    • Person的走向鏈:person子類實例對象 --> YYPerson子類 --> YYPerson子類的元類 --> NSObject根元類 --> NSObject根元類自身
  • 類的繼承關(guān)系鏈:
    • YYStudent子類 --> YYPerson父類 --> NSObject根類 --> nil
    • YYStudent子類的元類 --> YYPerson父類的元類 --> NSObject根元類 --> NSObject根類 --> nil
  • isa指針與superClass指針在方法調(diào)用中欢摄,起到至關(guān)重要的作用熬丧;

類的結(jié)構(gòu)

  • iOS底層系列02-- objc4-781源碼中的objc_class與objc_object中我們知道objc_class與objc_object這兩個結(jié)構(gòu)體且objc_class繼承自objc_object;
  • Class類是以objc_class為模版進行創(chuàng)建的怀挠;
  • OC任意對象id是以objc_object為模版進行創(chuàng)建的析蝴;
  • objc_class結(jié)構(gòu)體定義(新的)如下所示:
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() const {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }

    void setInfo(uint32_t set) {
        ASSERT(isFuture()  ||  isRealized());
        data()->setFlags(set);
    }

    ......
}
  • isa指針:繼承自objc_object害捕,占8個字節(jié);
  • superclass指針:屬于Class類型闷畸,是一個指針尝盼,占8個字節(jié);
  • cache成員:是一個cache_t結(jié)構(gòu)體腾啥,其內(nèi)存大小需要根據(jù)其內(nèi)部的成員來確定东涡,詳細計算見下面;
  • bits成員:是一個class_data_bits_t結(jié)構(gòu)體倘待,將Class的首地址進行偏移疮跑,偏移量為面3個成員的內(nèi)存大小總和,才能獲取到bits成員的首地址凸舵,bits成員存儲了類的相關(guān)信息數(shù)據(jù)祖娘;
計算cache成員的內(nèi)存大小
  • 剔除不會占用類空間的const、void啊奄、static和函數(shù)渐苏,結(jié)構(gòu)體如下所示:
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic<struct bucket_t *> _buckets;
    explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;
    
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;
#else
#error Unknown cache mask storage type.
#endif
    
#if __LP64__
    uint16_t _flags;
#endif
    uint16_t _occupied;
};
  • _buckets是結(jié)構(gòu)體指針類型占8個字節(jié);
  • mask_t是unsigned int 占4個字節(jié)菇夸;
  • uintptr_t是unsigned long 占8個字節(jié)琼富;
  • uint16_t是unsigned short 占2個字節(jié);
  • 所以cache_t結(jié)構(gòu)體不論哪種CACHE_MASK_STORAGE庄新,其內(nèi)存大小都會占8+4+2+2 = 16個字節(jié)鞠眉;
探索bits成員
  • 根據(jù)上面關(guān)于cache內(nèi)存大小的計算結(jié)果,然后isa指針與superclass指針分別占8個字節(jié)择诈,再根據(jù)內(nèi)存偏移械蹋,我們需要將class類的首地址進行32字節(jié)的偏移,方可得到bits成員的首地址羞芍;
  • YYPerson.h文件內(nèi)容:
@interface YYPerson : NSObject

@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)NSInteger weight;

- (void)walk;
- (void)eat;

+ (void)sing;

@end
  • LLDB調(diào)試結(jié)果如下所示:
Snip20210219_2.png
  • p/x YYPerson.class 獲取YYPerson類的首地址
  • YYPerson類的首地址為0x100002360哗戈,那么內(nèi)存偏移32個字節(jié)即為0x100002380,是bits成員的首地址荷科,注意是16進制的換算
  • p (class_data_bits_t *) 0x100002360 地址 強轉(zhuǎn)為class_data_bits_t類型
  • p $1->data()唯咬,bits調(diào)用函數(shù)data(),獲取class_rw_t結(jié)構(gòu)體
進入class_rw_t結(jié)構(gòu)體
  • 在class_rw_t結(jié)構(gòu)體定義中看到三個函數(shù)分別是獲取方法列表畏浆,屬性列表協(xié)議列表的副渴;
  • method_array_tproperty_array_t全度,protocol_array_t均是一個二維數(shù)組;
  • method_array_t中存儲的是method_list_t一維數(shù)組斥滤,method_list_t中存儲的是method_t
    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
        }
    }

    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>()->baseProperties};
        }
    }

    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
        }
    }
  • LLDB調(diào)試屬性列表properties()結(jié)果如下:
Snip20210219_3.png
  • p $3.properties() 調(diào)用class_rw_t結(jié)構(gòu)體中的properties()将鸵,獲取屬性列表數(shù)組property_list_t勉盅;
    • 可通過p $6.get(0) 獲取屬性name;
    • 可通過p $6.get(1) 獲取屬性weight顶掉;
  • LLDB調(diào)試實例方法列表methods()結(jié)果如下:
Snip20210219_4.png
  • p $3.methods() 調(diào)用class_rw_t結(jié)構(gòu)體中的methods()草娜,獲取方法列表數(shù)組method_list_t,注意此方法列表是實例方法列表痒筒;
  • 可通過p $12.get(i)獲取對應的方法宰闰;
Snip20210219_5.png
  • LLDB調(diào)試實例變量的存儲:
  • 首先在class_rw_t結(jié)構(gòu)體中存在下面這么一個函數(shù)ro(),其返回值為class_ro_t結(jié)構(gòu)體簿透;
    const class_ro_t * ro() const {
        auto v = get_ro_or_rwe();
        if (slowpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>()->ro;
        }
        return v.get<const class_ro_t *>();
    }
  • class_ro_t結(jié)構(gòu)體定義如下:
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;

    _objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];

    _objc_swiftMetadataInitializer swiftMetadataInitializer() const {
        if (flags & RO_HAS_SWIFT_INITIALIZER) {
            return _swiftMetadataInitializer_NEVER_USE[0];
        } else {
            return nil;
        }
    }

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
    ......
};
  • 有一個ivars成員移袍,此成員是用來存儲實例變量的;
  • ro中的方法列表老充,屬性列表與成員變量列表均是一維數(shù)組葡盗;
Snip20210219_8.png
Snip20210219_9.png
Snip20210219_10.png
  • p $5.ro() 獲取class_ro_t結(jié)構(gòu)體;
  • p $7.ivars 獲取實例變量列表啡浊;
  • p $9.get(0) 獲取實例變量 _name;
  • p $9.get(1) 獲取實例變量 _weight;
  • 總結(jié):
    • 類的屬性列表存儲在類的bits屬性中觅够,可通過bits --> data() --> properties()獲取屬性列表;
    • 類的實例變量列表存儲在類的bits屬性中巷嚣,可通過bits --> data() -->ro() --> ivars獲取實例變量列表喘先;
    • 類的實例方法列表存儲在類的bits屬性中,可通過bits --> data() --> methods() 獲取實例方法列表廷粒;
探索類方法的存儲位置
  • 類的bits成員窘拯,可通過data() --> methods()獲取的是類的實例方法,并沒有看到類方法评雌,猜測類方法應該存儲在元類的bits成員中树枫,下面通過LLDB來驗證一下:
Snip20210219_11.png
Snip20210219_12.png
  • p/x YYPerson.class 獲取YYPerson類的首地址;
  • x/4gx 0x00000001000023f0 讀取YYPerson類 前32個字節(jié)的內(nèi)存數(shù)據(jù)景东,其中0x00000001000023c8是isa的值砂轻,然后其與isa mask做位與運算得到元類的首地址0x00000001000023c8;
  • 元類首地址偏移32個字節(jié)斤吐,得到元類的bits成員搔涝,
  • 接下來的獲取類方法的步驟與獲取類的實例方法步驟相似;
  • 總結(jié):
    • 類的實例方法是存儲在類的bits成員中,類 --> bits --> data() --> methods() ;
    • 類的類方法是存儲在元類的bits成員中,元類 --> bits --> data() --> methods() ;

通過MJClassInfo.h查看類對象的數(shù)據(jù)結(jié)構(gòu)

  • 上面是通過LLDB控制臺調(diào)試分析類對象的數(shù)據(jù)結(jié)構(gòu)和措,下面再提供一種更直觀的方式庄呈,查看類對象的數(shù)據(jù)結(jié)構(gòu),引用MJ大神寫的MJClassInfo.h文件派阱,代碼實現(xiàn)如下:
#import <Foundation/Foundation.h>

#ifndef MJClassInfo_h
#define MJClassInfo_h

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# endif

#if __LP64__
typedef uint32_t mask_t;
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;

struct bucket_t {
    cache_key_t _key;
    IMP _imp;
};

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

struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
};

struct method_t {
    SEL name;
    const char *types;
    IMP imp;
};

struct method_list_t : entsize_list_tt {
    method_t first;
};

struct ivar_t {
    int32_t *offset;
    const char *name;
    const char *type;
    uint32_t alignment_raw;
    uint32_t size;
};

struct ivar_list_t : entsize_list_tt {
    ivar_t first;
};

struct property_t {
    const char *name;
    const char *attributes;
};

struct property_list_t : entsize_list_tt {
    property_t first;
};

struct chained_property_list {
    chained_property_list *next;
    uint32_t count;
    property_t list[0];
};

typedef uintptr_t protocol_ref_t;
struct protocol_list_t {
    uintptr_t count;
    protocol_ref_t list[0];
};

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;  // instance對象占用的內(nèi)存空間
#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;
};

struct class_rw_t {
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_list_t * methods;    // 方法列表
    property_list_t *properties;    // 屬性列表
    const protocol_list_t * protocols;  // 協(xié)議列表
    Class firstSubclass;
    Class nextSiblingClass;
    char *demangledName;
};

#define FAST_DATA_MASK          0x00007ffffffffff8UL
struct class_data_bits_t {
    uintptr_t bits;
public:
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
};

/* OC對象 */
struct mj_objc_object {
    void *isa;
};

/* 類對象 */
struct mj_objc_class : mj_objc_object {
    Class superclass;
    cache_t cache;
    class_data_bits_t bits;
public:
    class_rw_t* data() {
        return bits.data();
    }
    
    mj_objc_class* metaClass() {
        return (mj_objc_class *)((long long)isa & ISA_MASK);
    }
};

#endif /* MJClassInfo_h */
  • 工程測試代碼如下:
#import <Foundation/Foundation.h>
#import "YYPerson.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>
#import "MJClassInfo.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        mj_objc_class *personClass = (__bridge mj_objc_class *)([YYPerson class]);
        class_rw_t *personClassData = personClass->data();
        class_rw_t *personMetaClassData = personClass->metaClass()->data();
        
    }
    return 0;
}
  • 調(diào)試類對象YYPerson的結(jié)果如下:
Snip20210625_14.png
  • 可以看到實例變量與方法都是存儲在class_ro_t中诬留,class_rw_t不存儲實例變量與方法,只提供訪問實例變量與方法的函數(shù),具體見class_rw_t的結(jié)構(gòu)體定義文兑;
  • 調(diào)試元類對象的結(jié)構(gòu)如下:

常見API

  • 首先準備測試代碼YYPerson.h文件:
@interface YYPerson : NSObject

@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)NSInteger weight;

- (void)walk;
- (void)eat;

+ (void)sing;

@end
class_getInstanceMethod(Class cls, SEL sel)
  • class_getInstanceMethod(Class cls, SEL sel) 獲取類的實例方法盒刚,如果在傳入的類或者類的父類中沒有找到指定的實例方法,則返回NULL绿贞;
void YYClass_getInstanceMethod(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(pClass, @selector(walk));
    Method method2 = class_getInstanceMethod(metaClass, @selector(walk));

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        YYPerson *person = [[YYPerson alloc]init];
        person.name = @"liyanyan";
        person.weight = 130;

        Class cls = object_getClass(person);
        YYClass_getInstanceMethod(cls);
    }
    return 0;
}
  • 測試代碼的結(jié)果分析:
    1> method1 --> 0x100003270 有值 YYPerson類中有實例方法walk因块;
    2> method2 --> 0x0 無值 YYPerson元類中沒有實例方法walk,其查找順序為: YYPerson元類 --> 根元類 --> 根類 --> nil籍铁,在元類的繼承鏈上查找涡上;
    3> method3 --> 0x0 無值 YYPerson類中沒有實例方法sing,其查找順序為YYPerson類 --> 根類 --> nil拒名,在類的繼承鏈上查找吩愧;
    4> method4 --> 0x100003208 有值 YYPerson元類中有實例方法sing;
    5> 元類中查找實例方法就是查找類方法
class_getClassMethod(Class cls, SEL sel)
  • class_getClassMethod(Class cls, SEL sel) 獲取類的類方法(元類的實例方法)靡狞,如果在傳入的類或者類的父類中沒有找到指定的類方法耻警,則返回NULL
    其底層實現(xiàn)為:
Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}
Class getMeta() {
    if (isMetaClass()) return (Class)this;
    else return this->ISA();
}
  • cls->getMeta() 獲取元類 表明類方法的查找是在元類中甸怕;
  • 若傳進來的class為元類甘穿,就直接返回元類;若傳進來的class為非元類梢杭,則會返回class的isa温兼,即class的元類
  • 測試代碼:
void YYclass_getClassMethod(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(walk));
    Method method2 = class_getClassMethod(metaClass, @selector(walk));
    Method method3 = class_getClassMethod(pClass, @selector(sing));
    Method method4 = class_getClassMethod(metaClass, @selector(sing));
    
    NSLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        YYPerson *person = [[YYPerson alloc]init];
        person.name = @"liyanyan";
        person.weight = 130;

        Class cls = object_getClass(person);
        YYclass_getClassMethod(cls);
        
    }
    return 0;
}
  • 測試代碼的結(jié)果分析:
    1> method1 --> 0x0 無值 傳參YYPerson為非元類武契,則獲取YYPerson的isa即YYPerson的元類募判,元類中沒有walk方法,且在元類的繼承鏈上查找咒唆,都沒有找到届垫;
    2> method2 --> 0x0 無值 傳參YYPerson為元類,而元類中沒有walk方法全释,且在元類的繼承鏈上查找装处,都沒有找到
    3> method3 --> 0x1000031e0 有值 傳參YYPerson為非元類浸船,則獲取YYPerson的isa即YYPerson的元類妄迁,元類中有sing方法;
    4> method4 --> 0x1000031e0 有值 傳參YYPerson為元類李命,元類中有sing方法登淘;
class_getMethodImplementation(Class cls, SEL sel)
  • class_getMethodImplementation(Class cls, SEL sel) 獲取類中某個方法的是實現(xiàn);
    其底層實現(xiàn)為:
IMP class_getMethodImplementation(Class cls, SEL sel)
{
    IMP imp;

    if (!cls  ||  !sel) return nil;

    imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);

    // Translate forwarding function to C-callable external version
    if (!imp) {
        return _objc_msgForward;
    }

    return imp;
}
  • 若方法實現(xiàn)imp不存在封字,會進入消息的轉(zhuǎn)發(fā)黔州,也會返回一個函數(shù)指針耍鬓;
  • 測試代碼如下:
void YYClass_getMethodImplementation(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);

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

    IMP imp3 = class_getMethodImplementation(pClass, @selector(sing));
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(sing));

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        YYPerson *person = [[YYPerson alloc]init];
        person.name = @"liyanyan";
        person.weight = 130;

        Class cls = object_getClass(person);
        YYClass_getMethodImplementation(cls);
    }
    return 0;
}
  • 測試代碼的結(jié)果分析:
    1> imp1 --> 0x100001c20 有值 YYPerson類有walk實例方法實現(xiàn),則返回walk實現(xiàn)的函數(shù)指針辩撑;
    2> imp2 --> 0x10037eac0 有值 YYPerson元類中沒有walk實例方法(元類的實例方法也就是類方法)界斜,且在元類的繼承鏈上查找,都沒有找到合冀;進入消息轉(zhuǎn)發(fā),返回消息轉(zhuǎn)發(fā)的函數(shù)指針项贺;
    3> imp3 --> 0x10037eac0 有值 YYPerson類沒有sing實例方法君躺;在類的繼承鏈上查找,都沒有找到开缎,進入消息轉(zhuǎn)發(fā)棕叫,返回消息轉(zhuǎn)發(fā)的函數(shù)指針;
    4> imp4 --> 0x100001bb0 有值 YYPerson元類中有sing實例方法(即類方法)奕删,則返回sing實現(xiàn)的函數(shù)指針俺泣;
- (BOOL)isKindOfClass:(Class)cls與+ (BOOL)isKindOfClass:(Class)cls
  • - (BOOL)isKindOfClass:(Class)cls 判斷實例對象是否屬于指定參數(shù)類,會在實例對象的類的繼承鏈上判斷完残,其底層實現(xiàn)為:
- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
  • 可以看到首先獲取對象的類與傳參類進行比較伏钠,如果相等直接返回YES,如果不相等會在類的繼承鏈上 父類 --> 根類 --> nil 循環(huán)與傳參類進行比較谨设;

  • + (BOOL)isKindOfClass:(Class)cls 判斷類是否屬于指定參數(shù)類熟掂,會在類的元類的繼承鏈上判斷;
    其底層實現(xiàn)為:

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
  • 可以看到首先獲取類的元類與傳參類進行比較扎拣,如果相等直接返回YES赴肚,如果不相等會在元類的繼承鏈上 父類的元類 --> 根元類 --> 根類 --> nil 循環(huán)與傳參類進行比較

  • 測試代碼如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        YYPerson *person = [[YYPerson alloc]init];
        person.name = @"liyanyan";
        person.weight = 130;
        
        BOOL re1 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
        BOOL re2 = [(id)[YYPerson alloc] isKindOfClass:[YYPerson class]];
        BOOL re3 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
        BOOL re4 = [(id)[YYPerson class] isKindOfClass:[YYPerson class]];
    }
    return 0;
}
  • 發(fā)現(xiàn)一個問題isKindOfClass函數(shù)不論實例方法還是類方法都不會走上面的底層實現(xiàn)二蓝,分析其匯編代碼如下:
Snip20210220_16.png
  • isKindOfClass函數(shù)其實例方法與類方法誉券,底層調(diào)用的都是objc_opt_isKindOfClass函數(shù),其代碼實現(xiàn)為:
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
    if (slowpath(!obj)) return NO;
    Class cls = obj->getIsa();
    if (fastpath(!cls->hasCustomCore())) {
        for (Class tcls = cls; tcls; tcls = tcls->superclass) {
            if (tcls == otherClass) return YES;
        }
        return NO;
    }
#endif
    return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
  • 若obj是實例對象刊愚,obj->getIsa()獲取的是類踊跟,若與傳參類一直不等,會在類的繼承鏈上依次進行比較百拓;
  • 若obj是類琴锭,obj->getIsa()獲取的是元類,若與傳參類一直不等衙传,會在元類的繼承鏈上依次進行比較决帖;
  • 底層這么調(diào)用,主要是因為在llvm中編譯時對其進行了優(yōu)化處理蓖捶。
  • 上面測試代碼的結(jié)果分析:
  • re1為YES地回,[NSObject alloc]實例對象的類為NSObject(根類)與傳參類NSObject(根類)相等;
  • re2為YES,[YYPerson alloc]實例對象的類為YYPerson與傳參類YYPerson相等刻像;
  • re3為YES畅买,[NSObject class]類的元類(根元類)與傳參類NSObject(根類)不相等,然后在元類的繼承鏈上细睡,父類的元類 --> 根元類 --> 根類 --> nil谷羞,依次比較,根元類的父類是根類NSObject與傳參類NSObject(根類)相等溜徙;
  • re4為NO湃缎,[YYPerson class]類的元類與傳參類YYPerson(類)不相等,然后在元類的繼承鏈上蠢壹,父類的元類 --> 根元類 --> 根類 --> nil嗓违,依次比較,都不相等图贸;
- (BOOL)isMemberOfClass:(Class)cls與+ (BOOL)isMemberOfClass:(Class)cls
  • - (BOOL)isMemberOfClass:(Class)cls 判斷實例對象是否屬于指定參數(shù)類蹂季,不涉及類的繼承鏈,其底層實現(xiàn)為:
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}
  • 可以看到僅僅只獲取實例對象的類與傳參類進行比較疏日;
  • + (BOOL)isMemberOfClass:(Class)cls判斷類是否屬于指定參數(shù)類偿洁,不涉及元類的繼承鏈;
    其底層實現(xiàn)為:
+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}
  • 可以看到僅僅只獲取類的元類與傳參類進行比較制恍;
  • 測試代碼如下:
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        YYPerson *person = [[YYPerson alloc]init];
        person.name = @"liyanyan";
        person.weight = 130;
        
        BOOL re5 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
        BOOL re6 = [(id)[YYPerson alloc] isMemberOfClass:[YYPerson class]];
        BOOL re7 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
        BOOL re8 = [(id)[YYPerson class] isMemberOfClass:[YYPerson class]];
    }
    return 0;
}
  • isMemberOfClass函數(shù)調(diào)用的是上面的底層實現(xiàn)父能;與isKindOfClass函數(shù)底層調(diào)用不同;
  • re5為YES净神,[NSObject alloc]的類為NSObject何吝,與傳參類NSObject相等;
  • re6為YES鹃唯,[YYPerson alloc]的類為YYPerson爱榕,與傳參類YYPerson相等;
  • re7為NO坡慌,[NSObject class]的元類即根元類與傳參類NSObject(根類)不相等黔酥;
  • re8為NO,[YYPerson class]的元類與傳參類YYPerson(類)不相等洪橘;

如何獲取Class對象

  • 首先Class對象包含兩種分別為:類對象和元類對象跪者;
  • - (Class)class 與 + (Class)class:獲取的是類對象;
  • objc_getClass("類字符串"):獲取的是類對象熄求;
  • object_getClass(id obj)參數(shù)傳入實例對象渣玲,返回類對象參數(shù)傳入類對象弟晚,返回元類對象忘衍;
  • 代碼實現(xiàn)如下:
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        //實例對象
        NSObject *obj1 = [[NSObject alloc]init];
        NSObject *obj2 = [[NSObject alloc]init];
        NSLog(@"實例對象 -- %p -- %p",obj1,obj2);
        
        //類對象
        Class objClass1 = [obj1 class];
        Class objClass2 = [obj2 class];
        Class objClass3 = [NSObject class];
        Class objClass4 = object_getClass(obj1);
        Class objClass5 = objc_getClass("NSObject");
        Class objClass6 = object_getClass(objClass1);
        
        NSLog(@"class對象 -- %p -- %p -- %p -- %p -- %p -- %p",objClass1,objClass2,objClass3,objClass4,objClass5,objClass6);
        NSLog(@"class對象 -- %p",objClass6);
    }
    return 0;
}
  • 調(diào)試結(jié)果:
Snip20210624_12.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末逾苫,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子枚钓,更是在濱河造成了極大的恐慌铅搓,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搀捷,死亡現(xiàn)場離奇詭異星掰,居然都是意外死亡,警方通過查閱死者的電腦和手機指煎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門蹋偏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人至壤,你說我怎么就攤上這事∈嗑溃” “怎么了像街?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長晋渺。 經(jīng)常有香客問我镰绎,道長,這世上最難降的妖魔是什么木西? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任畴栖,我火速辦了婚禮,結(jié)果婚禮上八千,老公的妹妹穿的比我還像新娘吗讶。我一直安慰自己,他們只是感情好恋捆,可當我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布照皆。 她就那樣靜靜地躺著,像睡著了一般沸停。 火紅的嫁衣襯著肌膚如雪膜毁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天愤钾,我揣著相機與錄音瘟滨,去河邊找鬼。 笑死能颁,一個胖子當著我的面吹牛杂瘸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播劲装,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼胧沫,長吁一口氣:“原來是場噩夢啊……” “哼昌简!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起绒怨,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤纯赎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后南蹂,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體犬金,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年六剥,在試婚紗的時候發(fā)現(xiàn)自己被綠了晚顷。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡疗疟,死狀恐怖该默,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情策彤,我是刑警寧澤栓袖,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站店诗,受9級特大地震影響裹刮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜庞瘸,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一捧弃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧擦囊,春花似錦违霞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至泌类,卻和暖如春癞谒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背刃榨。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工弹砚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人枢希。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓桌吃,卻偏偏與公主長得像,于是被迫代替她去往敵國和親苞轿。 傳聞我的和親對象是個殘疾皇子茅诱,可洞房花燭夜當晚...
    茶點故事閱讀 43,440評論 2 348

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