iOS isa - 类的底层原理结构

422 阅读7分钟

上一篇我们对对象的底层本质和isa的原理进行了分析,发现对象里的isa成员变量很特别,所以这一篇我们就从isa来开始探索 类 对象 isa之间的底层结构

首先两个类分别为 XJPerson(继承NSObject) XJTeacher(继承XJPerson).

@interface XJPerson : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSUInteger age;
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age;
- (void)instanceMethod; //不实现
+ (void)classMethod;
@end

@implementation XJPerson
{
@public
    NSString *_hobby;
}

![截屏2021-07-05 上午10.13.06.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4db76b7960ba49049980e31ab09e233a~tplv-k3u1fbpfcp-watermark.image)
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age {
    self = [super init];
    if (self) {
        self.name = name;
        self.age = age;
    }
    return self;
}

+ (void)classMethod {
    
}

@end



@interface XJTeacher : XJPerson
@property (nonatomic, strong) NSString *subject;
@end

@implementation XJTeacher
@end

isa走位图

/// isa 链路
static void isaChain(id obj)
{
    // 类
    Class cls = object_getClass(obj);
    // 元类
    Class metaCls = object_getClass(cls);
    // 根元类
    Class rootCls = object_getClass(metaCls);
    // 根根元类
    Class metaOfRootCls = object_getClass(rootCls);
    
    NSLog(@"对象: %@", obj);
    NSLog(@"类: %@, %p", cls, cls);
    NSLog(@"元类: %@, %p", metaCls, metaCls);
    NSLog(@"根元类: %@, %p", rootCls, rootCls);
    NSLog(@"根根元类: %@, %p\n", metaOfRootCls, metaOfRootCls);
}

static void testCase1() {
    isaChain(NSObject.new);
    isaChain(XJPerson.new);
    isaChain(XJTeacher.new);
}

int main(int argc, const char * argv[]) {
    testCase1();
    return 0;
}

打印结果为:

截屏2021-07-02 下午5.10.46.png

根据上面得打印结果我们分析得出结论

对象 --isa--> 类 --isa--> 元类 --isa--> 根元类 --isa--> 根元类自己

这个结论可以通过下图表示出来

截屏2021-07-02 下午4.24.30.png

superclass 走位图

/// superclass 链路
static void superclassChain(Class cls)
{
    // 父类
    Class superCls = class_getSuperclass(cls);
    
    NSLog(@"父类: %@, %p", superCls, superCls);
    if (!superCls) {
        NSLog(@"\n");
        return;
    }
    superclassChain(superCls);
}

// 类的 superclass 链路
static void testCase2() {
    Class cls1 = NSObject.class;
    NSLog(@"类: %@, %p", cls1, cls1);
    superclassChain(cls1);
    
    Class cls2 = XJPerson.class;
    NSLog(@"类: %@, %p", cls2, cls2);
    superclassChain(cls2);
    
    Class cls3 = XJTeacher.class;
    NSLog(@"类: %@, %p", cls3, cls3);
    superclassChain(cls3);
}

// 元类的 superclass 链路
static void testCase3() {
    Class rootClass = NSObject.class;
    Class rootMetaClass = object_getClass(rootClass);
    NSLog(@"类: %@, %p", rootClass, rootClass);
    NSLog(@"%@元类: %@, %p", rootClass, rootMetaClass, rootMetaClass);
    superclassChain(rootMetaClass);
    
    Class personClass = XJPerson.class;
    Class personMetaClass = object_getClass(personClass);
    NSLog(@"类: %@, %p", personClass, personClass);
    NSLog(@"%@元类: %@, %p", personClass, personMetaClass, personMetaClass);
    superclassChain(personMetaClass);
    
    Class teacherClass = XJTeacher.class;
    Class teacherMetaClass = object_getClass(teacherClass);
    NSLog(@"类: %@, %p", teacherClass, teacherClass);
    NSLog(@"%@元类: %@, %p", teacherClass, teacherMetaClass, teacherMetaClass);
    superclassChain(teacherMetaClass);
}

int main(int argc, const char * argv[]) {
    testCase2();
    testCase3();
    return 0;
}

截屏2021-07-02 下午5.10.20.png

通过上面结果可以分析到superclass的链路并且分为两条:

类的 superclass 链路:

