04-类的结构分析

128 阅读7分钟

声明

由于类的机构分析在理解过程中比较困难,所以本片博客借鉴和使用了了 juejin.cn/post/687147… www.jianshu.com/p/05c8ba273… 两位大神的相关内容,如有问题请联系我并对内容进行修改或删除

—————————————————分割线 ————————————

对象 类 元类

对于实例对象,类对象,元类对象,根元类等的关系理解

  • 实例对象\color{red}{实例对象}的isa指向其类对象\color{red}{类对象}
  • 类对象\color{red}{类对象}的isa指向其元类对象\color{red}{元类对象}
  • 元类对象\color{red}{元类对象}的isa指向根元类\color{red}{根元类}
  • 根元类\color{red}{根元类}的isa指向根元类自己\color{red}{根元类自己}
  • 子类\color{red}{子类}的superclass指向其父类\color{red}{父类}
  • 父类\color{red}{父类}的superclass指向根类\color{red}{根类}
  • 根类\color{red}{根类}的superclass指向nil\color{red}{nil}
  • 根元类\color{red}{根元类}的superclass指向根类\color{red}{根类}

验证上述结论

//定义了一个LGPerson类,继承自NSObject
LGPerson *objc2 = [LGPerson alloc];
//打印objc2的内存地址
(lldb) p/x objc2
(LGPerson *) $14 = 0x00000001007640f0
//查看其地址指向的内存信息
(lldb) x/4gx 0x00000001007640f0
0x1007640f0: 0x001d8001000021ed 0x0000000000000000
0x100764100: 0x0000000000000000 0x0000000000000000
//查看其isa信息
(lldb) p/x 0x001d8001000021ed & 0x00007ffffffffff8ULL
(unsigned long long) $15 = 0x00000001000021e8
(lldb) po 0x00000001000021e8
LGPerson

//这里我们也可以通过直接打印LGPerson类的地址来对比一下上面的LGPerson到底是不是类对象
(lldb) p/x LGPerson.class
(Class) $18 = 0x00000001000021e8 LGPerson

//接下来继续查看类对象指向的内存信息
(lldb) x/4gx 0x00000001000021e8
0x1000021e8: 0x00000001000021c0 0x0000000100333140
0x1000021f8: 0x000000010032d430 0x0000802400000000
//发现其isa中通过po打印也是一个LGPerson类型的,这就是元类
(lldb) p/x 0x00000001000021c0 & 0x00007ffffffffff8ULL
(unsigned long long) $26 = 0x00000001000021c0
(lldb) po 0x00000001000021c0
LGPerson

//继续查看元类的内存信息
(lldb) x/4gx 0x00000001000021c0
0x1000021c0: 0x00000001003330f0 0x00000001003330f0
0x1000021d0: 0x0000000102a21210 0x0004e03500000007
//它的isa打印出来是个NSObject类型,这就是根元类
(lldb) p/x 0x00000001003330f0 & 0x00007ffffffffff8ULL
(unsigned long long) $25 = 0x00000001003330f0
(lldb) po 0x00000001003330f0
NSObject

//继续查看根元类的内存信息
(lldb) x/4gx 0x00000001003330f0
0x1003330f0: 0x00000001003330f0 0x0000000100333140
0x100333100: 0x00000001006447a0 0x0004e03100000007
//查看根元类的isa信息,发现根元类的isa直接指向了自己
(lldb) p/x 0x00000001003330f0 & 0x00007ffffffffff8ULL
(unsigned long long) $25 = 0x00000001003330f0
(lldb) po 0x00000001003330f0
NSObject

//对象的第2个8字节指针指向它的superclass,可以看到objc2的superclass为0x0000000000000000,说明对象是没有父类一说的
(lldb) x/4gx objc2
0x1007640f0: 0x001d8001000021ed 0x0000000000000000
0x100764100: 0x0000000000000000 0x0000000000000000

//先查看一下LGPerson对象的内存信息,其superclass为0x0000000100333140,po其信息为NSObject,它就是根类
(lldb) x/4gx LGPerson.class
0x1000021e8: 0x00000001000021c0 0x0000000100333140
0x1000021f8: 0x000000010032d430 0x0000802400000000
(lldb) po 0x0000000100333140
NSObject

