iOS底层系列10 -- 类的结构分析

元类

  • 实例对象是由类生产出来的,例如YYPerson这是一个类,通过YYPerson类我们可以调用其alloc方法,创建出一个实例对象person,YYPerson *person = [[YYPerson alloc]init],可以说类就像一个工厂一样, 工厂可以生产出成千上万的产品,这些产品的属性和行为都是一样,这些产品在编程的世界里就是所谓的实例对象,可以这么说生产实例对象的工厂我们称之为类实例对象的isa指针指向生产实例对象的类
  • 正所谓在面向对象的编程世界里,万物皆对象,所以生产实例对象的类,其本质也是一个对象那么生产类对象的工厂我们称之为元类类对象的isa指针指向生产类对象的类,也就是所谓的元类
  • 元类是系统生成的,其定义和创建都是由编译器完成;
  • 元类 是描述 类对象 的类,每个类都有一个独一无二的元类用来 存储类方法的相关信息
  • 元类本身是没有名称的,由于与类相关联,所以使用了和类名一样的名称;
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;
}
  • 调试结果如下:
Snip20210624_11.png
  • 可以看出,不同的实例对象占据不同的内存空间类对象在内存中只占用一份内存空间元类对象在内存中也只占用一份内存空间
  • object_getClass()函数,参数传入实例对象,返回的是类对象;
  • object_getClass()函数,参数传入类对象,返回的是元类对象;
  • class_isMetaClass()函数,判断类 是否是元类对象;
  • 下面我们通过代码来验证一下上面所阐述的内容:
Snip20210210_137.png
  • YYPerson继承自NSObject;
  • YYStudent继承自YYPerson;
  • 当代码执行到断点处停下,进行LLDB命令调试,结果如下所示:
Snip20210210_138.png
  • p/x person 读取实例对象person在内存中首地址;
  • p/x 0x001d800100002375 & 0x00007ffffffffff8ULL 将实例对象person的isa的值与isa的掩码0x00007ffffffffff8ULL做位与运算得到YYPerson类的地址值0x0000000100002370,其本质就是YYPerson类对象;
  • x/4gx 0x0000000100002370 获取YYPerson类对象在内存中信息数据;其中0x0000000100002348就是YYPerson类对象的isa指针的值;
  • p/x 0x0000000100002348 & 0x00007ffffffffff8ULL 将YYPerson类对象的isa与isa的掩码0x00007ffffffffff8ULL做位与运算得到YYPerson元类的地址值,即0x0000000100002348,其本质还是YYPerson,这就证实了上面所阐述的元类本身是没有名称的,由于与类相关联,所以使用了同类名一样的名称;

如何在LLDB调试控制台获取实例对象person的类对象YYPerson的内存地址?

  • 第一种方式:p/x [person class] 以16进制打印YYPerson类对象的内存地址;
  • 第二种方式:p/x [YYPerson class] 以16进制打印YYPerson类对象的内存地址;
  • 第三种方式:p/x object_getClass(person) 以16进制打印YYPerson类对象的内存地址;
  • 第四种方式:x/4gx person 首先读取实例对象person在内存中数据,前8个字节是isa指针的值,然后 p/x isa的值 & isa掩码值,得到的就是YYPerson类对象的内存地址;
  • p/x aaa 获取的是aaa的内存地址;
  • x/4gx aaa 获取的是aaa内存地址中的数据内容;

isa指针的指向

由上面的内容我们知道实例对象的isa指向类对象类对象的isa指向元类对象,那元类对象的isa指向哪里?会这样没有终点的一直指向下去么?

Snip20210210_140.png
  • 通过上面的LLDB调试分析可以得出下面的结论:
    • 实例对象 的 isa 指向 类对象;
    • 类对象 的 isa 指向 其元类对象;
    • 元类对象 的 isa 指向 根元类,即NSObject;
    • 根元类 的 isa 指向 它自己本身;

通过类Class所创建的实例对象在内存中可以成千上万,那么类对象在内存中占几份?

#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;
}
  • 控制台的打印结果如下:
Snip20210210_142.png
  • 可以看出类对象在内存中只占有一份.

引用官方一张 关于 isa走向 与 类继承关系图

Snip20210210_143.png
isa走向(虚线部分)
  • 实例对象(Instance)的isa指向类对象class;
  • 类对象的isa指向元类对象meta class;
  • 元类对象的isa指向根元类 root meta class(NSObject);
  • 根元类对象的isa指向自己本身(NSObject);