类 --superclass--> 父类 --superclass--> ... --superclass--> NSObject类 --superclass--> nil

类的元类 superclass 链路:

类的元类 --superclass--> 父类的元类 --superclass--> ... --superclass--> NSObject的元类 --superclass--> NSObject类 --superclass--> nil

上面两条链路可以由下图表示

截屏2021-07-02 下午4.24.51.png

通过上面得分析可以充分验证经典的isa/superclass的走位图

isa流程图.png

小结

  1. 对象之间不存在isa/superclass 链路关系.
  2. 对象之间不纯在继承关系,只有类才有继承.
  3. 万物皆是对象,无中生对象.

现在我们对isa的走位图以及类与superClass的继承关系都已经比较清晰了,那么类的成员变量的结构又是什么呢,我们继续开始探索挖掘

类的结构分析

首先我们来看源码 (使用的是objc4-818.2)

截屏2021-07-02 下午6.26.02.png

源码我们只看我们需要的(省略了部分源码,位置objc-runtime-new.h文件第1688行) 截屏2021-07-02 下午6.35.46.png (位置objc-runtime-new.h文件第338行,省略部分源码)

Class ISA:8字节

CLass superclass:8字节

cache_t cache:16字节

typedef unsigned long uintptr_t; 占8字节的无符号长整形

preopt_cache_t * 占8字节的指针

由于preopt_cache_t *联合体内部的指针类型,因为联合体互斥,所以整个联合体占8字节

所以struct cache_t 8+8,即cache占用16字节

小结1:探索ISA、superclass、cache是为了通过内存偏移找到bits

class_data_bits_t bits:

截屏2021-07-02 下午6.50.53.png (省略了部分源码:源码位置为objc-runtime-new.h文件第1577行-1685行)

小结2:通过获取class_rw_t* 类型的data(),将会拿到这个类的methods、properties、protocols、deepCopy、ro等等信息

class_rw_t

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

//省略了部分代码
    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;


    class_rw_ext_t *deepCopy(const class_ro_t *ro) {
        return extAlloc(ro, true);
    }

    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 *>(&ro_or_rw_ext)->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->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 *>(&ro_or_rw_ext)->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->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 *>(&ro_or_rw_ext)->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
        }
    }
};

(省略了部分源码:源码位置为objc-runtime-new.h文件第1458行-1574行)

小结3:通过解析class_rw_t这个结构体可以拿到类的信息,比如这个类的methods、properties、protocols、deepCopy、ro等等信息

获取类的属性

成员获取流程:NSObject.class -> class_data_bits_t -> class_rw_t -> property_array_t -> property_list_t -> property_t

上代码:

截屏2021-07-02 下午7.00.36.png

操作步骤:

截屏2021-07-05 上午10.04.37.png

步骤:

  1. x/4gx XJPerson.class 格式化输出XJPerson.class,拿到类的首地址
  2. p/x 0x100008610 + 0x20 首地址偏移32个字节(ISA8字节、superclass8字节、cache16字节),拿到类对象属性地址
  3. p (class_data_bits_t *)0x0000000100008630 将地址转化成class_data_bits_t类型,为了使用class_data_bits_t的函数
  4. p $2->data() 使用class_data_bits_t的data()函数,拿到class_rw_t类型的地址
  5. p $3->properties() 通过properties()函数获取XJPerson的成员变量
  6. p $4.list得到RawPtr<property_list_t>
  7. p $5.ptr解析出property_list_t的地
  8. p *$6 通过取地址的方式获取成员变量property_list_t
  9. p $7.get(0)/(1) 通过c++函数单个获取类的成员变量name、hobby

获取类的实例方法

实例方法获取流程:NSObject.class -> class_data_bits_t -> class_rw_t -> method_array_t -> method_list_t -> method_t -> big

与properties()一样,但是是获取class_rw_t对象的method()方法,有所不同的是拿到method之后并不能直接向properties一样就可以解析出来,因为在method_t结构体中还有一层结构体big(),所以在获取实例方法的时候得多解析一层结构体

截屏2021-07-05 上午10.54.55.png (省略了部分源码,源码位置为objc-runtime-new.h文件第726行-861行)

依然使用上面得案例代码进行LLDB调试:

截屏2021-07-05 下午2.14.55.png 截屏2021-07-05 下午2.15.23.png

