通过前面的分析,我们对于类的继承以及方法的存储位置有了一定的了解,那么这篇文章我们主要来分析两个比较常见的面试题,具体如下:
关于iskindOfClass & isMemberOfClass
的理解
//-----使用 iskindOfClass & isMemberOfClass 类方法
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL re3 = [(id)[LYPerson class] isKindOfClass:[LYPerson class]];
BOOL re4 = [(id)[LYPerson class] isMemberOfClass:[LYPerson class]];
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
//------iskindOfClass & isMemberOfClass 实例方法
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
BOOL re7 = [(id)[LYPerson alloc] isKindOfClass:[LYPerson class]];
BOOL re8 = [(id)[LYPerson alloc] isMemberOfClass:[LYPerson class]];
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
既然是分析底层原理,那么在给出上面的结果前,我们先来通过源码来分析下isKindOfClass
和isMemberOfClass
的类方法和实例方法分别做了哪些事情,具体源码如下:
+ (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;
}
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
源码分析
-
+(BOOL)isKindOfClass:(Class)cls
依次将
cls
与self(类)
的元类
,父元类(如果有父类)
,根元类
,根类
,nil
进行对比 -
-(BOOL)isKindOfClass:(Class)cls
依次将
cls
与self(对象)
的类
,父类
,根类
,nil
进行对比 -
+(BOOL)isMemberOfClass:(Class)cls
将
self(类)
的元类
与传入类cls
进行对比 -
-(BOOL)isMemberOfClass:(Class)cls
将
self(对象)
的类
与传入类cls
进行对比
断点调试
通过上面的源码分析完,我们其实已经差不多可以得出结论了,但是为了验证一下,我们还是对源码进行断点调试一下(防止有坑)
果然,通过调试我们发现源码中isKindOfClass
的类方法和实例方法并没有走到(确实有坑).既然没走,那我们就只能看下具体的堆栈信息,通过堆栈信息我们发现isKindOfClass
实际走的是BOOL objc_opt_isKindOfClass(id obj, Class otherClass)
方法,如下图:
接下来,我们来看下objc_opt_isKindOfClass
的源码是怎样的
// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
if (slowpath(!obj)) return NO;
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);
}
通过注释其实我们也可以很清楚的知道,调用[obj isKindOfClass]
的时候会改为调用objc_opt_isKindOfClass
,这里之所以会这样也是跟我们最开始分析alloc
方法时一样的道理,编译器llvm
编译时做了相应的优化
所以isKindOfClass
的实际流程图如下
至此,我们已经通过源码和实际的断点调试对iskindOfClass & isMemberOfClass
进行了分析,那么对于最初我们给出的问题,你有答案了吗?让我们一起来看下吧
结果
-
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
-
NSObject类
与NSObject元类
对比,不相等,继续下一步 -
NSObject类
与NSObject元类
的父类即NSObject类
对比,相等,返回YES
-
-
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
-
NSObject类
与NSObject的元类
对比,不相等,返回NO
-
-
BOOL re3 = [(id)[LYPerson class] isKindOfClass:[LYPerson class]];
-
LYPerson类
与LYPerson的元类
对比,不相等,继续下一步 -
LYPerson类
与LYPerson元类
的父类即NSObject元类
对比,不相等,继续下一步 -
LYPerson类
与NSObject元类
的父类即NSObject类
对比,不相等,继续下一步 -
LYPerson类
与NSObject类
的父类即nil
对比,不相等,结束,返回NO
-
-
BOOL re4 = [(id)[LYPerson class] isMemberOfClass:[LYPerson class]];
-
LYPerson类
与LYPerson的元类
对比,不相等,返回NO
-
-
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
-
NSObject类
与NSObject对象的类
即NSObject类
对比,相等,返回YES
-
-
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
-
NSObject类
与NSObject对象的类
对比,相等,返回YES
-
-
BOOL re7 = [(id)[LYPerson alloc] isKindOfClass:[LYPerson class]];
-
LYPerson类
与LYPerson对象的类
对比,相等,返回YES
-
-
BOOL re8 = [(id)[LYPerson alloc] isMemberOfClass:[LYPerson class]];
-
LYPerson类
与LYPerson对象的类
对比,相等,返回YES
-
方法归属
void lyInstanceMethod_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 lyClassMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method5 = class_getClassMethod(pClass, @selector(sayHello));
Method method6 = class_getClassMethod(metaClass, @selector(sayHello));
Method method7 = class_getClassMethod(pClass, @selector(sayHappy));
// 元类 为什么有 sayHappy 类方法 0 1
//
Method method8 = class_getClassMethod(metaClass, @selector(sayHappy));
NSLog(@"%s-%p-%p-%p-%p",__func__,method5,method6,method7,method8);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
LYPerson *person = [LYPerson alloc];
Class pClass = object_getClass(person);
lyInstanceMethod_classToMetaclass(pClass);
lyClassMethod_classToMetaclass(pClass);
NSLog(@"Hello, World!");
}
return 0;
}
同样的,我们还是先来看下源码实现
源码分析
@interface LYPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;
@end
@implementation LYPerson
- (void)sayHello{
NSLog(@"LYPerson say : Hello!!!");
}
+ (void)sayHappy{
NSLog(@"LYPerson say : Happy!!!");
}
@end
Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
// This deliberately avoids +initialize because it historically did so.
// This implementation is a bit weird because it's the only place that
// wants a Method instead of an IMP.
#warning fixme build and search caches
// Search method lists, try method resolver, etc.
lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
#warning fixme build and search caches
return _class_getMethod(cls, sel);
}
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
// NOT identical to this->ISA when this is a metaclass
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
根据源码我们发现获取类方法的本质其实就是获取元类的实例方法
那么根据源码我们来分析下上面的题目,如下:
-
Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
传入的
pClass
是LYPerson
类,需要去获取selName = sayHello
的实例方法,由于我们知道实例方法存在类中,所以此处返回值method1是有值的
-
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));
传入的
metaClass
是LYPerson元类
,需要去获取selName = sayHello
的实例方法,其查找的顺序为元类 --> 根元类 --> 根类 --> nil,直到最后也没有找到,所以class_getInstanceMethod返回NULL,其method2的地址为0x0,表示未找到,所以此处返回值method2是没有值的 -
Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
传入的
pClass
是LYPerson类
,需要去获取selName = sayHappy
的实例方法,查找顺序为LYPerson类 --> 根类 --> nil,也没有找到sayhello实例方法,返回NULL,所以method3的地址为0x0,表示未找到,所以此处返回值method3是没有值的 -
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
传入的
metaClass
是LYPerson元类
,需要去获取selName = sayHappy
的实例方法,首先在LYPerson元类中查找,发现有sayHappy的实例方法,主要是因为类对象的类方法存储在元类中,类方法在元类中是实例方法,然后返回查找到的实例方法,所以method4的地址为0x100003148,表示找到了指定的实例方法,所以此处返回值method4是有值的 -
Method method5 = class_getClassMethod(pClass, @selector(sayHello));
传入的
pClass
是LYPerson类
,需要去获取selName = sayHello
的类方法,首先判断 LYPerson类 是否是元类,此时不是,返回LYPerson的元类,然后在元类中查找 sayhello实例方法。查找顺序如下:元类 --> 根元类 --> 根类 --> nil,最后返回NULL,所以此处返回值method5是没有值的 -
Method method6 = class_getClassMethod(metaClass, @selector(sayHello));
传入的
metaClass
是LYPerson元类
,需要去获取selName = sayHello
的类方法,首先判断 LYPerson元类 是否是元类,此时是,直接返回元类,然后在元类中查找 sayhello实例方法,发现并没有找到,返回NULL,所以此处返回值method6是没有值的 -
Method method7 = class_getClassMethod(pClass, @selector(sayHappy));
传入的
pClass
是LYPerson类
,需要去获取selName = sayHappy
的类方法,首先判断 LYPerson类 是否是元类,此时不是,返回LYPerson的元类,然后在元类中查找 sayHappy实例方法,发现有这个实例方法,直接返回找到的实例方法,所以此处返回值method7是有值的 -
Method method8 = class_getClassMethod(metaClass, @selector(sayHappy));
传入的
metaClass
是LYPerson元类
,需要去获取selName = sayHappy
的类方法,首先判断 LYPerson元类 是否是元类,此时是,直接返回元类,然后在元类中查找 sayHappy实例方法,发现有这个实例方法,直接返回找到的实例方法,所以此处返回值method5是有值的
从上面的分析结果中,我们就发现了一个问题 method8也不为NULL,此时就很疑惑:元类中为什么会有 sayHappy 类方法?
主要还是因为class_getClassMethod方法在元类的判断导致的,这是苹果人为制造的 递归终止条件,目的就是防止无限次递归
具体代码如下:
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}