之前在某大厂iOS面试的时候,遇到了这么一道题:
有两个类,A、B,其中B继承于A,其中 A里面实现了一个类方法+(void)test
@interface A : NSObject
+ (void)test;
@end
@implementation A
+ (void)test {
NSLog(@"hello,A");
}
@end
B里面实现了一个实例方法-(void)test
@interface B : A
- (void)test;
@end
@implementation B
- (void)test{
NSLog(@"hello,B");
}
@end
然后通过下面三种方式调用 B的test方法, 问控制台会打印什么?为什么是这个结果?
1. [B test];
2. objc_msgSend(objc_getClass("B"),@selector(test));
3. IMP testFun = class_getMethodImplementation(B.class, @selector(test));
testFun(objc_getClass("B"), @selector(test));
这个题当时没答上来,后面实验了一下, 发现1、2 结果是一样的,都是调用了A的类方法,但3 不同,是调用的B的实例方法,也就是说1、2 打印的是hello A, 3打印的是hello B。
1跟2相同比较好理解,但是3为什么不一样呢?
直觉上来说应该是打印 hello A,1、2 结果也是, 但3就有点反直觉。
带着这个疑问,我专门下载并调试了objc的源码,发现1、3两种方式都会走到lookUpImpOrForward这个方法里面去,但是我们拿到的cls对象的地址却不相同
这是方式1的

这是方式3的

而仔细比较两个方法的参数,会发现方式1的inst的地址和方式3的cls地址非常接近,只差0x10,而根据objc_class继承于objc_object及类的内存布局可知,中间只差一个isa指针,这就说明了其实方式1的inst就是方式3的cls,也就是说我们通过class_getMethodImplementation拿到的IMP拿到的根本就不是类方法的实现,而是实例方法的实现。
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache;
class_data_bits_t bits;
}
后面想到OC的实例方法是保存在类的方法列表里面,而类方法是保存在元类的方法列表里面。当实例方法在当前类的方法列表里找不到时,会去他的父类的方法列表查找。同理,类方法在当前类的元类里找不到时,会去元类的父类里面查找。这样子类就能继承父类的同名方法的实现。

因为我们传入的B.class,是保存实例方法的,而真正要查找类方法,需要传入他的元类,也就是objc_getMetaClass("B"),这点从苹果官方给的提示中也可以看出来。

当我们把方式3改为下面的代码之后,就会打印Hello,A了。
···
IMP testFun = class_getMethodImplementation(objc_getMetaClass("B"), @selector(test));
testFun(objc_getMetaClass("B"), @selector(test));
···
Over
网友评论