利用runtime调用私有API

最近碰到一个需要,在登录异常的情况下,需要走账号验证流程,等到验证码等等验证通过了以后,就要进入要首页,但是拿到用户信息到进入首页这个过程的代码有点多,我当时是写成一个方法放在登录页面的,如果直接把所有代码都拷贝过来当然可以,但是有点麻烦,我想直接让界面退回到登录页,然后调用它的私有API,处理登录完成到进入首页的这部分逻辑。

方法一 (最简单的方法)

- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

这三个方法都可以调用私有API,区别就是SEL带的参数多少的问题,后面的withobjcect就是这个selector带的参数,分别是带0个参数,带一个参数,带两个参数
例子:

#import 

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject

@end

NS_ASSUME_NONNULL_END

#import "Person.h"

@implementation Person
//  无参数
- (void)noArg
{
    NSLog(@"no arg");
}
//  一个参数
- (void)oneArg:(NSString *)oneage
{
    NSLog(@"one arg = %@",oneage);
}
//  两个参数
- (void)twoArg:(NSString *)arg1 arg2:(NSNumber *)arg2
{
    NSLog(@"arg1 = %@,arg2 = %@",arg1,arg2);
}


@end

// 执行
    Person *p = [Person new];
    [p performSelector:@selector(noArg)];
    [p performSelector:@selector(oneArg:) withObject:@"123"];
    [p performSelector:@selector(twoArg:arg2:) withObject:@"123" withObject:@1];

// 打印
2018-07-23 12:02:27.992171+0800 privateMethod[48241:13537358] no arg
2018-07-23 12:02:27.992295+0800 privateMethod[48241:13537358] one arg = 123
2018-07-23 12:02:27.992368+0800 privateMethod[48241:13537358] arg1 = 123,arg2 = 1

至此,需求其实已经完成了,但是我记得以前看MJRefresh的时候,里面有用到msg_send()函数,理论上来说也是可以完成调用私有API的。

方法二 (msg_send())

在调用msg_send()消息发送之前,我们先打印一下这个类的所有方法看看

    unsigned int count = 0;
    Method *methods = class_copyMethodList([Person class], &count);
    for (int i = 0; i < count; i++) {
        SEL name = method_getName(methods[i]);
        NSString *methodName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
        NSLog(@"%@",methodName);
    }
// 打印
2018-07-23 12:06:04.016795+0800 privateMethod[48309:13549676] noArg
2018-07-23 12:06:04.016921+0800 privateMethod[48309:13549676] oneArg:
2018-07-23 12:06:04.017006+0800 privateMethod[48309:13549676] twoArg:arg2:

注意到,我们直接调用msg_send(),那么调用的函数是无参的,但是我们要传递参数

 * These functions must be cast to an appropriate function pointer type 
 * before being called. 

官方给的注释说在调用前,我们需要自定义一个函数指针类型去指向它。
那么假如说我要调用第三个函数,我应该这么去做

 void (*methodTwo)(id,SEL,NSString *,NSNumber *) = (void (*) (id,SEL,NSString * ,NSNumber *))objc_msgSend;
Person *p = [Person new];
            methodTwo(p,@selector(twoArg:arg2:),@"123",@2);
// 打印
2018-07-23 12:15:59.093006+0800 privateMethod[48493:13719630] arg1 = 123,arg2 = 2

需求达成

拓展 (不定参数)

假设现在新添加一个不定参数的函数

- (void)print:(NSString *)firstArg, ... NS_REQUIRES_NIL_TERMINATION {
    if (firstArg) {

        NSLog(@"%@", firstArg);

        va_list args;

        NSString *arg;

        va_start(args, firstArg);

        while ((arg = va_arg(args,id))) {
            NSLog(@"%@", arg);
        }

        va_end(args);
    }
}

其实这个方法的方法名跟只有一个参数的方法名是一样的,还是

print:

但是在定义函数指针的时候,我们还是要用...去表示省略参数

            void (*methodPrint)(id,SEL,NSString * , ...) = (void (*) (id,SEL,NSString * , ...))objc_msgSend;
            methodPrint(p,@selector(print:),@"123",@"456",nil);
// 打印
2018-07-23 12:41:46.454412+0800 privateMethod[49097:13875153] 123
2018-07-23 12:41:46.454546+0800 privateMethod[49097:13875153] 456

Demo参考地址
喜欢就给个star吧

你可能感兴趣的:(利用runtime调用私有API)