第5课-类的原理分析上-2
[TOC]
5.2.7 类方法在底层的存储
上一步中我们知道了对象方法存储在类的bits字段中,但是我们没有找到类方法,我们初步分析一下,如果类方法不在类中,那是不是可能在元类中呢,我们通过lldb验证一下:
验证通过。
我们思考一个问题,为什么类方法存储到元类里面呢? 首先我们通过上一步已经知道,对象方法已经存储到类里面,为了和对象方法区分,类方法是不是需要存储到另一个地方啊,那么也就存储元类最合适了。这也反向说明了元类存在的必要性。
结论:类方法是以实例方法的形式存储在元类中的bits字段中, 通过bits.data()->methods()---> list ---> ptr ---> get(index).big()获取
5.2.8 类方法在底层的存储-API方式解析
5.2.8.1 辅助Api
OBJC_EXPORT Class _Nullable object_getClass(id _Nullable obj) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);: object_getClass 返回一个对象的类- 入参:id对象
- 返回值: id对象对应的类
OBJC_EXPORT Class _Nullable objc_getMetaClass(const char * _Nonnull name) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);:objc_getMetaClass 获取类的元类信息- 入参:类的名字,C字符串类型
- 返回值:nil或者类的元类信息
OBJC_EXPORT const char * _Nonnull class_getName(Class _Nullable cls) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);: class_getName 返回累的名字- 入参:一个类对象 Class类型
- 返回值:类的名字,C字符串类型
OBJC_EXPORT Method _Nullable class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);:class_getInstanceMethod 获取一个类的实例方法- cls: 入参,Class类型
- SEL:入参,SEL类型
- 返回值:是一个Method类型的方法实现
OBJC_EXPORT Method _Nullable class_getClassMethod(Class _Nullable cls, SEL _Nonnull name) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);:class_getClassMethod 获取一个类的类方法- cls: 入参,Class类型
- SEL:入参,SEL类型
- 返回值:是一个Method类型的方法实现
OBJC_EXPORT IMP _Nullable class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);:class_getMethodImplementation 获取方法的实现- cls: 入参,Class类型
- SEL:入参,SEL类型
- 返回值:IMP方法的实现
OBJC_EXPORT Method _Nonnull * _Nullable class_copyMethodList(Class _Nullable cls, unsigned int * _Nullable outCount) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);:class_copyMethodList 获取当前类的实例方法列表,父类的实例方法不在里面- cls:入参,Class类型
- *outCount: int指针类型,用于计算类的实例方法个数
- Method *:方法的返回值是一个Method指针,是一个数组的首地址,用完一定要调用free(Method)释放内存
OBJC_EXPORT SEL _Nonnull method_getName(Method _Nonnull m) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);:method_getName 获取一个方法Method的SEL- 入参:方法Method
- 返回值:方法SEL
Method、SEL、IMP的关系 OC的一个方法对应底层的Method,而一个方法包含方法签名SEL和方法实现IMP
- SEL方法选择器,表示一个selector的指针
- IMP 函数指针,指向方法实现的首地址。
- Method:
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;//方法名
char *method_types OBJC2_UNAVAILABLE;//参数返回值字符串描述
IMP method_imp OBJC2_UNAVAILABLE;//方法的实现
}
5.2.8.2 代码验证类方法的存储位置
接下来我们通过代码的方式验证一下类方法的存储:
@interface ZBTeacher : NSObject
- (void)sayTeacher;
@end
@implementation ZBTeacher
- (void)sayTeacher{
}
@end
@interface ZBPerson : NSObject
{
NSObject *objc;
NSString *nickName;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSObject *obj;
- (void)sayPerson;
+ (void)sayClassPerson;
@end
@implementation ZBPerson
- (void)sayPerson{
NSLog(@"ZBPerson sayPerson");
}
+ (void)sayClassPerson{
NSLog(@"ZBPerson sayClassPerson");
}
@end
main函数调用:
void zbObjc_copyMethodList(Class pClass){
unsigned int count = 0;
Method *methods = class_copyMethodList(pClass, &count);
for (unsigned int i=0; i < count; i++) {
Method const method = methods[i];
// 获取方法名
NSString *key = NSStringFromSelector(method_getName(method));
// 打印方法名
NSLog(@"Method, name: %@", key);
}
free(methods);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
ZBPerson *person = [ZBPerson alloc];
// 获取person对象的类
Class pClass = object_getClass(person);
// 打印ZBPerson类的方法列表
zbObjc_copyMethodList(pClass);
const char *className = class_getName(pClass);
// 获取元类
Class metaClass = objc_getMetaClass(className);
// 打印元类信息
zbObjc_copyMethodList(metaClass);
}
return 0;
}
// 打印结果
2022-03-28 22:18:58.762562+0800 002-类方法归属分析[32998:1406464] -----------类-------------
2022-03-28 22:19:02.893222+0800 002-类方法归属分析[32998:1406464] Method, name: sayPerson
2022-03-28 22:19:02.893366+0800 002-类方法归属分析[32998:1406464] Method, name: name
2022-03-28 22:19:02.893468+0800 002-类方法归属分析[32998:1406464] Method, name: .cxx_destruct
2022-03-28 22:19:02.893556+0800 002-类方法归属分析[32998:1406464] Method, name: setName:
2022-03-28 22:19:02.893615+0800 002-类方法归属分析[32998:1406464] Method, name: obj
2022-03-28 22:19:02.893672+0800 002-类方法归属分析[32998:1406464] Method, name: setObj:
2022-03-28 22:19:03.552867+0800 002-类方法归属分析[32998:1406464] -----------元类-------------
2022-03-28 22:19:05.773974+0800 002-类方法归属分析[32998:1406464] Method, name: sayClassPerson
通过上面的结果打印,我们能够得出如下结论:
- 对象的实例方法存在类中,其中
cxx_destruct需要注意一下,这就是我们之前提到的OC底层的c++构造方法 - 类方法存在元类中
5.2.8.3 类方法在元类中为什么是以对象方法存在的?
我们接着探索,加入有如下函数:
void zbInstanceMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
// 获取元类
Class metaClass = objc_getMetaClass(className);
// 获取ZBPerson类和元类中实例方法sayPerson的实现
Method method1 = class_getInstanceMethod(pClass, @selector(sayPerson));
Method method2 = class_getInstanceMethod(metaClass, @selector(sayPerson));
// 获取ZBPerson类和元类中实例方法sayClassPerson的实现
Method method3 = class_getInstanceMethod(pClass, @selector(sayClassPerson));
Method method4 = class_getInstanceMethod(metaClass, @selector(sayClassPerson));
NSLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
// main函数调用如下:pClass是ZBPerson类
zbInstanceMethod_classToMetaclass(pClass);
// 打印如下:
zbInstanceMethod_classToMetaclass-0x1000081b0-0x0-0x0-0x100008148
在分析前,需要先了解class_getInstanceMethod这个方法,主要是用于获取实例方法,针对该方法,苹果有如下说明
那么我们分析打印结果,其中:
- 0x0:nil的意思
- method2,method3为nil,表示元类中不存在对象方法sayPerson,类中不存在类方法sayClassPerson
- method4有值,进一步验证了类方法是以实例方法的形式存储在元类中的。
- 打印结果进一步证明了上面的结论
我们接着探索,看下面代码:
void zbClassMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
// 获取元类
Class metaClass = objc_getMetaClass(className);
// 获取ZBPerson类和元类中类方法sayPerson的实现
Method method1 = class_getClassMethod(pClass, @selector(sayPerson));
Method method2 = class_getClassMethod(metaClass, @selector(sayPerson));
// 获取ZBPerson类和元类中类方法sayClassPerson的实现
Method method3 = class_getClassMethod(pClass, @selector(sayClassPerson));
Method method4 = class_getClassMethod(metaClass, @selector(sayClassPerson));
NSLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
// main函数调用
zbClassMethod_classToMetaclass(pClass);
// 结果打印
zbClassMethod_classToMetaclass-0x0-0x0-0x100008148-0x100008148
在分析前,需要先了解class_getClassMethod这个方法,主要是用于获取类方法,针对该方法,苹果有如下说明
其大致含义就是:如果在传入的类或者类的父类中没有找到指定的类方法,则返回NULL
通过上面的分析我们知道类方法是以实例方法的形式存储在元类中的,那么上面代码的method4为什么有值呢? 问题1: 类方法在元类中为什么是以对象方法存在的?
接下来我们看一下class_getClassMethod的源码
//获取元类
// NOT identical to this->ISA when this is a metaclass 判断是否是元类,是元类就直接返回,反之,继续找isa指向
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
可以得出class_getClassMethod的实现是获取类的类方法,其本质就是获取元类的实例方法,最终还是会走到class_getInstanceMethod,但是在这里需要注意的一点是:在getMeta源码中,如果判断出cls是元类,那么就不会再继续往下递归查找,会直接返回this,其目的是为了防止元类的无限递归查找
源码流程图如下所示
method4分析如下: pClass 是 ZBPerson元类,selName 是 sayClassPerson 首先判断 ZBPerson元类 是否是元类,此时是,直接返回元类,然后在元类中查找 sayClassPerson实例方法,发现有这个实例方法,直接返回找到的实例方法
结论1:也就是说,获取类方法底层就是获取元类的实例方法(对象方法)。正是因为万事万物都是对象,类和元类也是类对象,那么类方法和对象方法,底层的本质就是对象方法。之所以有类方法和对象方法,是因为在OC层面有所区分,但是在底层统一都叫做对象方法
5.2.8.4 class_getMethodImplementation方法通过SEL查找IMP的查找流程
我们继续探索,看如下代码:
void zbIMP_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
// 获取元类
Class metaClass = objc_getMetaClass(className);
// 获取ZBPerson类和元类中sayPerson方法的实现
IMP imp1 = class_getMethodImplementation(pClass, @selector(sayPerson));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayPerson));
// 获取ZBPerson类和元类中sayClassPerson方法的实现
IMP imp3 = class_getMethodImplementation(pClass, @selector(sayClassPerson));
IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayClassPerson));
NSLog(@"%s-%p-%p-%p-%p",__func__,imp1,imp2,imp3,imp4);
}
// main函数调用
zbIMP_classToMetaclass(pClass);
// 结果打印
zbIMP_classToMetaclass-0x100003b20-0x7ff80ec2ee00-0x7ff80ec2ee00-0x100003b50
这里我们又发现了两个问题?
- 元类中没有
sayPerson方法,为什么imp2还有值呢?应该返回nil才对啊 sayClassPerson类方法既然存在元类中,为什么imp3在类中也能获取到值呢?应该返回nil才对啊- imp2和imp3返回值也很相似
那么我查看一下class_getMethodImplementation的源码
我们发现当方法不存在的时候,返回了_objc_msgForward
结论:class_getMethodImplementation方法的查找流程,即通过sel查找IMP的过程,如果找不到方法IMP,那么也会返回一个_objc_msgForward的IMP,也就是进行了消息的转发
5.2.8.5 isKindOfClass与isMemberOfClass的区别
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //
BOOL re3 = [(id)[ZBPerson class] isKindOfClass:[ZBPerson class]]; //
BOOL re4 = [(id)[ZBPerson class] isMemberOfClass:[ZBPerson class]]; //
NSLog(@"\n re1 :%d\n re2 :%d\n re3 :%d\n re4 :%d\n",re1,re2,re3,re4);
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; //
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; //
BOOL re7 = [(id)[ZBPerson alloc] isKindOfClass:[ZBPerson class]]; //
BOOL re8 = [(id)[ZBPerson alloc] isMemberOfClass:[ZBPerson class]]; //
NSLog(@"\n re5 :%d\n re6 :%d\n re7 :%d\n re8 :%d\n",re5,re6,re7,re8);
// 结果打印
// 下面是通过类方法调用的打印
re1 :1
re2 :0
re3 :0
re4 :0
// 下面是通过对象方法调用的打印
re5 :1
re6 :1
re7 :1
re8 :1
接下来我们分析一下isKindOfClass方法的源码
其中类方法中:
- self->ISA():表示获取当前类的isa指向的类,即获取当前类的元类
- 第一次比较是获取类的元类与传入类对比,再次之后的对比是获取上次结果的父类 与传入类进行对比
实例方法:
- 第一次是获取对象类与传入类对比,如果不相等,后续对比是继续获取上次类的父类与传入类进行对比
isMemberOfClass的源码如下:
类方法:
- 获取类的元类,与传入类对比
对象方法:
- 获取对象的类,与传入类对比
结论如下: isKindOfClass
- 类方法:元类(isa) --> 根元类(父类) --> 根类(父类) --> nil(父类) 与 传入类的对比
- 实例方法:对象的类 --> 父类 --> 根类 --> nil 与 传入类的对比
isMemberOfClass
- 类方法: 类的元类 与 传入类 对比
- 实例方法:对象的类 与 传入类 对比
通过断点调试,isMemberOfClass 的类方法 和 实例方法的流程是正常的,会走到上面分析的源码,而isKindOfClass根本不会走到上面分析的源码中(!!!注意这里,这是一个坑点),而是会走到下面这个源码中,其类方法和实例方法都是走到objc_opt_isKindOfClass方法源码中
为什么会这样呢?主要是因为在llvm中编译时对其进行了优化处理
所以调用objc_opt_isKindOfClass实际走的逻辑如图所示
所以我们对代码进行分析:
- re1 :1 ,是 NSObject 与 NSObject 的对比,使用 +isKindOfClass
- NSObject(传入类,即根类) vs NSObject的元类即根元类 -- 不相等
- NSObject(传入类,即根类) vs 根元类的父类即根类 -- 相等,返回1
- re2 :0 ,是 NSObject 与 NSObject 的对比,使用 +isMemberOfClass
- NSObject根类(传入类) vs NSObject的元类即根元类 -- 不相等
- re3 :0 ,是 ZBPerson 与 ZBPerson 的对比,使用 +isisKindOfClass
- ZBPerson(传入类) vs ZBPerson的元类即元类ZBPerson -- 不相等
- ZBPerson(传入类) vs 元类ZBPerson的父类即根元类 -- 不相等
- ZBPerson(传入类) vs 根元类的父类即根类 -- 不相等
- ZBPerson(传入类) vs 根类的父类即 nil -- 不相等
- re4 :0 ,是 ZBPerson 与 ZBPerson 的对比,使用 +isMemberOfClass
- ZBPerson(传入类) vs 元类 -- 不相等
- re5 :1 ,是 NSObject对象 与 NSObject 的对比,使用-isKindOfClass
- NSObject(传入类,即根类) vs 对象的isa即NSObject根类 -- 相等
- re6 :1 ,是 NSObject对象 与 NSObject 的对比,使用-isMemberOfClass
- NSObject(传入类,即根类) vs 对象的类即NSObject根类 -- 相等
- re7 :1 ,是 ZBPerson对象 与 ZBPerson 的对比,使用-isKindOfClass
- ZBPerson(传入类) vs 对象的isa即ZBPerson -- 相等
- re8 :1 ,是 ZBPerson对象 与 ZBPerson 的对比,使用-isMemberOfClass
- ZBPerson(传入类) vs 对象的类即ZBPerson -- 相等