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 写出两个对象方法,
run
和jump
,调用那个,就是打印出所在的方法,
2.2 我们要做的目标是,交换run
和jump
的方法.即当调用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
方法
所有的控制器销毁的时候,都会调用新写方法,这个可以统一管理销毁的事件
替换了控制器的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_dealloc
和dealloc
是有问题的,应当找个特定的处理方法,现在没想出来。不过,想刚刚说的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,打印信息
//控制器的方法,直接给照片赋值
@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];
}
NSArray
和NSDictionary
对应类不可以写成self
,一定要写成对应的类簇形式,Google就行