前言
这篇文章需要涉及到 isa 与 superclass 的指向流程分析,如果你对此不清晰,可以在iOS底层原理05:isa底层原理探索下这篇文章中了解一下。
1、iskindOfClass & isMemberOfClass面试题
下面代码输出:
//Person 继承 NSObject
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL re3 = [(id)[Person class] isKindOfClass:[Person class]];
BOOL re4 = [(id)[Person class] isMemberOfClass:[Person class]];
NSLog(@"\n re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
BOOL re7 = [(id)[Person alloc] isKindOfClass:[Person class]];
BOOL re8 = [(id)[Person alloc] isMemberOfClass:[Person class]];
NSLog(@"\n re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
多花点时间思考思考......
isKindOfClass 源码解析
//类方法
//获取类的元类与传入的类对比,再次之后的对比是获取上次结果的父类与传入的类进行对比
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
//实例方法
//获取对象的类与传入的类对比,如果不相等,再次对比是继续获取上次 类的父类与传入的类进行对比
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
isMemberOfClass 源码解析
//类方法
//获取类的元类,与传入的类对比
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
//实例方法
//获取对象的类,与传入的类对比
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
通过断点调试,遇到了一个坑,isKindOfClass不走源码的流程,而是走到objc_opt_isKindOfClass源码中。为什么会有这个坑呢,因为在llvm中编译时对其进行了优化处理。
查看汇编代码
objc_opt_isKindOfClass源码
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
if (slowpath(!obj)) return NO;
//获取isa,
//如果obj 是对象,则isa是类,
//如果obj 是类,则isa是元类
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
for (Class tcls = cls; tcls; tcls = tcls->superclass) {
if (tcls == otherClass) return YES;
}
return NO;
}
#endif
return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
代码分析:
- re1
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
传入的obj 为 NSobject, otherClass 为 NSObject,cls = obj->getIsa()为 NSobject meta 进入循环
第一次循环:Class tcls = cls即
NSobject meta,otherClass 为NSobject;执行判断条件if (tcls == otherClass),不相等;执行tcls = tcls->superclass,此时tcls指向NSobject meta的父类 ,即NSObject。进入第二次循环。第二次循环:此时
tcls为NSobject,otherClass 依然是 NSobject,执行判断条件if (tcls == otherClass)相等,return YES。 所以 re1 的结果为 1。
- re2
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
传入的cls 为 NSObject
self->ISA( ),self的 isa 指向NSObject meta;NSObject meta与NSObject不相等。所以 re2 的结果为 0。
- re3
BOOL re3 = [(id)[Person class] isKindOfClass:[Person class]];
传入的obj 为 Person, otherClass 为 Person,cls = obj->getIsa()为 Person meta 进入循环
第一次循环:Class tcls = cls即
Person meta,otherClass 为Person;执行判断条件if (tcls == otherClass),不相等;执行tcls = tcls->superclass,此时tcls指向Person meta的父类 ,即NSObject。进入第二次循环。第二次循环:此时
tcls为NSobject meta,otherClass 依然是 Person,执行判断条件if (tcls == otherClass)不相等;执行tcls = tcls->superclass,此时tcls指向NSobject meta的父类 ,即NSObject。进入第三次循环。第三次循环:此时
tcls为NSobject,otherClass 依然是 Person,执行判断条件if (tcls == otherClass)不相等,执行tcls = tcls->superclass,此时tcls指向NSobject的父类 ,为nil,不满足for循环条件,结束循环,return NO,即re3 = 0;
- re4
BOOL re4 = [(id)[Person class] isMemberOfClass:[Person class]];
传入cls为
Person
self->ISA(),self的 isa 指向Person meta;Person meta与Person不相等。所以 re4 的结果为 0。
- re5
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
传入的
obj为NSobject对象,otherClass为NSObject,cls = obj->getIsa(),NSobject对象的isa指向根类即NSobject进入循环。Class tcls = cls即
NSobject,执行判断条件if (tcls == otherClass)相等所以 re5 的结果为 1。
- re6
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
传入的cls 为 NSObject, self 是
NSObject 对象[self class] 为
NSObject 类,[self class] 和 cls相等。所以 re6 的结果为 1。
- re7
BOOL re7 = [(id)[Person alloc] isKindOfClass:[Person class]];
传入的
obj为Person对象,otherClass为Person类,cls = obj->getIsa(),Person对象的isa指向Person类即Person进入循环。Class tcls = cls即
Person,执行判断条件if (tcls == otherClass)相等。r所以 re7 的结果为 1。
- re8
BOOL re8 = [(id)[Person alloc] isMemberOfClass:[Person class]];
传入的cls 为
Person类, self 是Person 对象。
[self class]为Person 类,[self class] 和 cls相等。 所以 re8 的结果为 1。
打印结果
2、class_getInstanceMethod面试题
前期准备
- 先定义一个
Person类继承NSobject,Person类有一个实例方法eat()和一个类方法run();再定义一个Teacher类继承Person类,有一个实例方法teach()。
@interface Person : NSObject
- (void)eat;
+ (void)run;
@end
@implementation Person
- (void)eat{
NSLog(@"eat something");
}
+ (void)run{
NSLog(@"run run");
}
@end
@interface Teacher : Person
-(void)teach;
@end
@implementation Teacher
-(void)teach{
NSLog(@"teacher teach");
}
@end
- 下面代码会打印什么
void myInstanceMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(pClass, @selector(eat));
Method method2 = class_getInstanceMethod(metaClass, @selector(eat));
Method method3 = class_getInstanceMethod(pClass, @selector(run));
Method method4 = class_getInstanceMethod(metaClass, @selector(run));
MyNSLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [Person alloc];
Class pClass = object_getClass(person);
myInstanceMethod_classToMetaclass(pClass);
}
return 0;
}
class_getInstanceMethod分析
-
分析之前,我们先看看
class_getInstanceMethod是什么?我们先看看苹果官方的说明,键盘上command + shift + 0搜索class_getInstanceMethod -
大概的意思是:
如果在传入的类或者类的父类中没有找到指定的实例方法,则返回NULL。 -
从代码中我们可以看出,传入的pClass是Person类,通过
objc_getMetaClass获取的metaClass是Person元类。
###代码分析:
method1
传入的pClass是
Person类,需要去获取SEL = eat的实例方法。在Person类中查找,从上面的代码可以得知,是有这个实例方法的,所以会返回查找到的实例方法,所以method1会有地址输出。
method2
传入的pClass是
Person元类,需要去获取SEL = eat的实例方法。在Person元类中查找,元类中没有这个实例方法的,所以会输出地址0x0。
method3
传入的pClass是
Person类,需要去获取SEL = run的实例方法。在Person类中没有这个实例方法的,所以会输出地址0x0。
method4
传入的pClass是
Person元类,需要去获取SEL = run的实例方法。在Person元类中有这个实例方法的,主要是
因为类对象的类方法存储在元类中,类方法在元类中是实例方法,所以method4会有地址输出。
- 输出结果:
myInstanceMethod_classToMetaclass - 0x1000031f0-0x0-0x0-0x100003188
3、class_getClassMethod面试题
下面代码会打印什么呢?
void myClassMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getClassMethod(pClass, @selector(eat));
Method method2 = class_getClassMethod(metaClass, @selector(eat));
Method method3 = class_getClassMethod(pClass, @selector(run));
Method method4 = class_getClassMethod(metaClass, @selector(run));
MyNSLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [Person alloc];
Class pClass = object_getClass(person);
myClassMethod_classToMetaclass(pClass);
}
return 0;
}
class_getClassMethod分析
- 首先先了解
class_getClassMethod方法,主要是用于获取类方法,苹果官方有如下说明 - 大概的意思是:
如果在传入的类或者类的父类中没有找到指定的类方法,则返回NULL - 再来看看源码:
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
// 返回的是元类的实例方法
return class_getInstanceMethod(cls->getMeta(), sel);
}
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
- 通过源码的实现,我们发现
class_getClassMethod方法当传入的是类时返回的是元类的实例方法,如果传入的是元类时就返回元类本身的实例方法。 - 在
getMeta源码中,如果判断出cls是元类,那么就不会再继续往下递归查找,会直接返回this,其目的是为了防止元类的无限递归查找。
###代码分析:
- method1
pClass 是
Person类,sel是eat。Person类不是元类,返回Person的元类,然后在元类中查找
eat实例方法。查找顺序如下:元类 --> 根元类 --> 根类 --> nil,最后返回NULL,地址0x0。
- method2
pClass 是
Person元类,sel是eat。此时是
元类,然后在元类中查找eat实例方法,并没有实例方法,最后返回NULL,地址0x0。
- method3
pClass 是
Person类,sel是run。
Person类不是元类,返回Person的元类,然后在元类中查找run实例方法,发现有这个实例方法,直接返回找到的实例方法,地址0x100003190。
- method4
pClass 是
Person元类,sel是run。
Person元类是元类,直接返回元类,然后在元类中查找run实例方法,发现有这个实例方法,直接返回找到的实例方法,地址0x100003190。
4、class_getMethodImplementation面试题
- 先来看看下面代码
void myIMP_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
IMP imp1 = class_getMethodImplementation(pClass, @selector(eat));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(eat));
IMP imp3 = class_getMethodImplementation(pClass, @selector(run));
IMP imp4 = class_getMethodImplementation(metaClass, @selector(run));
MyNSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
MyNSLog(@"%s",__func__);
}
class_getMethodImplementation分析
-
class_getMethodImplementation主要是返回方法的具体实现,苹果官方有如下说明大概意思是:返回的函数指针
可能是运行时内部的函数,而不是实际的方法实现。 例如,如果类的实例不响应selector,则返回的函数指针将成为运行时消息转发机制的一部分。 -
再来看看
class_getMethodImplementation源码实现
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
//查找方法实现
imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
// Translate forwarding function to C-callable external version
//如果没有找到,则进行消息转发
if (!imp) {
return _objc_msgForward;
}
return imp;
}
代码分析:
- imp1 函数指针地址:0x100001b50
pClass 是
Person类,sel是eat。在
Person.m文件,可以得出Person类中可以查找到eat的具体实现,所以返回一个imp函数指针的地址。
- imp2 函数指针地址:0x1002c3800
pClass 是
Person元类,sel是eat。根据
类方法存储在元类中可知,eat是一个实例方法,并不存储在元类中,也没有其任何实现,所以进行了消息转发。
- imp3 函数指针地址:0x1002c3800
pClass 是
Person类,sel是run。在Person.m文件,
run是一个类方法,并不存储在类中,也没有其任何实现,所以进行了消息转发。
- imp4 函数指针地址:0x100001b20
pClass 是
Person元类,sel是run。根据
类方法存储在元类中可知,可以在元类中查找到run的具体实现,所以返回一个imp函数指针的地址
总结
class_getInstanceMethod:获取实例方法,如果指定的类或其父类不包含带有指定SEL的实例方法,则为NULL
class_getClassMethod:获取类方法,如果指定的类或其父类不包含具有指定SEL的类方法,则为NULL。
class_getMethodImplementation:获取方法的具体实现,如果未查找到,则进行消息转发。