美文网首页
从一道Objective-C的方法调用面试题说起

从一道Objective-C的方法调用面试题说起

作者: FingerStyle | 来源:发表于2022-12-16 00:53 被阅读0次

之前在某大厂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的


Snip20221217_4.png

这是方式3的


Snip20221216_2.png
而仔细比较两个方法的参数,会发现方式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的实例方法是保存在类的方法列表里面,而类方法是保存在元类的方法列表里面。当实例方法在当前类的方法列表里找不到时,会去他的父类的方法列表查找。同理,类方法在当前类的元类里找不到时,会去元类的父类里面查找。这样子类就能继承父类的同名方法的实现。

image.png

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

image.png

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

Over

相关文章

网友评论

      本文标题:从一道Objective-C的方法调用面试题说起

      本文链接:https://www.haomeiwen.com/subject/dprdqdtx.html