方法归属及isKindOfClass、isMemberOfCla
本文将会对两个题目进行探究。
方法归属问题
开始先来看一个题目。
void lgInstanceMethod(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));
NSLog(@"instance method: %p - %p - %p - %p", method1, method2, method3, method4);
}
void lgClassMethod(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));
NSLog(@"class Method: %p - %p - %p - %p", method1, method2, method3, method4);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
Class pClass = Animal.class;
lgInstanceMethod(pClass);
lgClassMethod(pClass);
}
return 0;
}
Animal的定义如下:
@interface Animal : NSObject
@end
@implementation Animal
- (void)eat {
NSLog(@"eat~~~~");
}
+ (void)run {
NSLog(@"run~~~~");
}
@end
问instance method和class method分别会打印什么?
要解决这个问题,我们需要看class_getInstanceMethod
和class_getClassMethod
的定义。
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.
Method meth;
meth = _cache_getMethod(cls, sel, _objc_msgForward_impcache);
if (meth == (Method)1) {
// Cache contains forward:: . Stop searching.
return nil;
} else if (meth) {
return meth;
}
// Search method lists, try method resolver, etc.
lookUpImpOrForward(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
meth = _cache_getMethod(cls, sel, _objc_msgForward_impcache);
if (meth == (Method)1) {
// Cache contains forward:: . Stop searching.
return nil;
} else if (meth) {
return meth;
}
return _class_getMethod(cls, sel);
}
继续跟进找答案
static Method _class_getMethod(Class cls, SEL sel)
{
mutex_locker_t lock(runtimeLock);
return getMethod_nolock(cls, sel);
}
static method_t *
getMethod_nolock(Class cls, SEL sel)
{
method_t *m = nil;
runtimeLock.assertLocked();
// fixme nil cls?
// fixme nil sel?
ASSERT(cls->isRealized());
while (cls && ((m = getMethodNoSuper_nolock(cls, sel))) == nil) {
cls = cls->superclass;
}
return m;
}
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
auto const methods = cls->data()->methods();
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
// <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
// caller of search_method_list, inlining it turns
// getMethodNoSuper_nolock into a frame-less function and eliminates
// any store from this codepath.
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
}
return nil;
}
最后找到关键代码
auto const methods = cls->data()->methods();
而在上一篇文章中我们知道,类对象的methods中存放的是实例方法,元类的methods存放的是类方法,所以
instance method打印应该是method1,method4有值,method2,method3为nil。
接着看class_getClassMethod
的定义
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
可以看到它的内部实现也是调用了class_getInstanceMethod
,不同是它的传入参数是cls->getMeta()
,查看它的定义,
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
显然,如果传入的当前类是类对象,那么就返回类isa指向的元类对象,如果当前类是元类,那么返回自己本身。所以根据类方法存放在元类中,所以
class method打印应该是method1,method2为nil,method3,method4有值。
验证结果
2020-09-16 12:21:42.774155+0800 isa_test[93360:4177590] instance method: 0x100002170 - 0x0 - 0x0 - 0x100002108
2020-09-16 12:21:42.774206+0800 isa_test[93360:4177590] class Method: 0x0 - 0x0 - 0x100002108 - 0x100002108
isKindOfClass和isMemberOfClass
接下来是这个题目
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL re3 = [(id)[Animal class] isKindOfClass:[Animal class]];
BOOL re4 = [(id)[Animal class] isMemberOfClass:[Animal class]];
NSLog(@" 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)[Animal alloc] isKindOfClass:[Animal class]];
BOOL re8 = [(id)[Animal alloc] isMemberOfClass:[Animal class]];
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
同样我们在源码objc4-7.8.1中查找答案。
首先是类方法的isKindOfClass
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
可以看到类方法isKindOfClass
会先通过isa指针找到根元类,先比较元类和cls,而后在依次通过superclass找父元类遍历比较,而通过isa走位图(详见类结构探究(一)-- isa与superclass的指向),可以知道元类的superclass最终会走到根元类,而根元类的superclass指向了根类NSObject,所以,re1的结果为YES,re3结果为NO。
再来看类方法的isMemberOfClass
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
它是直接通过类的isa指向与目标class进行比较,当isa指向的是元类,元类和类并不相等,所以re2和re4为NO。
在来看实例方法的isKindOfClass
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
实例方法的isKindOfClass会先找到类进行比较,无法匹配在通过superclass遍历父类进行匹配知道根类NSObject,所以re5和re7都为YES,
而实例方法的isMemberOfClass
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
只是简单当前类比较,说re6和re7也为YES。
根据探索,结论为
- re1: YES
- re2: NO
- re3: NO
- re4: NO
- re5: YES
- re6: YES
- re7: YES
- re8: YES
验证结果
2020-09-16 14:52:08.522482+0800 isa_test[96542:4235809] re1 :1
re2 :0
re3 :0
re4 :0
2020-09-16 14:52:08.522546+0800 isa_test[96542:4235809] re5 :1
re6 :1
re7 :1
re8 :1
思考
这两道看似简单,但实际上是对类结构已经isa走位的一个考察,另外,我们无法通过方法名臆断方法执行结果,所以学习最好的途径还是多阅读源码,多思考。