//这里我们也可以通过直接打印NSObject根类的地址来对比一下上面的0x0000000100333140到底是不是NSObject类对象
(lldb) p/x NSObject.class
(Class) $31 = 0x0000000100333140 NSObject

//继续打印根类对象的内存信息,可以看到其superClass为0x0000000000000000
(lldb) x/4gx 0x0000000100333140
0x100333140: 0x00000001003330f0 0x0000000000000000
0x100333150: 0x0000000100764490 0x0001801000000003

//这里查看一下LGPerson元类的内存信息,其superClass为0x00000001003330f0,这就是根元类
(lldb) x/4gx 0x00000001000021c0
0x1000021c0: 0x00000001003330f0 0x00000001003330f0
0x1000021d0: 0x0000000102a21210 0x0004e03500000007

//继续查看根元类的内存信息,发现其supClass为0x0000000100333140,po打印也是NSObject类型,跟上面打印的NSObject.class类对象的地址是一样的,说明根元类的superClass指向根类对象
(lldb) x/4gx 0x00000001003330f0
0x1003330f0: 0x00000001003330f0 0x0000000100333140
0x100333100: 0x00000001006447a0 0x0004e03100000007
(lldb) po 0x0000000100333140
NSObject

类在oc中实际上是以结构体的形式存在的,所以也可以通过调试和变异将main.m转成cpp文件查看相关类,元类,根类等关系的代码 先用clang命令转换

clang -rewrite-objc main.m -o main.cpp
//如果imort了UIKit
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / Applications/Xcode.app/Contents/Developer/Platforms/ iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk main.m

然后查看结构体以及关系

typedef struct objc_object LGPerson;
...
//该方法中确定了LGPerson类、元类和NSObject类、元类之间的关系
static void OBJC_CLASS_SETUP_$_LGPerson(void ) {
    OBJC_METACLASS_$_LGPerson.isa = &OBJC_METACLASS_$_NSObject;
    OBJC_METACLASS_$_LGPerson.superclass = &OBJC_METACLASS_$_NSObject;
    OBJC_METACLASS_$_LGPerson.cache = &_objc_empty_cache;
    OBJC_CLASS_$_LGPerson.isa = &OBJC_METACLASS_$_LGPerson;
    OBJC_CLASS_$_LGPerson.superclass = &OBJC_CLASS_$_NSObject;
    OBJC_CLASS_$_LGPerson.cache = &_objc_empty_cache;
}

类的结构

objc——class 结构体的代码定义如下 相关变量解读:

  • isa\color{red}{isa}
  • superclass\color{red}{superclass} 指向父类信息的指针
  • cache\color{red}{cache}
  • bits\color{red}{bits} 类实例方法,属性和协议的存储
typedef struct objc_class *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
    ...
}
struct objc_object {
private:
    isa_t isa;
...
}

bits信息获取

结构体指针偏移

对于下面代码的解释 结构体指针的地址就是该结构体成员第一个成员的地址,然后第一个成员age是int类型,所以往后偏移4个字节,则好指向height

//定义一个结构体MyStu
struct MyStu{
    int age;
    int height;
};
struct MyStu stu1 = {20, 180};//声明一个变量stu1
struct MyStu *stuP = &stu1;//用一个结体指针指向stu1

//打印stuP的地址
(lldb) p/x stuP
(MyStu *) $28 = 0x00007ffeefbff548
//把地址往后偏移4个字节,并且要告诉系统你指向的地址是个什么类型的值
(lldb) p/x (int *)(0x00007ffeefbff548 + 0x4)
(int *) $29 = 0x00007ffeefbff54c
//打印该指针中的值,拿到height
(lldb) p *$29
(int) $30 = 180
bits信息
//定义了一个protocol
@protocol MyProtocol <NSObject>
@optional
- (void)myProtocolMethod;
@end
//定义了一个LGPerson类,并声明了相关属性和方法
@interface LGPerson : NSObject<MyProtocol>
{
    NSString *innerName;
}
@property (nonatomic, strong) NSString *outName;
- (void)sayHello;

+ (void)sayGood;
@end

//1.打印LGPerson.class的指针信息
(lldb) p/x LGPerson.class
(Class) $32 = 0x00000001000025f0 LGPerson

//2.偏移32字节,16进制是0x20,并告诉系统它是一个指向class_data_bits_t类型的指针了
(lldb) p/x (class_data_bits_t *)(0x00000001000025f0 + 0x20)
(class_data_bits_t *) $33 = 0x0000000100002610

