上一篇我们对对象的底层本质和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;
}
打印结果为:
根据上面得打印结果我们分析得出结论
对象 --isa--> 类 --isa--> 元类 --isa--> 根元类 --isa--> 根元类自己
这个结论可以通过下图表示出来
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;
}
通过上面结果可以分析到superclass的链路并且分为两条:
类的 superclass 链路:
类 --superclass--> 父类 --superclass--> ... --superclass--> NSObject类 --superclass--> nil
类的元类 superclass 链路:
类的元类 --superclass--> 父类的元类 --superclass--> ... --superclass--> NSObject的元类 --superclass--> NSObject类 --superclass--> nil
上面两条链路可以由下图表示
通过上面得分析可以充分验证经典的isa/superclass的走位图
小结
- 对象之间不存在isa/superclass 链路关系.
- 对象之间不纯在继承关系,只有类才有继承.
- 万物皆是对象,无中生对象.
现在我们对isa的走位图以及类与superClass的继承关系都已经比较清晰了,那么类的成员变量的结构又是什么呢,我们继续开始探索挖掘
类的结构分析
首先我们来看源码 (使用的是objc4-818.2)
源码我们只看我们需要的(省略了部分源码,位置objc-runtime-new.h文件第1688行) (位置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:
(省略了部分源码:源码位置为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
上代码:
操作步骤:
步骤:
- x/4gx XJPerson.class 格式化输出XJPerson.class,拿到类的首地址
- p/x 0x100008610 + 0x20 首地址偏移32个字节(ISA8字节、superclass8字节、cache16字节),拿到类对象属性地址
- p (class_data_bits_t *)0x0000000100008630 将地址转化成class_data_bits_t类型,为了使用class_data_bits_t的函数
- p $2->data() 使用class_data_bits_t的data()函数,拿到class_rw_t类型的地址
- p $3->properties() 通过properties()函数获取XJPerson的成员变量
- p $4.list得到RawPtr<property_list_t>
- p $5.ptr解析出property_list_t的地
- p *$6 通过取地址的方式获取成员变量property_list_t
- 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(),所以在获取实例方法的时候得多解析一层结构体
(省略了部分源码,源码位置为objc-runtime-new.h文件第726行-861行)
依然使用上面得案例代码进行LLDB调试:
详细步骤:
- 前面四步与上面获取properties()一致,当我们通过
p $2->data()
拿到3->methods()`函数获取XJPerson的实例方法 p $4.list
和p $5.ptr
解析出method_list_t
的地址p *$6
通过取地址的方式获实例变量数组method_list_t
,entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 6),非常明显有6个实例方法- 通过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
(省略了部分源码,源码位置为objc-runtime-new.h文件第1037行-1171行)
ivar_t源码:
调试代码不变,下面的调试大致步骤图:
可以看出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动态调试过程
详细步骤:
x/4gx XJPerson.class
格式化打印类XJPerson
,得到类的首地址p/x 0x0000000100008638
&0x00007ffffffffff8
将isa指针和ISA_MASK做与操作,拿到XJPerson的metaClassx/4gx 0x0000000100008638
,格式化打印XJPerson的metaClass,拿到元类的首地址p/x 0x100008638 + 0x20
,将元类的首地址偏移32个字节(ISA8字节、superclass8字节、cache_t16字节),那多元类的class_data_bits_t对象地址p (class_data_bits_t *)0x0000000100008658
将地址转化为class_data_bits_t对象,方便调用函数p $3->data()
调用class_data_bits_t的data函数,拿到class_rw_t对象p $4->methods()
获取class_rw_t的methods方法列表p $5.list
和p $6.ptr
拿到指向method_list_t地址的指针p *$7
取地址,拿到了method_list_t
对象,count为1
,entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
, 有一个类方法- 通过
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 无符号长整形