类的方法的归属
一、class_getInstanceMethod
看以下代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
BKPerson *person = [BKPerson alloc];
Class pClass = object_getClass(person);
logInstanceMethodFromClass(pClass);
}
return 0;
}
void logInstanceMethodFromClass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(pClass, @selector(sayDad));
Method method2 = class_getInstanceMethod(metaClass, @selector(sayDad));
Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
BKLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
可以看出来打印的是什么结果吗?
首先,Class pClass = object_getClass(person);
获取到了BKPerson
类,传入logInstanceMethodFromClass
方法中,
const char *className = class_getName(pClass);
这一步获取到BKPerson
类的类名的常量字符串“BKPerson”
,用以传入Class metaClass = objc_getMetaClass(className);
获取到BKPerson
类的元类。
它的源码定义是:
Class objc_getMetaClass(const char *aClassName)
{
Class cls;
if (!aClassName) return Nil;
cls = objc_getClass (aClassName);
if (!cls)
{
_objc_inform ("class `%s' not linked into application", aClassName);
return Nil;
}
return cls->ISA();
}
也就是如果aClassName
为nil
是就返回Nil
,否则,objc_getClass
获取到当前类放在cls
,如果cls
是空的话,就返回Nil
,否则cls->ISA()
返回类对象的isa
保存的元类。如果不懂的,可以看下这篇文章iOS底层之类的结构分析对ISA
的解释。
这一步相信大部分人都能看出来获取到了元类。
重点在于class_getInstanceMethod
这个方法做了什么。
我们先看看这个sayDad
、sayHappy
方法的定义。
@interface BKPerson : NSObject
- (void)sayDad;
+ (void)sayHappy;
@end
@implementation BKPerson
- (void)sayDad{
NSLog(@"BKPerson say : Dad~");
}
+ (void)sayHappy{
NSLog(@"BKPerson say : I'm happy");
}
@end
区别是- (void)sayDad;
是一个类的实例方法,+ (void)sayHappy;
是类的类方法。
我们再从class_getInstanceMethod
的方法的源码看下,这个方法是怎么实现的。
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);
}
if (!cls || !sel) return nil;
如果传入的类或者方法编号为空,则返回nil
,显然我们这里不是空的。重点在于这一句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;
}
可以看到重点在于这一行代码while (cls && ((m = getMethodNoSuper_nolock(cls, sel))) == nil) { cls = cls->superclass; }
,查看条件语句的方法:
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;
}
可以看到条件只要是 类存在 并且 在类的方法列表里没找到这个方法编号时,则会查找其父类的方法列表,循环下去,直到找到时返回方法,或者找不到时返回nil
,又或者找到根类NSObject的父类nil
,则不满足条件,也返回nil
。
而这里我们需明确的一点是类的实例方法是在类的方法列表里,而类对象的方法(类方法)是在元类的方法列表里,所以class_getInstanceMethod
方法只能从类的方法列表,或者父类的方法列表里查找,只能找到类的实例方法,而无法查找到类方法。
这时候我们看回这道题。
1.
Method method1 = class_getInstanceMethod(pClass, @selector(sayDad));
- 由于
sayDad
为实例方法,pClass
为BKPerson
,所以可以找到方法的地址。
2.
Method method2 = class_getInstanceMethod(metaClass, @selector(sayDad));
- 由于
sayDad
为实例方法,metaClass
为BKPerson
的元类,所以无法在元类里找到实例方法的地址。地址为空。
3.
Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
- 由于
sayHappy
为类方法,pClass
为BKPerson
,所以无法在类里找到类方法的地址。地址为空
4.
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
- 由于
sayHappy
为类方法,metaClass
为BKPerson
的元类,所以在元类里可以找到类方法的地址。
打印这4个方法的地址
0x1000031b0-0x0-0x0-0x100003148
二、class_getClassMethod
void logClassMethodFromClass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getClassMethod(pClass, @selector(sayDad));
Method method2 = class_getClassMethod(metaClass, @selector(sayDad));
Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
// 元类 为什么有 sayHappy 类方法 0 1
//
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
BKLog(@"%s-%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_getInstanceMethod
,那么重点在于cls->getMeta()
做了什么事。
查看源码
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
可以看到当这个类是元类时,则返回自己,否则返回类的isa
指向的元类。
对返回的元类使用class_getInstanceMethod
查找类的实例方法列表,而我们知道类的类方法是在其元类的实例方法列表里。说明class_getClassMethod
,只要传入的参数是BKPerson
的元类,和BKPerson
的类方法时,才能找到方法地址。
看回上面的题可知
1.
Method method1 = class_getClassMethod(pClass, @selector(sayDad));
-
pClass
是BKPerson类,getMeta
返回的是BKPerson
类的元类。sayDad
是BKPerson的实例方法,所以在BKPerson
的元类方法列表里是找不到BKPerson
的实例方法的,返回方法地址为空。
2.
Method method2 = class_getClassMethod(metaClass, @selector(sayDad));
-
metaClass
是BKPerson的元类,getMeta
返回的是自己,sayDad
是BKPerson的实例方法,所以在元类metaClass
的实例方法列表里是找不到BKPerson
的实例方法的,返回方法地址为空。
3.
Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
-
pClass
是BKPerson类,getMeta
返回的是BKPerson
类的元类。sayHappy
是BKPerson
的类方法,存储在其元类的实例方法列表中,所以可以找到方法的地址。
4.
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
-
metaClass
是BKPerson类的元类,getMeta
返回的是自己。sayHappy
是元类的实例方法,可以找到方法地址。
打印结果为:
0x0-0x0-0x100003148-0x100003148
三、class_getMethodImplementation
执行以下代码
void logClass_IMPFromClass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
// - (void)sayDad;
// + (void)sayHappy;
IMP imp1 = class_getMethodImplementation(pClass, @selector(sayDad));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayDad));
IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));
IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));
NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
NSLog(@"%s",__func__);
}
那么打印结果又是如何?
class_getMethodImplementation
这个方法在苹果文档里是这样的
大概意思为class_getMethodImplementation
可能比method_getImplementation(class_getInstanceMethod(cls, name))
快。
返回的函数指针可以是运行时内部的函数,而不是实际的方法实现。例如,如果类的实例不响应选择器,则返回的函数指针将成为运行时消息转发机制的一部分。
查看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;
}
如果查找函数的实现地址没有找到,则会通过消息转发机制返回函数指针。
那么上面的题目可以解答:
1.
IMP imp1 = class_getMethodImplementation(pClass, @selector(sayDad));
-
pClass
为BKPerson,sayDad为其实例方法,所以可以找到其方法实现,直接返回函数指针地址。
2.
IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayDad));
-
metaClass
为BKPerson的元类,sayDad为BKPerson的实例方法,所以无法在元类中找到其方法实现,通过消息转发返回。
3.
IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));
-
pClass
为BKPerson,sayHappy为其类方法,存储在其元类的实例方法列表中。所以无法在BKPerson方法列表找到其实现,通过消息转发返回。
4.
IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));
-
metaClass
为BKPerson的元类,sayHappy为BKPerson的元类的实例方法,所以可以在元类的方法列表中找到其方法实现,直接返回函数指针地址。
所以打印结果为:
0x100001d10-0x7fff66fe8580-0x7fff66fe8580-0x100001d40
通过上面获取方法地址的3个函数,可以得出总结:
-
class_getInstanceMethod
方法只能从类的方法列表,或者父类的方法列表里查找到,只可能找到当前类的实例方法,而无法查找到类方法。 -
class_getClassMethod
方法只能从当前类的元类的方法列表中查找类方法,而无法找到当前类的实例方法。 -
class_getMethodImplementation
方法查找函数的实现地址,如果没有找到,则会通过消息转发机制返回。
类和对象的归属:iskindOfClass & isMemberOfClass
BOOL cls1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //
BOOL cls2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //
BOOL cls3 = [(id)[BKPerson class] isKindOfClass:[BKPerson class]]; //
BOOL cls4 = [(id)[BKPerson class] isMemberOfClass:[BKPerson class]]; //
NSLog(@"\n cls1 :%hhd\n cls2 :%hhd\n cls3 :%hhd\n cls4 :%hhd\n",cls1,cls2,cls3,cls4);
BOOL obj1 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; //
BOOL obj2 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; //
BOOL obj3 = [(id)[BKPerson alloc] isKindOfClass:[BKPerson class]]; //
BOOL obj4 = [(id)[BKPerson alloc] isMemberOfClass:[BKPerson class]]; //
NSLog(@"\n obj1 :%hhd\n obj2 :%hhd\n obj3 :%hhd\n obj4 :%hhd\n",obj1,obj2,obj3,obj4);
比较上面的方法,得出打印的信息。
首先可以发现,上面4个方法是类方法,下面4个方法是实例方法。
从源码出发
+ (BOOL)isKindOfClass
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
可以看出,这个for
循环语句里,逻辑是这样的:
Class tcls = self->ISA()
获取当前类的isa
指向的元类,赋值到tcls
;
tcls
不为空时,如果tcls == cls
为true
,返回YES
,否则判断tcls
的父类是否等于cls
,等于返回YES
,不等于则继续判断其父类直到根类NSObject
,再到nil
时则不满足循环条件,返回NO
。
父类的继承关系是:父类->根类NSObject
->nil
。
所以,只要self
的元类(或其self
的元类的父类),等于cls
则为真,否则为假。
对比顺序是:当前类的元类->当前类的元类的父类->NSObject
->nil
== 传入类?
+ (BOOL)isMemberOfClass
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
只有self
当前类的元类是cls
,则为真,否则为假。
对比顺序是:当前类的元类 == 传入类?
- (BOOL)isKindOfClass
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
这个for
循环语句里,逻辑是这样的:
Class tcls = [self class]
获取当前对象的类tcls
;
如果类tcls
为nil
,则返回NO
;
如果类不为nil
,当类tcls
等于cls
类,返回YES
,否则,将tcls
的父类赋值给tcls
,重新执行以上逻辑。
所以,只要self
当前对象的类(或其父类)是cls
类,则为真,否则为假。
对比顺序是:当前对象的类->父类->根类NSObject
->nil
== 传入类?
- (BOOL)isMemberOfClass
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
只有self
当前对象的类是cls
,则为真,否则为假。
对比顺序是:当前对象的类 == 传入类?
而+ (Class)class
和- (Class)class
也是由区别的
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
要注意+ (Class)class
返回的是自身。- (Class)class
返回的是当前对象的类。
回到题干
- 类的对比为
BOOL cls1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //
左边:NSObject
的元类是根元类NSObject
,根元类NSObject
的父类是NSObject
;
右边:NSObject
自身;
所以结果为1
。
BOOL cls2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //
左边:NSObject
的元类是根元类NSObject
;
右边: NSObject
自身;
结果为0
。
BOOL cls3 = [(id)[BKPerson class] isKindOfClass:[BKPerson class]]; //
左边:BKPerson
的元类是元类BKPerson
,元类BKPerson
的父类是根元类NSObject
,根元类NSObject
的父类是NSObject
,NSObject
的父类是nil
;
右边: BKPerson
自身;
都不等于,结果为0
。
BOOL cls4 = [(id)[BKPerson class] isMemberOfClass:[BKPerson class]]; //
左边:BKPerson
的元类是元类BKPerson
;
右边: BKPerson
自身;
不等于,结果为0
。
NSLog(@"\n cls1 :%hhd\n cls2 :%hhd\n cls3 :%hhd\n cls4 :%hhd\n",cls1,cls2,cls3,cls4);
打印结果为
cls1 :1
cls2 :0
cls3 :0
cls4 :0
- 而对象的对比为
BOOL obj1 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; //
左边:NSObject
对象的类是NSObject
;
右边: NSObject
自身;
等于,结果为1
。
BOOL obj2 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; //
左边:NSObject
对象的类是NSObject
;
右边: NSObject
自身;
等于,结果为1
。
BOOL obj3 = [(id)[BKPerson alloc] isKindOfClass:[BKPerson class]]; //
左边:BKPerson
对象的类是BKPerson
;BKPerson
类的父类是NSObject
,NSObject
的父类是nil
;
右边: BKPerson
自身;
等于,结果为1
。
BOOL obj4 = [(id)[BKPerson alloc] isMemberOfClass:[BKPerson class]];
左边:BKPerson
对象的类是BKPerson
;
右边: BKPerson
自身;
等于,结果为1
。
NSLog(@"\n obj1 :%hhd\n obj2 :%hhd\n obj3 :%hhd\n obj4 :%hhd\n",obj1,obj2,obj3,obj4);
打印结果为
obj1 :1
obj2 :1
obj3 :1
obj4 :1
然而,我们看源码以为代码执行过程真的是按照以上的方法执行的,这却是一个很大的坑点。虽然最后的结果是正确的。但是通过跟进程序的执行,可以发现,无论- (BOOL)isKindOfClass
还是+ (BOOL)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);
}
主要也是执行for
循环里的语句,如果传入的obj
是类的实例对象,则对比类(或父类、NSObject
、nil
)与传入的类是否一致,如果传入的obj
是类,则获取到返回类的元类(或者元类的父类、根元类NSObject
、或NSObject
、或nil
)。
为什么会走这个方法呢?而不走isKindOfClass
?
原因是编译器优化了执行的效率。
以上,如有疑虑或欠妥之处,欢迎指出!