在iOS面试中关于类&isa的面试题有很多,其中有两道题是比较经典的,这里我来解读一下。
1、class_getInstanceMethod,class_getClassMethod
2、isKindOfClass 和 isMemberOfClass的区别
第一题的题目
@interface MCPerson : NSObject
{
NSString *nickName;
}
@property (nonatomic,copy) NSString *name;
-(void)sayHello;
+(void)sayHappy;
@end
@implementation MCPerson
-(void)sayHello{
NSLog(@"%s",__func__);
}
+(void)sayHappy{
NSLog(@"%s",__func__);
}
@end
void mcInstanceMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
[[NSObject class] isKindOfClass:[NSObject class]];
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 mcClassMethod_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));
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
NSLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
- objc_getMetaClass:获取元类
- class_getInstanceMethod:获取实例方法
- class_getClassMethod:获取类方法
要求给出上述哪些方法存在,哪些方法不存在
首先看一下打印结果
首先我们必须要了解,实例方法、类方法分别存在哪里
- 实例方法以实例方法存在类中
- 类方法以实例方法存在元类中
- class_getInstanceMethod 苹果官方文档的定义是:如果在传入的类或者类的父类中没有找到指定的实例方法,则返回NULL
- class_getClassMethod 苹果官方文档的定义是:如果在传入的类或者类的父类中没有找到指定的类方法,则返回NULL
- 元类也是有父类的哦
知道这些,我们很清楚的就可以得到第一个函数的答案
// 类中有该实例方法 1
Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
// 元类中没有该实例方法 0
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));
// 类中没有该实例方法,有这个类方法 0
Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
// 类方法以实例方法的形式存在元类中 1
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
第二个函数就有一些疑问了
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));
// 为什么元类中有这个类方法,类方法不是以实例方法的形式存在元类中的吗?
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
NSLog(@"%s-%p-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
所以,我们需要看一下class_getClassMethod的底层实现
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在底层也是调用的class_getInstanceMethod方法,由于是元类,在中metaClass是元类,所以直接返回了自己,就相当于是在当前元类中找实例方法,所以找到了方法。
在元类中
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
等价于
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
总结:
class_getInstanceMethod:获取实例方法,如果指定的类或其父类不包含带有指定选择器的实例方法,则为NULL
class_getClassMethod:获取类方法,如果指定的类或其父类不包含具有指定选择器的类方法,则为NULL。如果传的类是元类,等价于调用class_getInstanceMethod方法
class_getMethodImplementation:获取方法的具体实现,如果未查找到,则进行消息转发,之后再详细解析
第二题:isKindOfClass 和 isMemberOfClass的区别
- 1、isKindOfClass可用于判断对象是否是一个类的成员,或者是该派生类的成员
- 2、isMemberOfClass可用于判断对象是否是当前类的成员
如下题,推测输出结果
void isClassEqual(){
//-----使用 iskindOfClass & isMemberOfClass 类方法
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //
BOOL re3 = [(id)[MCPerson class] isKindOfClass:[MCPerson class]]; //
BOOL re4 = [(id)[MCPerson class] isMemberOfClass:[MCPerson class]]; //
NSLog(@"\n re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
}
void isInstanceEqual (){
//-----使用 iskindOfClass & isMemberOfClass 类方法
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; //
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; //
BOOL re7 = [(id)[MCPerson alloc] isKindOfClass:[MCPerson class]]; //
BOOL re8 = [(id)[MCPerson alloc] isMemberOfClass:[MCPerson class]]; //
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}
先看下输出结果
分析源码
//第一次比较是 获取类的元类 与 传入类对比,再次之后的对比是获取上次结果的父类 与 传入 类进行对比
+ (BOOL)isKindOfClass:(Class)cls {
// NSOBject-> isa 根元类
// NSObject元类是根元类 根元类的父类是NSObject
// NSObject VS NSObject -> NSObject元类 VS NSObject -> NSObject VS NSObject return YES;
// MCPerson元类 的父类是NSObject元类
// MCPerson VS MCPerson -> MCPerson元类 VS MCPerson -> NSObject元类 VS MCPerson ---- return NO
for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
//第一次是获取对象的类 与 传入类对比,如果不相等,后续对比是继续获取上次 类的父类 与传入类进行对比
- (BOOL)isKindOfClass:(Class)cls {
// NSObject 对象 VS NSObject 直接 NSObject类 VS NSObject类 return YES;
// MCPerson 对象 VS MCPerson 直接 MCPerson类 VS MCPerson类 return YES;
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
// 获取元类进行比较
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
// 获取类进行比较
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
然而理想很丰满,现实很骨感啊,isMemberOfClass是可以进入源码断点的,但是isKindOfClass不进断点,原因是llvm对isKindOfClass进行了优化。
断点进行调试,Debug-> Debug Workflow -> Always show Disassembly 显示汇编,找到了objc_opt_isKindOfClass方法,断点打进去试试
果然断住了
看到这里,基本就了解了,答案如下