runtime入门

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方法

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

《runtime入门》 要执行好几个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方法,咋办?

《runtime入门》 千万不要这样写,是死循环

应该这样写

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

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

通过rt_imageNamed:替换imageNamed:方法

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

《runtime入门》 控制器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就行

参考链接

    原文作者:王鑫20111
    原文地址: https://www.jianshu.com/p/c2ca4ca618f2
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