详细步骤:

  1. 前面四步与上面获取properties()一致,当我们通过 p $2->data() 拿到3后,通过p3后,通过 `p 3->methods()`函数获取XJPerson的实例方法
  2. p $4.listp $5.ptr解析出method_list_t的地址
  3. p *$6 通过取地址的方式获实例变量数组method_list_t,entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 6),非常明显有6个实例方法
  4. 通过c++函数get()与big()单个获取类的实例方法:

p $7.get(0).big():-[XJPerson sayNB]自定义实例方法sayNB

p $7.get(1).big():-[XJPerson hobby]成员变量hobby的getter方法,是系统生成的

p $7.get(2).big():-[XJPerson sethobby:]成员变量hobby的setter方法,是系统生成的

p $7.get(3).big():-[XJPerson init:]XJPerson的init方法,是系统生成的

p $7.get(4).big():-[XJPerson name]成员变量name的getter方法,是系统生成的

p $7.get(5).big():-[XJPerson setName:]成员变量name的setter方法,是系统生成的

成员变量在哪里

成员变量获取流程源码:NSObject.class -> class_data_bits_t -> class_rw_t -> class_ro_t -> ivar_list_t -> ivar_t

截屏2021-07-05 下午6.13.35.png (省略了部分源码,源码位置为objc-runtime-new.h文件第1037行-1171行)

ivar_t源码:

截屏2021-07-05 下午6.15.53.png

调试代码不变,下面的调试大致步骤图:

截屏2021-07-05 下午6.28.31.png 截屏2021-07-05 下午6.32.27.png 可以看出ivars存在ro中,成员变量自动生成了属性_name,_hobby

类方法在哪里

类方法获取流程:NSObject.class -> metaClass -> class_data_bits_t -> class_rw_t -> method_array_t -> method_list_t -> method_t -> big

案例源码照旧(上面的say666方法,有写实现),下图是lldb动态调试过程

截屏2021-07-05 下午7.04.21.png

详细步骤:

  1. x/4gx XJPerson.class格式化打印类XJPerson,得到类的首地址
  2. p/x 0x0000000100008638 & 0x00007ffffffffff8将isa指针和ISA_MASK做与操作,拿到XJPerson的metaClass
  3. x/4gx 0x0000000100008638,格式化打印XJPerson的metaClass,拿到元类的首地址
  4. p/x 0x100008638 + 0x20,将元类的首地址偏移32个字节(ISA8字节、superclass8字节、cache_t16字节),那多元类的class_data_bits_t对象地址
  5. p (class_data_bits_t *)0x0000000100008658将地址转化为class_data_bits_t对象,方便调用函数
  6. p $3->data()调用class_data_bits_t的data函数,拿到class_rw_t对象
  7. p $4->methods()获取class_rw_t的methods方法列表
  8. p $5.listp $6.ptr拿到指向method_list_t地址的指针
  9. p *$7取地址,拿到了method_list_t对象,count为1entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1), 有一个类方法
  10. 通过c++函数get()big()单个获取类的类方法: p $8.get(0).big(): (method_t::big) $9 = { name = "say666" types = 0x0000000100003f2a "v16@0:8" imp = 0x0000000100003b50 (KCObjcBuild`+[XJPerson say666]) }

结论:类的类方法存在元类的methods里面,等同于类的类方法是元类的实例方法

补充

friend 友元类

对于一个没有定义public访问权限的类,能够让其他的类操作它的私有成员往往是有用的。例如你写了一段binary tree的代码,Node是节点类,如果能够让连接多个节点的函数不需要调用public方法就能够访问到Node的私有成员的话,一定是很方便的。

C++中的friend关键字其实做这样的事情:在一个类中指明其他的类(或者)函数能够直接访问该类中的private和protected成员。

struct str_a {
    
    int area_length;
    
    struct str_b *sb;
    
    int testFunc();
    
};

struct str_b {
    //如果这里没有这个friend,那么str_a 的sb 成员无法访问private 成员变量。
    friend str_a;
    
private:
    int age;
    
public:
    double height;
};

int str_a::testFunc() {
    
    if (sb->age > 10) {
        return 20;
    }
    return 10;
}

小端模式

iOS是小端模式,内存上从右往左读取

ULL

ULL:unsigned long long 无符号长整形