类的继承superClass的走向(实线部分)
  • 类对象之间的继承关系:
    • 子类的SuperClass指向其父类Superclass;
    • 父类的SuperClass指向根类RootClass,这里的根类就是NSOject;
    • 根类的SuperClass指向nil,可以理解成无中生有;
  • 元类对象之间也存在继承关系:
    • 子类的元类的SuperClass指向父类的元类Superclass(meta);
    • 父类的元类的SuperClass指向根元类RootClass(metal);
    • 根元类的SuperClass指向根类RootClass 也就是NSObject;
    • 根类的SuperClass指向nil,可以理解成无中生有;
  • 实例对象之间没有继承关系,类与元类之间才有继承关系;
  • 通过例子来实际阐述上面的关系图,YYStudent与YYPerson:
Snip20210213_5.png
  • isa的走向链:
    • student的走向链:student子类实例对象 --> YYStudent子类 --> YYStudent子类的元类 --> NSObject根元类 --> NSObject根元类自身
    • Person的走向链:person子类实例对象 --> YYPerson子类 --> YYPerson子类的元类 --> NSObject根元类 --> NSObject根元类自身
  • 类的继承关系链:
    • YYStudent子类 --> YYPerson父类 --> NSObject根类 --> nil
    • YYStudent子类的元类 --> YYPerson父类的元类 --> NSObject根元类 --> NSObject根类 --> nil
  • isa指针与superClass指针在方法调用中,起到至关重要的作用;

类的结构

  • iOS底层系列02-- objc4-781源码中的objc_class与objc_object中我们知道objc_class与objc_object这两个结构体且objc_class继承自objc_object;
  • Class类是以objc_class为模版进行创建的
  • OC任意对象id是以objc_object为模版进行创建的
  • objc_class结构体定义(新的)如下所示:
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个字节;
  • superclass指针:属于Class类型,是一个指针,占8个字节;
  • cache成员:是一个cache_t结构体,其内存大小需要根据其内部的成员来确定,详细计算见下面;
  • bits成员:是一个class_data_bits_t结构体,将Class的首地址进行偏移,偏移量为面3个成员的内存大小总和,才能获取到bits成员的首地址,bits成员存储了类的相关信息数据;
计算cache成员的内存大小
  • 剔除不会占用类空间的const、void、static和函数,结构体如下所示:
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是结构体指针类型占8个字节;
  • mask_t是unsigned int 占4个字节;
  • uintptr_t是unsigned long 占8个字节;
  • uint16_t是unsigned short 占2个字节;
  • 所以cache_t结构体不论哪种CACHE_MASK_STORAGE,其内存大小都会占8+4+2+2 = 16个字节
探索bits成员
  • 根据上面关于cache内存大小的计算结果,然后isa指针与superclass指针分别占8个字节,再根据内存偏移,我们需要将class类的首地址进行32字节的偏移,方可得到bits成员的首地址;
  • YYPerson.h文件内容:
@interface YYPerson : NSObject

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

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

+ (void)sing;

@end
  • LLDB调试结果如下所示:
Snip20210219_2.png
  • p/x YYPerson.class 获取YYPerson类的首地址
  • YYPerson类的首地址为0x100002360,那么内存偏移32个字节即为0x100002380,是bits成员的首地址,注意是16进制的换算
  • p (class_data_bits_t *) 0x100002360 地址 强转为class_data_bits_t类型
  • p $1->data(),bits调用函数data(),获取class_rw_t结构体
进入class_rw_t结构体
  • 在class_rw_t结构体定义中看到三个函数分别是获取方法列表属性列表协议列表的;
  • method_array_tproperty_array_tprotocol_array_t均是一个二维数组;
  • method_array_t中存储的是method_list_t一维数组,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调试属性列表properties()结果如下:
Snip20210219_3.png
  • p $3.properties() 调用class_rw_t结构体中的properties(),获取属性列表数组property_list_t;
    • 可通过p $6.get(0) 获取属性name;
    • 可通过p $6.get(1) 获取属性weight;
  • LLDB调试实例方法列表methods()结果如下:
Snip20210219_4.png
  • p $3.methods() 调用class_rw_t结构体中的methods(),获取方法列表数组method_list_t,注意此方法列表是实例方法列表
  • 可通过p $12.get(i)获取对应的方法;
