前言
原文链接: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信息:
可以试着在代码中调用这个方法:
- (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方法,文件结构如下:
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;
}
这时候就可以编译了,查看结果:
惊人的的发现竟然调用成功了
代码参见文件中的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了呢?