1,关于编译时和运行时
编译时:
Objective-C、Java、Swift等高级语言,其可读性很强,但是并不能直接被机器识别,所以就需要将这些源代码编译成相对应的机器语言(比如汇编语言),最终会生成二进制代码。这就是编译时做的事情。
运行时:
Objective-C是一门动态性的语言,它会将一些工作放在代码运行的时候才会去处理,而并非所有代码都在编译时处理。也就是说,有很多的类和成员变量以及方法实现等,在编译的时候是不知道的,而在运行的时候,我们所编写的代码才会转换成完整的、确定的代码。因此我们需要一个运行时系统(Runtime System)来处理编译后的代码。Runtime System实际上是一个C语言写的底层库,即一套API,系统在编译完代码之后,在运行的时候还需要依赖Runtime System才能够完整的、确定的代码。这就是Runtime。
2,实例方法存在于类的methodList中,类方法存在于元类的methodList中。
实例对象是类的实例,类对象是元类的实例。
基于以上两点可知,类方法在元类的methodList中是以实例方法的姿态存在的!!
3,Runtime的应用
很多人觉得Runtime很高大上、很难学、很难理解、华而不实。实际上,当你真正理解了Runtime之后,你会发现:“原来我真的可以用Runtime解决很多实际问题~”
(1)Runtime——使用类目给某个类添加属性
(2)通过消息转发防止程序崩溃:Runtime——消息转发流程
(3)提高OC对象序列化与反序列化的效率:Runtime应用——序列化&反序列化
(4)Hook方法进行代码调试:Runtime应用——在不修改原方法的基础上给原方法添加功能
(5)防止在NSDictionary中传入nil的时候程序崩溃:当NSDictionary遇见nil
除了上面几种应用,我接下来再为大家介绍一种应用——万能跳转。
一般情况下,如果我们需要在某页面进行页面跳转到另外一个页面,那么就在当前页面使用import引入另一页面的文件,然后新建跳转即可。但是在一些特殊的场景下,为了规避苹果的审查,我们需要服务器数据来控制页面的跳转,即需要动态实现控制器的获取或者创建,此时该怎么处理呢?
代码如下:
代码语言:javascript复制#import "ViewController.h"
#import <objc/runtime.h>
@interface ViewController ()
@property (nonatomic, strong)NSMutableArray *dataSources;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
/*
*万能跳转
*/
self.dataSources = @[
@{
@"class":@"NormanRedVC",
@"data":@{
@"name":@"norman",
@"bgColor":@"red"
}
},
@{
@"class":@"NormanGreenVC",
@"data":@{
@"slogan":@"和谐学习,不急不躁",
@"bgColor":@"green"
}
},
@{
@"class":@"NormanOrangeVC",
@"data":@{
@"ending":@"我就是我,样色不一样的烟火",
@"bgColor":@"orange"
}
},
].mutableCopy;
UIButton *redBtn = [[UIButton alloc] initWithFrame:CGRectMake(20, 100, 100, 60)];
[redBtn setTitle:@"toRedVC" forState:UIControlStateNormal];
[redBtn setBackgroundColor:UIColor.redColor];
[redBtn addTarget:self action:@selector(toRedVC:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:redBtn];
UIButton *greenBtn = [[UIButton alloc] initWithFrame:CGRectMake(20, 300, 100, 60)];
[greenBtn setTitle:@"toGreenVC" forState:UIControlStateNormal];
[greenBtn setBackgroundColor:UIColor.greenColor];
[greenBtn addTarget:self action:@selector(toGreenVC:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:greenBtn];
UIButton *orangeBtn = [[UIButton alloc] initWithFrame:CGRectMake(20, 500, 100, 60)];
[orangeBtn setTitle:@"toOrangeVC" forState:UIControlStateNormal];
[orangeBtn setBackgroundColor:UIColor.orangeColor];
[orangeBtn addTarget:self action:@selector(toOrangeVC:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:orangeBtn];
}
#pragma mark - ButtonAction
- (void)toRedVC:(UIButton *)btn
{
[self pushToAnyVCWithData:self.dataSources[0]];
}
- (void)toGreenVC:(UIButton *)btn
{
[self pushToAnyVCWithData:self.dataSources[1]];
}
- (void)toOrangeVC:(UIButton *)btn
{
[self pushToAnyVCWithData:self.dataSources[2]];
}
#pragma mark -
- (void)pushToAnyVCWithData:(NSDictionary *)dataDic
{
//1,获取或者创建类
const char *clsName = [dataDic[@"class"] UTF8String];
Class cls = objc_getClass(clsName);//根据类名得到该类
if (!cls) {
//如果在本工程中没有该类名所对应的类,那么就新建一个类
Class superClass = [UIViewController class];
cls = objc_allocateClassPair(superClass, clsName, 0);//为当前类开辟一块内存
class_addIvar(cls, "ending", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));//增加成员变量
class_addIvar(cls, "show_lb", sizeof(UILabel *), log2(sizeof(UILabel *)), @encode(UILabel *));//增加成员变量
objc_registerClassPair(cls);//注册该类
//给跳转页(新建的类)的viewDidload方法添加方法实现
Method method = class_getInstanceMethod([self class], @selector(lg_instancemethod));
IMP methodIMP = method_getImplementation(method);
const char * types = method_getTypeEncoding(method);
BOOL rest = class_addMethod(cls, @selector(viewDidLoad), methodIMP, types);
NSLog(@"rest == %d", rest);
}
//2,实例化对象
id instance = nil;
@try {
//从StoryBoard中加载
instance = [[cls alloc] init];
// UIStoryboard *sb = [UIStoryboard storyboardWithName:@"" bundle:[NSBundle mainBundle]];
// instance = [sb instantiateViewControllerWithIdentifier:dataDic[@"class"]];
} @catch (NSException *exception) {
instance = [[cls alloc] init];
} @finally {
NSLog(@"实例化对象成功");
}
//给实例对象赋值
NSDictionary *dic = dataDic[@"data"];
[dic enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
//检测是否存在key的属性
if (class_getProperty(cls, [key UTF8String])) {
[instance setValue:obj forKey:key];
}
//检测是否存在key的变量
if (class_getInstanceVariable(cls, [key UTF8String])) {
[instance setValue:obj forKey:key];
}
}];
//3,页面跳转
[self presentViewController:instance animated:YES completion:^{
}];
}
- (void)lg_instancemethod
{
[super viewDidLoad];
/*
*注意,这个方法实际上是跳转页面(即x手动新建的类)的viewDidLoad方法的替换
*这里的self是跳转页面的实例。并不是说在ViewController中的self就是ViewController或者其实例,
*该消息给谁发送,也就是说,该消息的接收者是谁,那么self就是谁
*/
[self setValue:UIColor.orangeColor forKeyPath:@"view.backgroundColor"];
[self setValue:[[UILabel alloc] initWithFrame:CGRectMake(100, 200, 200, 30)] forKey:@"show_lb"];
UILabel *show_lb = [self valueForKey:@"show_lb"];
[[self valueForKey:@"view"] addSubview:show_lb];
show_lb.text = [self valueForKey:@"ending"];
show_lb.font = [UIFont systemFontOfSize:14];
show_lb.textColor = UIColor.blackColor;
show_lb.textAlignment = NSTextAlignmentCenter;
show_lb.backgroundColor = UIColor.whiteColor;
NSLog(@"hello world");
}
@end
上面的代码是当前页面(即ViewController.m)的实现代码。
在程序中我只实现了NormanRedVC这个类,代码如下:
代码语言:javascript复制//NormanRedVC.m
#import "NormanRedVC.h"
@interface NormanRedVC ()
@property (nonatomic, copy)NSString *name;
@end
@implementation NormanRedVC
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.redColor;
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 200, 80)];
label.backgroundColor = UIColor.redColor;
label.text = self.name;
[self.view addSubview:label];
}
@end
而NormanGreenVC和NormanOrangeVC这两个类在程序中是没有实现的,这个时候,我们进行判断,当在工程中找不到对应的类的时候,我会手动新建一个控制器,如下:
代码语言:javascript复制 //1,获取或者创建类
const char *clsName = [dataDic[@"class"] UTF8String];
Class cls = objc_getClass(clsName);//根据类名得到该类
if (!cls) {
//如果在本工程中没有该类名所对应的类,那么就新建一个类
Class superClass = [UIViewController class];
cls = objc_allocateClassPair(superClass, clsName, 0);//为当前类开辟一块内存
class_addIvar(cls, "ending", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));//增加成员变量
class_addIvar(cls, "show_lb", sizeof(UILabel *), log2(sizeof(UILabel *)), @encode(UILabel *));//增加成员变量
objc_registerClassPair(cls);//注册该类
//给跳转页(新建的类)的viewDidload方法添加方法实现
Method method = class_getInstanceMethod([self class], @selector(lg_instancemethod));
IMP methodIMP = method_getImplementation(method);
const char * types = method_getTypeEncoding(method);
BOOL rest = class_addMethod(cls, @selector(viewDidLoad), methodIMP, types);
NSLog(@"rest == %d", rest);
}
- (void)lg_instancemethod
{
[super viewDidLoad];
/*
*注意,这个方法实际上是跳转页面(即x手动新建的类)的viewDidLoad方法的替换
*这里的self是跳转页面的实例。并不是说在ViewController中的self就是ViewController或者其实例,
*该消息给谁发送,也就是说,该消息的接收者是谁,那么self就是谁
*/
[self setValue:UIColor.orangeColor forKeyPath:@"view.backgroundColor"];
[self setValue:[[UILabel alloc] initWithFrame:CGRectMake(100, 200, 200, 30)] forKey:@"show_lb"];
UILabel *show_lb = [self valueForKey:@"show_lb"];
[[self valueForKey:@"view"] addSubview:show_lb];
show_lb.text = [self valueForKey:@"ending"];
show_lb.font = [UIFont systemFontOfSize:14];
show_lb.textColor = UIColor.blackColor;
show_lb.textAlignment = NSTextAlignmentCenter;
show_lb.backgroundColor = UIColor.whiteColor;
NSLog(@"hello world");
}
上述代码的演示效果如下:
以上。