Runtime再理解

2019-10-24 12:59:23 浏览数 (1)

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");
}
    

上述代码的演示效果如下:

以上。

0 人点赞