Snip20210219_5.png
  • LLDB调试实例变量的存储:
  • 首先在class_rw_t结构体中存在下面这么一个函数ro(),其返回值为class_ro_t结构体
    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结构体定义如下:
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中的方法列表,属性列表与成员变量列表均是一维数组;
Snip20210219_8.png
Snip20210219_9.png
Snip20210219_10.png
  • p $5.ro() 获取class_ro_t结构体;
  • p $7.ivars 获取实例变量列表;
  • p $9.get(0) 获取实例变量 _name;
  • p $9.get(1) 获取实例变量 _weight;
  • 总结:
    • 类的属性列表存储在类的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个字节的内存数据,其中0x00000001000023c8是isa的值,然后其与isa mask做位与运算得到元类的首地址0x00000001000023c8;
  • 元类首地址偏移32个字节,得到元类的bits成员,
  • 接下来的获取类方法的步骤与获取类的实例方法步骤相似;
  • 总结:
    • 类的实例方法是存储在类的bits成员中,类 --> bits --> data() --> methods() ;
    • 类的类方法是存储在元类的bits成员中,元类 --> bits --> data() --> methods() ;

通过MJClassInfo.h查看类对象的数据结构

  • 上面是通过LLDB控制台调试分析类对象的数据结构,下面再提供一种更直观的方式,查看类对象的数据结构,引用MJ大神写的MJClassInfo.h文件,代码实现如下:
#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对象占用的内存空间
#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;  // 协议列表
    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;
}
  • 调试类对象YYPerson的结果如下:
Snip20210625_14.png
  • 可以看到实例变量与方法都是存储在class_ro_t中,class_rw_t不存储实例变量与方法,只提供访问实例变量与方法的函数,具体见class_rw_t的结构体定义;
  • 调试元类对象的结构如下:

常见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;
}
  • 测试代码的结果分析:
    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
    其底层实现为:
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;
}
  • 测试代码的结果分析:
    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) 获取类中某个方法的是实现;
    其底层实现为:
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;
}
  • 若方法实现imp不存在,会进入消息的转发,也会返回一个函数指针;
  • 测试代码如下:
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;
}
  • 测试代码的结果分析:
    1> imp1 --> 0x100001c20 有值 YYPerson类有walk实例方法实现,则返回walk实现的函数指针;
    2> imp2 --> 0x10037eac0 有值 YYPerson元类中没有walk实例方法(元类的实例方法也就是类方法),且在元类的继承链上查找,都没有找到;进入消息转发,返回消息转发的函数指针;
    3> imp3 --> 0x10037eac0 有值 YYPerson类没有sing实例方法;在类的继承链上查找,都没有找到,进入消息转发,返回消息转发的函数指针;
    4> imp4 --> 0x100001bb0 有值 YYPerson元类中有sing实例方法(即类方法),则返回sing实现的函数指针;
- (BOOL)isKindOfClass:(Class)cls与+ (BOOL)isKindOfClass:(Class)cls
  • - (BOOL)isKindOfClass:(Class)cls 判断实例对象是否属于指定参数类,会在实例对象的类的继承链上判断,其底层实现为:
- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
  • 可以看到首先获取对象的类与传参类进行比较,如果相等直接返回YES,如果不相等会在类的继承链上 父类 --> 根类 --> nil 循环与传参类进行比较

  • + (BOOL)isKindOfClass:(Class)cls 判断类是否属于指定参数类,会在类的元类的继承链上判断;
    其底层实现为:

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
  • 可以看到首先获取类的元类与传参类进行比较,如果相等直接返回YES,如果不相等会在元类的继承链上 父类的元类 --> 根元类 --> 根类 --> nil 循环与传参类进行比较

  • 测试代码如下:

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;
}
  • 发现一个问题isKindOfClass函数不论实例方法还是类方法都不会走上面的底层实现,分析其汇编代码如下:
Snip20210220_16.png
  • isKindOfClass函数其实例方法与类方法,底层调用的都是objc_opt_isKindOfClass函数,其代码实现为:
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()获取的是元类,若与传参类一直不等,会在元类的继承链上依次进行比较;
  • 底层这么调用,主要是因为在llvm中编译时对其进行了优化处理。
  • 上面测试代码的结果分析:
  • 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 判断实例对象是否属于指定参数类,不涉及类的继承链,其底层实现为:
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}
  • 可以看到仅仅只获取实例对象的类与传参类进行比较;
  • + (BOOL)isMemberOfClass:(Class)cls判断类是否属于指定参数类,不涉及元类的继承链;
    其底层实现为:
+ (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函数调用的是上面的底层实现;与isKindOfClass函数底层调用不同;
  • 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)参数传入实例对象,返回类对象参数传入类对象,返回元类对象
  • 代码实现如下:
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;
}
  • 调试结果:
Snip20210624_12.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容