isa流程分析
首先来看一张比较熟悉的isa的流程走位图
isa指向链
- 实例对象
instance的isa指向类class; - 类对象
class也有isa指向的是元类meta, - 元类
mata中也有isa指向的是根元类root meta;
类的继承链
子类继承与父类,父类继承与根类,根类指向的是nil;- 在
元类中也存在继承,子类的元类继承与父类的元类,父类的元类继承与根元类,根元类又继承与根类;
isa流程实例验证
补充:在获取到对象的
isa值后,可以通过&(与)一个掩码ISA_MASK0x007ffffffffffff8ULL来获取到对象关联的类地址。
isa指向链验证
定义一个LGPerson继承NSObject,同时定义LGTeacher继承LGPerson。
LGPerson *p = [LGPerson alloc];
NSLog(@"%@",p);
LGTeacher *t = [LGTeacher alloc];
NSLog(@"%@",t);
在LLDB调试对象t,通过t的isa找到关联类LGTeacher的地址为0x0000000100008310。
接着我们按同样的方式输出,找到
LGTeacher类对象的isa关联类地址为0x00000001000082e8。
往下找,找到
LGTeacher元类对象的isa关联类地址为0x00007ff85e5f2220。
按同样的方式,找到
NSObject类对象的isa关联地址为0x00007ff85e5f2220,和LGTeacher元类对象的isa关联类地址一致,可以验证元类的isa指向根元类。
根元类的isa指向自己。
类的继承链验证
同样按照LGPerson继承NSObject,LGTeacher继承LGPerson的定义。
Class tClass = LGTeacher.class;
Class pClass = class_getSuperclass(tClass);
Class nClass = class_getSuperclass(pClass);
Class rClass = class_getSuperclass(nClass);
NSLog(@"\n tClass-%@ \n pClass-%@ \n nClass-%@ \n rClass-%@ \n", tClass, pClass, nClass, rClass);
通过上面代码输出打印,很明显看出
subClass->superClass->NSObject->nil的继承链关系。
LGTeacher * teacher = [LGTeacher alloc];
Class tClass = object_getClass(teacher);
Class mtClass = object_getClass(tClass);
Class mtSuperClass = class_getSuperclass(mtClass);
NSLog(@"\n teacher %p 实例对象 -- %p 类 -- %p 元类 -- %p 元类父类", teacher, tClass, mtClass, mtSuperClass);
LGPerson * person = [LGPerson alloc];
Class pClass = object_getClass(person);
Class mpClass = object_getClass(pClass);
Class mpSuperClass = class_getSuperclass(mpClass);
NSLog(@"\n person %p 实例对象 -- %p 类 -- %p 元类 -- %p 元类父类", person, pClass, mpClass, mpSuperClass);
NSObject * obj = [NSObject alloc];
Class objClass = object_getClass(obj);
Class mobjClass = object_getClass(objClass);
Class mobjSuperClass = class_getSuperclass(mobjClass);
NSLog(@"\n NSObject %p 实例对象 -- %p 类 -- %p 元类 -- %p 元类父类", obj, objClass, mobjClass, mobjSuperClass);
teacher 0x600000201ba0 实例对象 -- 0x100008310 类 -- 0x1000082e8 元类 -- 0x100008338 元类父类
person 0x600000201bc0 实例对象 -- 0x100008360 类 -- 0x100008338 元类 -- 0x7ff85e5f2220 元类父类
NSObject 0x600000004030 实例对象 -- 0x7ff85e5f2270 类 -- 0x7ff85e5f2220 元类 -- 0x7ff85e5f2270 元类父类
通过输出信息我们可以看出
teacher的元类父类地址0x100008338==person的元类地址0x100008338;person的元类父类地址0x7ff85e5f2220==NSObject的元类地址0x7ff85e5f2220;NSObject的元类父类地址0x7ff85e5f2270==NSObject的类地址0x7ff85e5f2270;
综合,我们可以得出元类的继承链关系为
Sub Meta->Super Meta->Root Meta->NSObject。
类的结构
前面对象的本质中,我们知道Class的定义是objc_class结构体类型,那么就看一下objc_class的结构体的结构是怎样定义的。
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
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
... 其他代码
}
这里
objc_class的继承与objc_object,objc_object的结构中只有一个isa指针, 因此,objc_class的成员组成为:
- 从
objc_object继承的指针isa;superclass:Class类型指向父类的指针cache:缓存相关的东西bits:数据
cache结构
cache_t也是一个结构体定义
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // 8字节
union {
struct {
explicit_atomic<mask_t> _maybeMask; // uint32_t 4字节
#if __LP64__
uint16_t _flags; // 2字节
#endif
uint16_t _occupied; // 2字节
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache; // 8字节
};
}
分析整个cache_t的结构,发现cache_t的内存总共为16字节。
关于cache_t的探究专门写了一篇文章。
bits探究
bits是今天的探究重点,bits到底存储了哪些数据,在objc_class里有一段源码是data操作。
class_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
这里data的类型是class_rw_t,在class_rw_t的源码中,我们可以看到几个方法:
ro:成员变量、methods:方法、properties:属性、protocols协议 ,我们在类中定义的方法,属性等就行通过调取class_rw_t结构体中的方法获取的。
实例探究
下面我们通过实例,来验证一下类的结构是否如上面分析的一致。
我们自定义一个类
LGPerson继承自NSObject,定义一些属性和实力方法以及类方法。
LLDB调试输出
这里我们可以看出第一个地址
0x0000000100008228是类的第一个成员isa,第二个地址0x0000000100816140是类的第二个成员superclass。
前面我们知道isa指针8字节,superclass指针8字节,cache结构体16字节,通过LGPerson的首地址便宜8 + 8 + 16 = 32字节我们就可以得到bits的内存地址。
这里用
LGPerson的首地址0x100008250加上32字节,得到地址0x0000000100008270,然后转换一下指针类型,得到$9为class_data_bits_t类型,调用data方法就得到$10地址为class_rw_t类型,下面就来探究class_rw_t中的属性、方法、协议。
注意
在转换
class_data_bits_t这一步,需要在源码环境调试,关于源码的下载和编译,介绍一个靓仔,# iOS 全网最新objc4 可调式/编译源码还有编译好的源码的下载地址
属性properties
调用class_rw_t的properties()方法,得到property_array_t类型的数组,继承与list_array_tt,找到list下的ptr。
ptr为property_list_t类型,是继承与entsize_list_tt,
entsize_list_tt部分源码
struct entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count; // 数量
uint32_t entsize() const {
return entsizeAndFlags & ~FlagMask;
}
uint32_t flags() const {
return entsizeAndFlags & FlagMask;
}
Element& getOrEnd(uint32_t i) const {
ASSERT(i <= count);
return *PointerModifier::modify(*this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize()));
}
Element& get(uint32_t i) const { // 获取元素方法
ASSERT(i < count);
return getOrEnd(i);
}
// ... 省略
};
通过get方法,获取元素,可以看到下面的结果,LGPerson中的属性name和age在properties()里,而成员变量hobby不在这里。
方法methods
调用class_rw_t的methods()方法,得到method_array_t类型的数组,继承与list_array_tt,同样找到list下的ptr。
这里看到
ptr是method_list_t类型,同样继承与entsize_list_tt,其中有count为6,输出查看
这里的元素为method_t类型,method_t为结构体类型,其中的一个成员变量为big的结构体,里面是方法名称等信息。
我们在调用
method_t的big方法,查看输出
这里看到6个方法分别是
- 实例方法:
sayHello;- 属性name的get方法;
C++析构函数:.cxx_destruct- 属性
name的set方法;- 属性
age的get方法;- 属性
age的get方法;
这里的6个方法都是实力对象方法,并没有我们定义的类方法sayWorld。
协议protocols
我们自定义一个协议LGPersonDelegate,让LGPerson遵守并实现协议方法
调用
class_rw_t的protocols()方法,得到protocol_array_t类型的数组,继承与list_array_tt,同样找到list下的ptr。
这里看到
ptr是protocol_list_t类型,不是继承自entsize_list_tt,那我们看一下protocol_list_t的定义
看到
protocol_list_t的定义,我们知道count值为1,说明是有值,但是其成员是protocol_ref_t为uintptr_t类型,那怎么输出查看这个count中的1到底是什么呢
查看
protocol_ref_t的定义,通过注释信息,我们可以看到protocol_ref_t未映射到protocol_t类型,那我们就找protocol_t的定义
这里看到
protocol_t中有mangledName以及instanceMethods等,只要得到protocol_t就可以输出我们想要的名称方法等信息,怎么才能从protocol_ref_t映射到protocol_t呢,全局找一下吧
一不小心找到了😁,这里我们看到,
protocol_ref_t是可以强转protocol_t的,那我们就试试
强转成功,调用
demangledName方法,我们就得到了LGPersonDelegate,那我们再找一下协议方法
按照
method输出的经验,成功找到协议方法personDelegateMethod。
ro
调用class_rw_t的ro方法,得到class_ro_t的结构体
查看
class_ro_t的内容,看到这里有成员ivars,这引起了我们的注意
接着查看
ivars,是ivar_list_t类型的结构体,也是继承entsize_list_tt,那么我们就可以调用get方法查看成员。
调用get方法查看输出,这里就可以看到3个成员变量,自定义hobby和系统帮我们定义的属性生成带_的成员变量。
类方法
在上面的methods方法中,我们并没有得到定义的类方法sayWorld,不由得我陷入了沉思,我们定义的类结构中,没有我们定义的类方法,那类方法会存在哪里呢?
这里我们换个思考角度想一下,在methods中全都是实例方法,也就是对象方法,对象方法是存在类中;类方法是类对象在调用,那类方法是不是存在元类中呢?不得而知,那么我们就去验证一下。
首先获取到LGPerson的元类地址,然后按照methods方面的方法步骤,我们成功输出sayWorld方法,也验证了上面的猜想。
这里我们不由的想,
OC的底层是C和C++实现的,在C语言和C++中,不存在对象方法和类方法的区分,有的都是函数实现,在OC的设计中,一个类可以new出无数个对象,因此把方法存在类中,而不是动态创建的对象中,是合理的。因为
OC的对象方法和类方法的定义是-和+的区分,那么方法名称就会有重名的存在,因此才会引入元类的概念,元类的存在就是解决类方法的问题。
类的结构总结
类的结构总结用一张流程图
其中
protocol_ref_t是一个无符号长整型,可以强转为protocol_t,这是后面才找到的。
以上就是对于OC中类的结构分析,如有疑问或错误之处,请评论区留言或私信我,非常感谢!!!