前言
本文章主要有两方面的内容:
- 上篇文章遗留的问题解析
- 通过两个经典面试题巩固isa走位
遗留的问题解析
上篇文章遗留了两个问题.
- 为什么在类中创建的成员变量,在
property_list没有找到 - 为什么在类中创建的类方法,在
method_array_t没有找到
- 先看第一个遗留问题
在上篇文章中的class_rw_t结构体中见到了methods(),properties()的方法,也通过断点调试获取了里面的内容,接着上篇文章继续断点调试探索下ro()这个指针中的东西
继续调试
分别打印下里面地址存储的信息
继续打印:
打印baseMethodList和baseProperties
总结:
有此我们可以看出成员变量存储在ro()的ivars中.因为const class_ro_t *ro() const{}
创建过程中用const修饰所以它在系统编译时就实现了,所以不能更改.
- 第二个遗留问题
经过isa结构分析得出了一个经典的isa走位图,我们根据走位图的分析,猜测我们的类方法存在在元类中,下面验证下猜测的对不对,isa分析中得知,对象的isa指针指向类,类(类对象)的isa指向元类,元类的isa指针指向根元类,根元类的isa指针指向自己,根据这个原理验证.进入源码:
继续按照上篇文章的方法打印调试
继续调试:
只有一个方法“sayBay”
打印下元类信息:
总结:
上面的步骤验证了类方法存在元类中,也验证了根据isa走位图猜测的类方法存储位置.
面试题(一)验证
- 资料1:
结果:
第一组NSLog()
**知识点:
isKindOfClass 源码解析(实例方法 & 类方法)
- isMemberOfClass 源码解析(实例方法 & 类方法)**
断点跟进:由LLVM优化断点走这边
总结:
isKindOfClass
* 类方法:元类 --> 根元类 --> 根类 --> nil 与 传入类的对比
- 实例方法**:
对象的类 --> 父类 --> 根类 --> nil与传入类的对比**
isMemberOfClass
-
类方法:
类的元类与传入类对比 -
实例方法:
对象的父类与传入类对比
用上面的答案分析下执行结果:
类方法 + (BOOL)isKindOfClass:(Class)cls和 +(BOOL)isMemberOfClass:(Class)aClass
- re1
[NSObject class] 根类(NSObject) 源码中的 tcls = self->ISA(),获取的是NSObject(根元类),所以第一次for循环return false,继续for循环 tcls = tcls->superclass赋值NSObject(根元类)的superclass是根类(NSObject)所以return true
- re3
MRPerson类 vs MRPerson的元类即元类LGPerson -- 不相等
- re2
NSObject根类(类) vs NSObject的元类即根元类 -- 不相等
- re4
MRPerson类 vs 元类 -- 不相等
**实例方法 调用 **- (BOOL)isKindOfClass:(Class)cls 和- (BOOL)isMemberOfClass:(Class)aClass
- re5
- re7
LGPerson(类对象) vs 对象的类即LGPerson -- 相等
- re6
NSObject(类对象,即根类) vs 对象的类即NSObject根类 -- 相等
- re8
LGPerson(类对象) vs 对象的类即LGPerson -- 相等
面试题(二)验证
假设我们有一个MRPerson类,并且- (void)sayHello和+ (void)sayHappy 都有实现
#import <Foundation/Foundation.h>
#import "MRPerson.h"
#import <objc/runtime.h>
#ifdef DEBUG
#define FYLog(format, ...) printf("%s\n", [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]);
#else
#define FYLog(format, ...);
#endif
void fyObjc_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);
}
void fyInstanceMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));
Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
NSLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
void fyClassMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getClassMethod(pClass, @selector(sayHello));
Method method2 = class_getClassMethod(metaClass, @selector(sayHello));
Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
// 元类 为什么有 sayHappy 类方法 0 1
//
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
NSLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
void fyIMP_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
// - (void)sayHello;
// + (void)sayHappy;
IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));
IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));
IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));
NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
NSLog(@"%s",__func__);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
MRPerson *person = [MRPerson alloc];
Class pClass = object_getClass(person);
fyObjc_copyMethodList(pClass);
fyInstanceMethod_classToMetaclass(pClass);
fyClassMethod_classToMetaclass(pClass);
fyIMP_classToMetaclass(pClass);
}
return 0;
}
打印结果:
分析结果之前先看下这两个方法的源码结构:
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name) 返回类的对象方法
调取苹果的API
其大致含义就是:如果在传入的类或者类的父类中没有找到指定的实例方法,则返回NULL
所以对fyInstanceMethod_classToMetaclass 这个方法的调用分析
-
method1:0x100003150
-
传入的
pClass是MRPerson元类传入的pClass是MRPerson类,需要去获取selName = sayHello的实例方法 -
首先在
MRPerson中查找,有前面的MRPerson类可知,是有这个实例方法的,所以会返回查找到的实例方法,所以method1的地址不为0x0
-
****method2:****0x0
-
传入的
pClass是MRPerson元类,需要去获取selName = sayHello的实例方法 -
其查找的顺序为
元类 --> 根元类 --> 根类 --> nil,直到最后也没有找到,所以class_getInstanceMethod返回NULL,其method2的地址为0x0,表示未找到 -
****method3:****0x0
-
传入的
pClass是MRPerson类,需要去获取selName = sayHello的实例方法 -
查找顺序为
MRPerson类 --> 根类 --> nil,也没有找到sayhello实例方法,返回NULL,所以method3的地址为0x0,表示未找到
-
****method4:****0x1000030e8
-
传入的
pClass是MRPerson元类,需要去获取selName = sayHello的实例方法 -
首先在
MRPerson元类中查找,发现有sayHappy的实例方法,主要是因为类对象的类方法存储在元类中,类方法在元类中是实例方法,然后返回查找到的实例方法,所以method4的地址为0x1000030e8,表示找到了指定的实例方法
class_getClassMethod(Class _Nullable cls, SEL _Nonnull name) 返回类的类方法
cls->getMeta()
if(是元类) return 自己,因为我们类方法最终存在于元类这一层,就不用继续往下找了,防止了无限递归
所以对fyClassMethod_classToMetaclass这个方法的调用分析
-
method1 地址:0x0
-
pClass是MRPerson类,selName是sayHello -
首先判断
MRPerson类是否是元类,此时不是,返回MRPerson的元类,然后在元类中查找sayhello实例方法。查找顺序如下:元类 --> 根元类 --> 根类 --> nil,最后返回NULL -
method2地址:0x0
-
pClass是MRPerson元类,selName是sayHello -
首先判断
MRPerson元类是否是元类,此时是,直接返回元类,然后在元类中查找sayhello实例方法,发现并没有找到,返回NULL -
**method3地址:**0x1000030e8
-
pClass是MRPerson类,selName是sayHappy -
首先判断
LGPerson类是否是元类,此时不是,返回LGPerson的元类,然后在元类中查找 **sayHappy**实例方法,发现有这个实例方法,直接返回找到的实例方法 -
**method4地址:**0x1000030e8
-
pClass是MRPerson元类,selName是sayHappy -
首先判断
MRPerson元类是否是元类,此时是,直接返回元类,然后在元类中查找sayHappy实例方法,发现有这个实例方法,直接返回找到的实例方法 -
getMeta()方法做元类判断,所以method4返回一次有值
class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name) 返回方法的实现
根据上面的源码方法流程,现查找,找不到_objc_msgForward转发
总结:
-
class_getInstanceMethod:获取实例方法,如果指定的类或其父类不包含带有指定选择器的实例方法,则为NULL -
class_getClassMethod:获取类方法,如果指定的类或其父类不包含具有指定选择器的类方法,则为NULL。 -
class_getMethodImplementation:获取方法的具体实现,如果未查找到,则进行消息转发