前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Runtime再理解

Runtime再理解

作者头像
拉维
发布2019-10-24 12:59:23
6350
发布2019-10-24 12:59:23
举报
文章被收录于专栏:iOS小生活

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

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

以上。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-10-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 iOS小生活 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档