如何在不越狱的iOS中调用私有API

前言

原文链接:http://www.lanvsblue.top/2016/10/08/how-to-use-private-api-in-ios/

在iOS的API中,几乎每一个类都会有一部分未公开的方法,而这些方法在一般情况下是不能被开发者调用的,可以说最广为人知的就是:[UIView recursiveDescription]了。这是一个UIView的私有方法,所以在API Reference中是找不到的。它是一个神奇的方法,可以打印出所有view层次直接的关系和view的description。如果正确调用会可以输出如下log信息:
rd-example

可以试着在代码中调用这个方法:

- (void)viewDidLoad {
    [super viewDidLoad];
	//......
	//......

	[self.view recursiveDescription];

	//......
	//......
}

编译是会报错的:no visible @interface for 'UIView' declares the selector 'recursiveDescription'。那么应该如何调用像这样私有的API呢?我们先看一个简单的例子。

PS:所有本文有关的代码可以在这边下载

PS:注意!!!调用了私有API的App是不能上架AppStore的,所以请斟酌以后再调用。

Objective-C调用私有方法

要想调用私有API首先我们得学会如何在Objective-C中调用私有方法。我们来看一个例子:
这个例子包含一个类和一个main方法,文件结构如下:
file-struct

ExampleClass.h文件:

/ * 这是ExampleClass类的头文件,ExampleClass类包含:
  * 一个公有的方法,方法名为 -(void)publicMethod;
  * 一个私有的方法,方法名为 -(void)privateMethod; 在.m文件中实现
  * /

#import <Foundation/Foundation.h>

@interface ExampleClass : NSObject
-(void)publicMethod;
@end

ExampleClass.m文件:

/ * 这是ExampleClass类的实现文件,ExampleClass类包含:
  * 一个公有的方法,方法名为 -(void)publicMethod;
  * 一个私有的方法,方法名为 -(void)privateMethod; 在.m文件中实现
  * /

#import "ExampleClass.h"

@implementation ExampleClass {

}

- (void)publicMethod {
    NSLog(@"This is PUBLIC method");
}

-(void)privateMethod{
    NSLog(@"This is PRIVATE method");
}

@end

一个名为ExampleClass的类中,有一个公有方法和一个私有方法。公有方法可以直接用[Class Method]这样的形式调用,而私有方法应该怎么样调用呢?有下面两个方法:

方法一:巧用Category欺骗编译器

如果直接使用[Class Method]这样的方法调用ExampleClass类的privateMethod会报找不到此方法的错误,刚刚已经尝试过UIView类的recursiveDescription方法了。

明明是存在这个方法的,但是编译不通过。那么我们要做的就是欺骗编译器,是存在这个方法的。我们可以构造一个匿名的Category来实现,还是刚刚的ExampleClass的例子,看看怎么样用Category欺骗编译器:

main.h文件:

#import <Foundation/Foundation.h>
#import "ExampleClass.h"

@interface ExampleClass()
-(void)privateMethod;
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ExampleClass *example = [[ExampleClass alloc] init];
        
        [example publicMethod];
        
        [example privateMethod];
    }

    return 0;
}

这时候就可以编译了,查看结果:
ExampleClass-result

惊人的的发现竟然调用成功了

代码参见文件中的Example1。

方法二:使用OC的runtime特性:[NSObject performSelector:]

首先介绍两个NSObject的方法:

  • [NSObject respondsToSelector:]
  • [NSObject performSelector:]

第一个方法返回一个布尔值用来查看receiver或receiver的父类是否实现了这个selector;第二个方法发送一个消息(oc里面讲消息,其他语言为方法)给receiver,这个方法返回的就是发送的消息所返回的值。

为了证明[ExampleClass privateMethod]这个方法确实是存在的,我首先来使用第一个方法:[NSObject respondsToSelector:]:

main.h文件:

#import <Foundation/Foundation.h>
#import "ExampleClass.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ExampleClass *example = [[ExampleClass alloc] init];

        [example publicMethod];

        NSLog(@"%d",[example respondsToSelector:@selector(privateMethod)]);
    }

    return 0;
}

运行以后发现程序输出了1,证明ExampleClass确实是包含privateMethod方法的,这时候我们改一下main.h,尝试使用Objective-C的runtime特性,使用上文提到的[NSObject performSelector:]方法,调用privateMethod

main.h:

#import <Foundation/Foundation.h>
#import "ExampleClass.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ExampleClass *example = [[ExampleClass alloc] init];

        [example publicMethod];

        if([example respondsToSelector:@selector(privateMethod)]){
            [example performSelector:@selector(privateMethod)];
        }

    }

    return 0;
}

这样也能看见相同的效果。

代码参见文件中的Example2。

调用私有API

如何调用私有API就可以举一反三出来了,这里就使用方法一举个例子,这个例子在iOS中运行:

#import "ViewController.h"

@interface UIView()
-(id)recursiveDescription;
@end


@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(20,50,200,300)];
    view1.backgroundColor = [UIColor greenColor];
    [self.view addSubview:view1];
    
    UIView *view2 = [[UIView alloc] initWithFrame:CGRectMake(10,20,150,250)];
    view2.backgroundColor = [UIColor redColor];
    [view1 addSubview:view2];
    
    UIView *view3 = [[UIView alloc] initWithFrame:CGRectMake(10,20,100,200)];
    view3.backgroundColor = [UIColor blueColor];
    [view2 addSubview:view3];
    
    UIView *view4 = [[UIView alloc] initWithFrame:CGRectMake(170,20,20,100)];
    view4.backgroundColor = [UIColor grayColor];
    [view1 addSubview:view4];
    
    UIView *view5 = [[UIView alloc] initWithFrame:CGRectMake(70,50,200,50)];
    view5.backgroundColor = [UIColor brownColor];
    [view2 addSubview:view5];
    NSLog(@"%@",view5);
    
    
    //测试私有方法
    [self runPrivateMethod];
}

-(void)runPrivateMethod{
    NSLog(@"-------------------------ON TEST-------------------------");
    NSLog(@"%d",[self.view respondsToSelector:@selector(recursiveDescription)]);
    NSLog(@"%@",[self.view recursiveDescription]);
    NSLog(@"-------------------------END TEST-------------------------");
}

@end

是不是打印出文首那要的log了呢?

5 个赞

NSClass/SelectorFromString() + encrypted string would be better or you won’t pass AppStore review process. : )

1 个赞