反编译分析并模拟实现methodSignatureForSelector方法

前言

最近跟同事讨论了有关methodSignatureForSelector:的问题,大概如下:

  1. 一个ProtocolA声明了一个实例方法funcA,然后一个类ClassA声明实现这个ProtocolA,但是并没有实现方法funcA,对ClassA的实例调用methodSignatureForSelector:@selector(funcA)能否返回正确的signature?
  2. 一个类ClassB,在@interface声明了实例方法funcB,但是没有实现funcB,对ClassB的实例调用methodSignatureForSelector:@selector(funcB),能否返回正确的signature?

写个Demo验证了下,结果非常有意思,不由得好奇起来,所以深入研究了下methodSignatureForSelector:的实现,然后自己模拟实现出来。

Github地址:TTGRemakeMethodSignatureForSelector

详细的研究过程如下:

写Demo验证问题

测试类TestClass和Protocol如下:

@protocol TestProtocol <NSObject>
- (void)protocolTestFunc1;
@end

@interface TestClass : NSObject <TestProtocol> // 不实现Protocol任何方法
- (void)testFunc1; // 不实现
@end
@implementation TestClass
@end

调用methodSignatureForSelector:获取protocolTestFunc1testFunc1的methodSignature,protocolTestFunc1可以获取到,但是testFunc1就不行。

扩展到所有情况

分析整理下可能的情况:

对于一个类:

  1. 方法没有声明,也没有实现
  2. 方法有声明,但是没有实现
  3. 方法没有声明,但是有实现
  4. 实例方法和类方法都有上面1、2、3的情况

对于一个Protocol:

  1. 方法有声明,但是没有实现
  2. 实例方法和类方法
  3. required和optional方法

经过验证,发现:

  1. 对于类,只要@implementation里实现了方法,不管@interface是否有声明,都可以返回methodSignature
  2. 对于Protocol声明的方法,不管实例方法还是类方法,不管required还是optional的,都可以返回methodSignature

所以,有必要进一步研究methodSignatureForSelector:的实现。

从Runtime源码入手

methodSignatureForSelector:NSObject的方法,所以想知道原理,第一步当然就是翻看Runtime的源码,看看有没有实现,最新的objc4-709源码,实现如下:

// From objc4-709 NSObject.mm
// Replaced by CF (returns an NSMethodSignature)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    _objc_fatal("-[NSObject methodSignatureForSelector:] "
                "not available without CoreFoundation");
}

可见,Runtime源码是没有实现的,要到CoreFoundation里面找。

找CoreFoundation源码

接着Google CoreFoundation源码,找到https://opensource.apple.com/source/CF/CF-1153.18/,怎么都找不到有关methodSignatureForSelector:的实现,难道要就此打住?

反编译大法

既然找不到源码,那就只好祭出反编译大法,直接上汇编=。=

找到CoreFoundation动态库

CoreFoundation是在程序运行时链接上去的动态库,属于系统动态库的一部分,所以首先要找到CoreFoundation动态库的二进制代码文件,命令行:

sudo -i  
find / -name CoreFoundation.framework  

搜到的结果很多,以iPhone模拟器的为例:

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/CoreFoundation.framework

打开CoreFoundation.framework文件夹,CoreFoundation文件就是要反编译的文件。

Hopper Disassembler反编译

用Hopper Disassembler打开CoreFoundation文件,在Labels搜索methodSignatureForSelector:,就能找到对应的汇编实现代码,如下:

Hopper翻译的代码如下:

void * +[NSObject methodSignatureForSelector:](void * self, void * _cmd, void * arg2) {  
    rdi = self;
    rbx = arg2;
    if ((rbx != 0x0) && (___methodDescriptionForSelector(object_getClass(rdi), rbx) != 0x0)) {
            rax = [NSMethodSignature signatureWithObjCTypes:rdx];
    }
    else {
            rax = 0x0;
    }
    return rax;
}

可发现,关键在于___methodDescriptionForSelector:方法。

___methodDescriptionForSelector:方法的实现

进一步看___methodDescriptionForSelector:的实现。

汇编代码、Hopper翻译的代码都很长,循环也不见了,全是goto:

经过反复校对整理(排除Hopper的错误=。=),可以整理出主要的流程如下图所示:

根据流程,可以得出,___methodDescriptionForSelector:方法:

  1. class_copyProtocolListprotocol_getMethodDescription方法,检查是否有对应的selector,不管是否实现
  2. class_getInstanceMethod检查selector是否有implementation,注意,是implementation,跟是否声明了没有关系

