上一篇我们通过研究类的结构,解析了类的属性,成员变量,实例方法和类方法所在的位置。本篇我会从另一个角度探究类的类方法的储存位置以及isKindOf和isMemberOf的解析。
类方法的归属问题
上一节我抄了近路,直接使用著名的一句话:”类方法是元类的实例方法“找到了类方法+ (void)classMethod;
。今天我会使用苹果api来找到类方法的存储位置。
前期准备工作
首先介绍一下会用到的api:
class_getName() // 获取char类型类名称
objc_getMetaClass() // 获取类的元类
class_getInstanceMethod() // 判断类中是否有传入的实例方法并返回方法地址
class_getClassMethod() // 判断类中是否有传入的类方法并返回方法地址
class_getMethodImplementation() // 判断类中是否有传入方法的实现Imp,并返回Imp地址
工程还是用我们上一篇的工程,创建一个实例方法,一个类方法,并实现一下:
1.通过打印方法列表验证结论
通过打印类的方法名来验证一下我们上一篇的结论:类方法不在当前类的方法列表中,而是在元类的实例方法列表中:
void fcObjc_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));
FCLog(@"Method, name: %@", key);
}
free(methods);
}
调用方法:
// 当前类FCPerson
FCPerson *person = [FCPerson alloc];
Class pClass = object_getClass(person);
// FCPerson的元类
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
// FCPerson的方法列表
fcObjc_copyMethodList(pClass);
// FCPerson元类的方法列表
NSLog(@"*************");
fcObjc_copyMethodList(metaClass);
输出结果:
验证成功:类方法不存在类中,而是存在元类中,类方法是元类的实例方法
2.通过class_getInstanceMethod()
输出类/元类中是否含有对应实例方法来验证:
void fcInstanceMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
// 类/元类中是否含有实例方法instanceMethod
Method method1 = class_getInstanceMethod(pClass, @selector(instanceMethod));
Method method2 = class_getInstanceMethod(metaClass, @selector(instanceMethod));
// 类/元类中是否含有类方法classMethod
Method method3 = class_getInstanceMethod(pClass, @selector(classMethod));
Method method4 = class_getInstanceMethod(metaClass, @selector(classMethod));
FCLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
调用&输出:
如图:FCPerson中是否含有instanceMethod :有
FCPerson中是否含有classMethod :没有
FCPerson元类中是否含有instanceMethod :没有
FCPerson元类中是否含有classMethod :有,验证成功
3.通过class_getClassMethod()
输出类/元类中是否含有对应的类方法来验证:
void fcClassMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getClassMethod(pClass, @selector(instanceMethod));
Method method2 = class_getClassMethod(metaClass, @selector(instanceMethod));
Method method3 = class_getClassMethod(pClass, @selector(classMethod));
Method method4 = class_getClassMethod(metaClass, @selector(classMethod));
FCLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
调用&输出:
如图:FCPerson中是否含有类方法instanceMethod :没有
FCPerson的元类中是否含有类方法instanceMethod :没有
FCPerson中是否含有类方法classMethod :有
FCPerson的元类中是否含有类方法classMethod :有
为什么FCPerson的元类中也含有类方法classMethod??
查看class_getClassMethod
源码:
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
// 获取类方法实际上获取的是元类的实例方法
return class_getInstanceMethod(cls->getMeta(), sel);
}
Class getMeta() {
// 获取元类时会先判断传入的是否是元类,如果是元类则返回this(自己)
if (isMetaClassMaybeUnrealized()) return (Class)this;
else return this->ISA();
}
由class_getClassMethod
和其中getMeta()
的实现可以得知:获取类方法实际上获取的是元类的实例方法,而在getMeta()
获取元类时会先判断传入的是否是元类,如果是元类则返回this(自己),因此输出结果为:FCPerson的元类中是否含有类方法classMethod :有
4.分别查看实例方法和类方法在类和元类中是否含有方法实现Imp:
void fcIMP_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
IMP imp1 = class_getMethodImplementation(pClass, @selector(instanceMethod));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(instanceMethod));// 0
// sel -> imp 方法的查找流程 imp_farw
IMP imp3 = class_getMethodImplementation(pClass, @selector(classMethod)); // 0
IMP imp4 = class_getMethodImplementation(metaClass, @selector(classMethod));
NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
NSLog(@"%s",__func__);
}
调用&输出:
如图:FCPerson中是否含有实例方法instanceMethod的实现:有
FCPerson中是否含有类方法classMethod的实现:有???
FCPerson元类中是否含有实例方法instanceMethod的实现:有???
FCPerson元类中是否含有类方法classMethod的实现:有
明明FCPerson中不应该包含类方法classMethod的实现,FCPerson元类中不应该有实例方法instanceMethod的实现,但为什么结果是有的呢?仔细看下日志发现两条返回Imp地址为同一个:0x7fff6767d580。Why?
当对一个对象发送一条消息,找不到对应实现的时候会发生什么呢?没错,就是我们熟悉的消息转发流程啦,进入class_getMethodImplementation
同样可以看到,当找不到Imp的时候会返回_objc_msgForward:
IMP class_getMethodImplementation(Class cls, SEL sel) {
IMP imp;
if (!cls || !sel) return nil;
lockdebug_assert_no_locks_locked_except({ &loadMethodLock });
imp = lookUpImpOrNilTryCache(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
// Translate forwarding function to C-callable external version
if (!imp) {
return _objc_msgForward;
}
return imp;
}
因此0x7fff6767d580其实就是_objc_msgForward,我们之前的结论还是没问题滴。
至此,我们通过多个方式验证了我们的结论:类方法不存在类中,而是存在该类的元类中,类方法是元类的实例方法。
补充知识:为什么要有元类?我用很简单的例子来解释说明一下,看代码:
- (void)saySomething;
+ (void)saySomething;
这样写有没有问题?没问题对吧,虽然方法名相同,但一个是实例方法,一个是类方法。但这只是OC层面的写法,如果还原到底层的C/C++层面,他们其实就都是函数,底层是没有实例方法和类方法的概念的,那么编译器如何区分这两个方法呢?苹果也是发现了有这个坑点,所以生成了类的元类,把类方法以实例方法的形式存在元类中,解决了这个问题。
isKindOf和isMemberOf解析###
isKindOf和isMemberOf这对方法经常出现在面试题中,那么他们具体是做什么用的,有什么含义,有什么区别?我们接下来做详细解析,首先看经典题目:
FCPerson *person = [FCPerson alloc];
NSObject *obj = [NSObject alloc];
BOOL re1 = [[NSObject class] isKindOfClass:[NSObject class]];
BOOL re2 = [obj isKindOfClass:[NSObject class]];
BOOL re3 = [[FCPerson class] isKindOfClass:[FCPerson class]];
BOOL re4 = [person isKindOfClass:[FCPerson class]];
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
BOOL re5 = [[NSObject class] isMemberOfClass:[NSObject class]];
BOOL re6 = [obj isMemberOfClass:[NSObject class]];
BOOL re7 = [[FCPerson class] isMemberOfClass:[FCPerson class]];
BOOL re8 = [person isMemberOfClass:[FCPerson class]];
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
结果如何先不说,大家可以先自己思考,我们先依次来看源码,先看isKindOf:
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
if (tcls == cls) return YES;
}
return NO;
}
isKindOfClass()
分为类方法和实例方法,两个方法内部都是循环遍历。
类方法的含义为:第一次循环先找出自己的元类,然后去跟传入类比较,如果不相等再去找元类的父类,以此类推。
实例方法含义为:第一次循环先获取自己的当前类,然后去跟传入类比较,如果不相等再去找父类,以此类推。
很容易看出类方法和实例方法的区别在于,类方法使用元类链来比较,实例方法使用的是对象本身的类链,可以参考isa的走位图,看元类和类两条链的继承关系
套用到题目中:
re1 = [[NSObject class] isKindOfClass:[NSObject class]]
:使用类方法,NSObject的元类为根元类,根元类!=NSObject,继续循环,根元类的父类为根类,根类==NSObject,结果为1;
re2 = [obj isKindOfClass:[NSObject class]]
:实例方法,objc的类为NSObject,NSObject==NSObject,结果1;
re3 = [[FCPerson class] isKindOfClass:[FCPerson class]]
:类方法,FCPerson的元类!= FCPerson,继续向父类查找,很明显不会再等于FCPerson,结果0;
re4 = [person isKindOfClass:[FCPerson class]]
:实例方法,person的类为FCPerson,第一次循环返回1;
看完isKindOf
再来看isMemberOf
,源码:
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
同样是类方法和实例方法,类方法返回元类是否等于传入类,实例方法返回当前对象的类是否等于传入类。再来套用题目:
re5 = [[NSObject class] isMemberOfClass:[NSObject class]]
:类方法,NSObject的元类为根元类,不等于NSObject,返回0;
re6 = [obj isMemberOfClass:[NSObject class]]
:实例方法,obj的类为NSObject,返回1;
re7 = [[FCPerson class] isMemberOfClass:[FCPerson class]]
:类方法,FCPerson的元类不等于FCPerson,返回0;
re8 = [person isMemberOfClass:[FCPerson class]]
:实例方法,person的类为FCPerson,返回1;
知识补充#:以上对isMemberOfClass和isKindOfClass的分析只针对于模拟器,实际上用真机调用的时候,系统会将isKindOfClass方法重定向为objc_opt_isKindOfClass
,源码缩略后如下:
objc_opt_isKindOfClass(id obj, Class otherClass)
{
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {
if (tcls == otherClass) return YES;
}
return NO;
}
return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
与isKindOfClass
方法相比较,首先获取cls的方法为Class cls = obj->getIsa();
:
objc_object::getIsa()
{
if (fastpath(!isTaggedPointer())) return ISA();
extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
uintptr_t slot, ptr = (uintptr_t)this;
Class cls;
slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
cls = objc_tag_classes[slot];
if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
cls = objc_tag_ext_classes[slot];
}
return cls;
}
getIsa()
方法中做了很多位置运算,但是只看最终的cls可以得知要么cls = objc_tag_classes[slot];
,要么cls = objc_tag_ext_classes[slot];
:
#define objc_tag_classes objc_debug_taggedpointer_classes
#define objc_tag_ext_classes objc_debug_taggedpointer_ext_classes
extern "C" {
extern Class objc_debug_taggedpointer_classes[_OBJC_TAG_SLOT_COUNT];
extern Class objc_debug_taggedpointer_ext_classes[_OBJC_TAG_EXT_SLOT_COUNT];
}
以我目前的水平其实并不能看太懂,但是只通过ext来判断,猜测objc_tag_classes []
获取的是当前类,objc_tag_ext_classes []
获取的是当前类的元类。
按照这样的猜测继续看objc_opt_isKindOfClass
源码,其实可以看出当传入的obj是类的时候,cls就是类的元类;传入obj为对象时,cls就是obj的类。然后经过了一个判断if (fastpath(!cls->hasCustomCore()))
,如果符合条件则进入到跟之前相同的循环判断,得出相同的结果;如果不符合条件则一律返回No。
对比objc_opt_isKindOfClass
和isKindOfClass
两个方法,为什么系统要重定向isKindOfClass
?看起来关键就在于判断条件if (fastpath(!cls->hasCustomCore()))
,贴出hasCustomCore
的代码:
#if FAST_CACHE_HAS_DEFAULT_CORE
bool hasCustomCore() const {
return !cache.getBit(FAST_CACHE_HAS_DEFAULT_CORE);
}
#if __LP64__
bool getBit(uint16_t flags) const {
return _flags & flags;
}
是跟缓存相关的,目前我还看不出什么情况下会不符合条件,如果以后知道了会继续补充,有详细了解的大佬也欢迎下方留言哦,我们下节见~