//3.调用class_data_bits_t中data()方法拿到class_rw_t类型的结构体指针,类的方法、属性、协议全存在它里面
(lldb) p/x $33->data()
(class_rw_t *) $34 = 0x000000010070c270

//4.调用class_rw_t中的methods()方法,拿到一个method_array_t类型的class
(lldb) p/x $33->data()->methods()
(const method_array_t) $35 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x0000000100002408
      arrayAndFlag = 0x0000000100002408
    }
  }
}

//5.method_array_t里面有一个属性list,猜测方法列表应该都在它里,先打印其类型,发现是个method_list_t的指针
(lldb) p/x $35.list
(method_list_t *const) $36 = 0x0000000100002408

//6.通过*指针看一下它的值是什么,是个method_list_t类型的结构体,这里可以看到总共有4个方法,并且第一个方法为sayHello
(lldb) p/x *$36
(method_list_t) $37 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 0x0000001a
    count = 0x00000004
    first = {
      name = 0x0000000100000f78 "sayHello"
      types = 0x0000000100000e1c "v16@0:8"
      imp = 0x0000000100000b90 (KCObjc`-[LGPerson sayHello])
    }
  }
}

//7.method_list_t继承自entsize_list_tt,它里面有个get方法,可以它来获取方法1
(lldb) p/x $37.get(0)
(method_t) $38 = {
  name = 0x0000000100000f78 "sayHello"
  types = 0x0000000100000e1c "v16@0:8"
  imp = 0x0000000100000b90 (KCObjc`-[LGPerson sayHello])
}

//8.method_list_t继承自entsize_list_tt,它里面有个get方法,可以它来获取方法2
(lldb) p/x $37.get(1)
(method_t) $39 = {
  name = 0x0000000100000f81 "outName"
  types = 0x0000000100000d82 "@16@0:8"
  imp = 0x0000000100000bc0 (KCObjc`-[LGPerson outName])
}

//9.method_list_t继承自entsize_list_tt,它里面有个get方法,可以它来获取方法3
(lldb) p/x $37.get(2)
(method_t) $40 = {
  name = 0x0000000100000f89 "setOutName:"
  types = 0x0000000100000e30 "v24@0:8@16"
  imp = 0x0000000100000be0 (KCObjc`-[LGPerson setOutName:])
}

//10.method_list_t继承自entsize_list_tt,它里面有个get方法,可以它来获取方法4
(lldb) p/x $37.get(3)
(method_t) $41 = {
  name = 0x00007fff777bcb90 ".cxx_destruct"
  types = 0x0000000100000e1c "v16@0:8"
  imp = 0x0000000100000c10 (KCObjc`-[LGPerson .cxx_destruct])
}

//11.尝试获取方法5时已报错
(lldb) p/x $37.get(4)
Assertion failed: (i < count), function get, file /Users/dyf/Desktop/可编译objc源码/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
//步骤1:拿到LGPerson类对象的指针地址
(lldb) p/x LGPerson.class
(Class) $42 = 0x00000001000025f0 LGPerson

//步骤2:直接依次调用相关方法拿到list,并通过*指针获取其值
(lldb) p/x *((class_data_bits_t *)(0x00000001000025f0 + 0x20))->data().methods().list
(method_list_t) $48 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 0x0000001a
    count = 0x00000004
    first = {
      name = 0x0000000100000f78 "sayHello"
      types = 0x0000000100000e1c "v16@0:8"
      imp = 0x0000000100000b90 (KCObjc`-[LGPerson sayHello])
    }
  }
}
  Fix-it applied, fixed expression was: 
    *((class_data_bits_t *)(0x00000001000025f0 + 0x20))->data()->methods().list
    
//步骤3:获取其中方法列表    
(lldb) p/x $48.get(1)
(method_t) $49 = {
  name = 0x0000000100000f81 "outName"
  types = 0x0000000100000d82 "@16@0:8"
  imp = 0x0000000100000bc0 (KCObjc`-[LGPerson outName])
}

总结

在调试过程中中虽然进行了如何查看方法和属性列表,但是类方法+ (void)sayGood;和非property声明的属性NSString *innerName;在上面的过程中没有打印出来的,这是因为类方法和非property声明的属性存在于元类中,我们上面分析的LGPerson.class是类对象并非元类。