所以,符合前面的结论。

模拟实现

流程都有了,不如更进一步,自己实现一个methodSignatureForSelector:方法出来~

模拟实现___methodDescriptionForSelector:

按照流程,实现代码如下:

// 模拟实现___methodDescriptionForSelector方法:
struct objc_method_description ttg_MethodDescription(Class class, SEL sel) {  
    // 结果
    struct objc_method_description description = (struct objc_method_description){NULL, NULL};
    Class currentClass = class;

    while (currentClass && description.name == NULL) {
        // 获取Protocol列表
        unsigned int count = 0;
        __unsafe_unretained Protocol **protocols = class_copyProtocolList(currentClass, &count);

        // 遍历所有Protocol
        for (unsigned int i = 0; i < count; i++) {
            // Required 方法
            description = protocol_getMethodDescription(protocols[i], sel, YES, class_isMetaClass(currentClass) ^ 1);
            if (description.name != NULL) {
                break;
            }

            // Optional 方法
            description = protocol_getMethodDescription(protocols[i], sel, NO, class_isMetaClass(currentClass) ^ 1);
            if (description.name != NULL) {
                break;
            }
        }

        // 释放
        free(protocols);

        // 找到、返回
        if (description.name != NULL) {
            return description;
        }

        // 获取父类,继续
        currentClass = class_getSuperclass(currentClass);
    }

    // 获取实例方法
    Method method = class_getInstanceMethod(class, sel);
    if (method) {
        // 找到实例方法
        return *method_getDescription(method);
    } else {
        // 返回空
        return (struct objc_method_description){NULL, NULL};
    }
}
  1. 用description记录迭代中的结果,只要找到就返回
  2. 用currentClass记录每次迭代的类class
  3. Protocol的@required和@optional方法都要检查
  4. 注意释放Protocol数组
  5. 最后才用class_getInstanceMethod获取类本身的实现

模拟实现methodSignatureForSelector:

用Category给NSObject加两个方法:

@interface NSObject (TTGRemakeMethodSignatureForSelector)
- (NSMethodSignature *)ttg_methodSignatureForSelector:(SEL)sel;
+ (NSMethodSignature *)ttg_methodSignatureForSelector:(SEL)sel;
@end

实现基本一样:

@implementation NSObject (TTGRemakeMethodSignatureForSelector)

- (NSMethodSignature *)ttg_methodSignatureForSelector:(SEL)sel {
    struct objc_method_description description = ttg_MethodDescription([self class], sel); // 注意!是获取实例的class
    if (sel && description.types != NULL) {
        return [NSMethodSignature signatureWithObjCTypes:description.types];
    } else {
        return nil;
    }
}

+ (NSMethodSignature *)ttg_methodSignatureForSelector:(SEL)sel {
    struct objc_method_description description = ttg_MethodDescription(object_getClass(self), sel); // 必须用object_getClass获取metaClass
    if (sel && description.types != NULL) {
        return [NSMethodSignature signatureWithObjCTypes:description.types];
    } else {
        return nil;
    }
}

@end

注意要获取正确的"class":

  1. 对于对象实例,从[obj class]object_getClass(obj)结果一致,都是对象的类
  2. 对于类,要获取元类,也就是metaClass,只能用object_getClass(class)

验证模拟实现

验证自己模拟实现的methodSignatureForSelector:和系统的是否一致:

// 测试equal相等
NSMethodSignature *signature1 = [test methodSignatureForSelector:@selector(testFunc1)];  
NSMethodSignature *signature2 = [test ttg_methodSignatureForSelector:@selector(testFunc1)];  
NSLog(@"signature1 == signature2: %d", [signature1 isEqual:signature2]);

// Invoke实例方法
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[test ttg_methodSignatureForSelector:@selector(testFunc1)]];  
invocation.target = test;  
invocation.selector = @selector(testFunc1);  
[invocation invoke];

// Invoke类方法
invocation = [NSInvocation invocationWithMethodSignature:[TestClass ttg_methodSignatureForSelector:@selector(staticTestFunc1)]];  
invocation.target = [TestClass class];  
invocation.selector = @selector(staticTestFunc1);  
[invocation invoke];

结果跟系统实现的行为一致,更多Case见Github的Demo。

总结

从两个小问题出发,到最后通过反编译、模拟实现,终于搞清楚了methodSignatureForSelector:的原理、流程、各种隐藏特性!哈哈~~过瘾~~