美文网首页
runtime入门

runtime入门

作者: mkb2 | 来源:发表于2016-09-10 16:33 被阅读110次
    1.什么是运行时?

    1.1 OC Runtime 是被忽略的特性之一
    Objective-C 的 Runtime 是一个运行时库(Runtime Library),它是一个主要使用 C 和汇编写的库,为 C 添加了面相对象的能力并创造了 Objective-C。这就是说它在类信息(Class information) 中被加载,完成所有的方法分发,方法转发,等等。Objective-C runtime 创建了所有需要的结构体,让 Objective-C 的面相对象编程变为可能。

    1.2 - RunTime简称运行时。OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制

    • 对于C语言,函数的调用在编译的时候会决定调用哪个函数。
    • 对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
    • 事实证明:
      • 在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。
      • 在编译阶段,C语言调用未实现的函数就会报错。

    编译器会把[target doMethodWith:var1]; 转换为 objc_msgSend(target,@selector(doMethodWith:),var1);

    1.3 运行时的属于 Method

    • 1.3.1 Instance Method(实例方法):以 ‘-’ 开始,比如 -(void)doFoo; 在对象实例上操作。
    • 1.3.2 Class Method(类方法):以 ‘+’ 开始,比如 +(id)alloc。

    在c语言中,如果你编译成功,那么以实际的运行过程中,系统会严格的按照刚才编译的顺序执行;oc中的只要编译成功就好了,真正运行的时候,执行什么方法,再说吧,看看到时候是啥,充满了不确定性

    2.小demo- 交换对象方法和类方法

    2.1 写出两个对象方法,runjump,调用那个,就是打印出所在的方法,
    2.2 我们要做的目标是,交换runjump的方法.即当调用run的时候,实际调用了jump的方法

    //狗对象的.h文件
    - (void)run;
    
    - (void)jump;
    
    + (void)eat;
    
    +(void)drink;
    
    //狗对象的.m文件
    - (void)run{
        NSLog(@"%s",__func__);
    }
    
    - (void)jump{
        NSLog(@"%s",__func__);
    }
    
    + (void)eat{
        NSLog(@"%s",__func__);
    }
    
    +(void)drink{
        NSLog(@"%s",__func__);
    }
    

    viewController中调用狗对象,看看打印

        //获取对象的方法
        RTDog *dog = [[RTDog alloc] init];
        [dog run];
        [dog jump];
    
    //运行结果如下
    2016-09-10 09:04:06.902 RunTime[646:11432] -[RTDog run]
    2016-09-10 09:04:06.902 RunTime[646:11432] -[RTDog jump]
    

    现在去交换对象方法

        //交换方法
    //获取对象方法用这个 ,第二个参数是是这个样子的~~~
        class_getInstanceMethod(__unsafe_unretained Class cls, SEL name)
    
        Method method1 = class_getInstanceMethod([RTDog class], @selector(run));
        Method method2 = class_getInstanceMethod([RTDog class], @selector(jump));
        method_exchangeImplementations(method1, method2);    
        //获取对象的方法
        RTDog *dog = [[RTDog alloc] init];
        [dog run];
        [dog jump];
    

    运行结果如下

    //方法被交换了!
    2016-09-10 09:04:06.902 RunTime[646:11432] -[RTDog jump]
    2016-09-10 09:04:06.902 RunTime[646:11432] -[RTDog run]
    

    交换类方法

            Method method1 = class_getClassMethod([RTDog class], @selector(eat));
            Method method2 = class_getClassMethod([RTDog class], @selector(drink));
        method_exchangeImplementations(method1, method2);
        [RTDog eat];
        [RTDog drink];
    

    结果如下

    2016-09-10 09:21:45.387 RunTime[712:19847] +[RTDog drink]
    2016-09-10 09:21:45.388 RunTime[712:19847] +[RTDog eat]
    
    二.给控制器新写一个rt_dealloc方法.替换系统中的dealloc方法

    所有的控制器销毁的时候,都会调用新写方法,这个可以统一管理销毁的事件

    要执行好几个push操作

    替换了控制器的dealloc方法只有,在替换的方法中我们打印数据

    2016-09-10 15:58:39.067 RunTime[746:14220] -[UIViewController(RTExtension) rt_dealloc]-<UIViewController: 0x7fef72d2b840>
    2016-09-10 15:59:27.303 RunTime[746:14220] -[UIViewController(RTExtension) rt_dealloc]-<UIViewController: 0x7fef72fb7100>
    2016-09-10 15:59:27.821 RunTime[746:14220] -[UIViewController(RTExtension) rt_dealloc]-<UIViewController: 0x7fef72c38ca0>
    2016-09-10 15:59:28.343 RunTime[746:14220] -[UIViewController(RTExtension) rt_dealloc]-<UIViewController: 0x7fef72c38730>
    
    

    代码如下

    + (void)load{
        
        //交换方法
        
            Method method1 = class_getInstanceMethod(self, NSSelectorFromString(@"dealloc"));
            Method method2 = class_getInstanceMethod(self, @selector(rt_dealloc));
            method_exchangeImplementations(method1, method2);
    }
    
    - (void)rt_dealloc{
        NSLog(@"%s-%@",__func__,self);
    }
    

    1.先写一个控制器的分类,然后在load方法中去替换两个方法,load方法的调用时在一加载到内存中就调用,所以特别适合监听各种方法的替换过程。
    2.既然已加载内存中药去替换,所以这里的.h文件是没有用的,直接删除就好 。但是会报错,#import "UIViewController+RTExtension.h"显示没有发现,直接替换成#import "UIKit/UiKit.h"就好了,这个是告诉系统我们有UIViewController这个控件的

    三.在rt_dealloc执行之后,再去执行 dealloc方法

    1.在rt_dealloc方法中,我们执行完了自己的东西,如何再去调用dealloc的数据?

    举个栗子:将控制器 -3继承自ViewController,然后重写该控制器的dealloc方法

    - (void)dealloc{
        NSLog(@"viewControoller被销毁了");
    }
    

    运行结果如下

    RunTime[826:24134] viewControoller被销毁了
    RunTime[826:24134] -[UIViewController(RTExtension) rt_dealloc]-<ViewController: 0x7fbb09757a00>
    

    刚才不是说好了,交换了吗,怎么两个都调用了?
    因为刚才的方法中这样写的 Method method1 = class_getInstanceMethod(self, NSSelectorFromString(@"dealloc"));我们写成class_getInstanceMethod(self, @selector(dealloc));这样是报错的。也就是ACR状态下,调用这个方法是要特殊处理的

    所以,按照我这样写替换rt_deallocdealloc是有问题的,应当找个特定的处理方法,现在没想出来。不过,想刚刚说的run,jump,eat,drink方法都是没有错的。

    假设刚刚的dealloc方法是可以的,(假设不需要特殊处理),在实际的开发中,我们替换了两个方法,但是我想执行完rt_dealloc再去执行dealloc方法,咋办?

    千万不要这样写,是死循环

    应该这样写

    - (void)rt_dealloc{
        NSLog(@"%s-%@",__func__,self);
        [self rt_dealloc];
    }
    
    

    因为此刻的rt_delloc已经替换成了dealloc

    通过rt_imageNamed:替换imageNamed:方法

    目的,通过前边的方法,替换后边的,如果是nil,打印信息

    控制器1中放置一个imageView,然后拉线到外边,然后调用imageNamed:方法,加载图片
    //控制器的方法,直接给照片赋值
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.imageV.image = [UIImage imageNamed:@"1323"];
    }
    @end
    
    #import "UIKit/UiKit.h"
    #import <objc/runtime.h>
    @implementation UIImage (THExtension)
    + (void)load{
        //交换两个方法
        Method method1 = class_getClassMethod(self, @selector(imageNamed:));
        Method method2 = class_getClassMethod(self, @selector(rt_imageNamed:));
        method_exchangeImplementations(method1, method2);
    }
    
    +  (id)rt_imageNamed:(NSString *)name
    {
        UIImage *image = [self rt_imageNamed:name];
        if (image == nil) {
            NSLog(@"图片是空的");
        }
        return image;
    }
    @end
    

    1.删除了.h文件,因为直接在load方法中替换方法
    2.+ (id)rt_imageNamed:(NSString *)name调用,实际上控制器调用的 iamgeNamed:方法
    3.UIImage *image = [self rt_imageNamed:name];这个是调用的iamgeNamed:方法
    4.调用的时候,还是执行过去的,不会影响任何的东西


    这样做有什么好处?因为系统的方法不够完全,不能完全符合我的需求,所以我要按照自己的意愿做一些处理,然后通过运行时来改变他。类似的,我们经常遇到向数组,字典中添加nil的时候,我们通过addObject:方法添加对象,可以过滤掉nil并且给他个提示,也是写一个分类,然后判断,很简单

    reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
    
    #import "NSMutableArray+Extension.h"
    #import <objc/runtime.h>
    
    @implementation NSMutableArray (Extension)
    + (void)load{
        //交换两个方法 class_getInstanceMethod
        Method method1 = class_getInstanceMethod(self, @selector(addObject:));
        Method method2 = class_getInstanceMethod(NSClassFromNSString(@"__NSArrayM"), @selector(rt_addObject:));
        method_exchangeImplementations(method1, method2);
        
    }
    
    - (void)rt_addObject:(id)anObject{
        if (anObject) {
            [self rt_addObject:anObject];
        }else{
            NSLog(@"对象是nil,不能给你添加到数组中");
        }
    }
    @end
    
    //控制器中的数组添加方法
    - (void)viewDidLoad {
        [super viewDidLoad];
        NSMutableArray *arr = [NSMutableArray array];
        [arr addObject:@"4"];
        NSString *str = nil;
        [arr addObject:str];
    }
    

    NSArrayNSDictionary对应类不可以写成self,一定要写成对应的类簇形式,Google就行

    参考链接

    相关文章

      网友评论

          本文标题:runtime入门

          本文链接:https://www.haomeiwen.com/subject/ghsyettx.html