前言
在平日的iOS开发中,最常见的的用到runtime
的应该就是分类添加属性了,我们平时在类里使用@property
添加属性的时候,系统会自动生成带“”做前缀的的成员变量以及该变量的setter和getter方法。但是在分类里使用@property
添加属性,.m文件会出现警告,提示没有实现改属性的的setter和getter方法。需要我们手动添加代码,而且手动添加的代码也无法用“”做前缀+属性变量名去访问属性,需要我们动态用runtime
的代码去实现。那么runtime
是什么?runtime
的方法又有哪些?作为进阶学习,我们应该要掌握哪些常用的runtime
代码,接下来一一讲解。
一、什么是runtime
通俗来说,RunTime其实就是一个库,是oc底层的C语言API,我们平时写的oc代码,其实最终都会被编译器转化为runtime
代码。
runtime
顾名思义就是运行时的意思,即我们使用runtime
的代码,系统并不是编译的时候去执行,而是运行中去到该代码处才会去动态执行这些代码。像是一些变量的值,可能根据需求我们得在程序运行到某一个步骤的时候才能确定它的值,然后取出来,这时候则可以使用runtime
的代码在对应的那个时刻去获取变量的值,然后动态的做一些操作。
二、runtime的方法
使用RunTime库里的代码,需要引入头文件
#import <objc/runtime.h>
有些函数则需要引入
#import <objc/message.h>
点击进入头文件,可以看到runtime
提供的方法
下图只是其中一部分,其他自己可以去头文件自己浏览
三、runtime需要掌握的方法
为啥我们需要学习runtime
?我认为有以下几点
1.runtime
能做一些我们用oc代码比较难实现的功能,例如拦截某个方法,然后进行方法交换(一半是拦截交换系统方法)
2.我们平时oc的函数调用,实际最后都会变成runtime
的消息转发机制,掌握这个消息转发机制,我们就可以把消息重定向到别的对象,从而写出更加灵活的代码,
3.能利用系统动态调用函数这个机制去动态执行一些操作,比如新增一个对象,添加一个方法or一个属性,替换掉方法等。
runtime
有哪些作为进阶需要掌握的方法?
1.给分类添加属性
比如我想给某个SDK里的对象添加属性,但是无法修改这个对象,这时候就可以用分类来实现
这里需要用到关联函数objc_setAssociatedObject
和objc_getAssociatedObject
,其定义如下
函数objc_setAssociatedObject
的参数意义如下
第一个参数:表示给哪个对象添加关联
第二个参数:表示关联的key,后面要获取的objc_getAssociatedObject
则是通过这个key去获取值
第三个参数:表示这个key对应的值,第四个参数则是这个
第四个参数:policy表示关联策略,是一个枚举,目前有五种策略
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};
等效于
而函数objc_getAssociatedObject
的参数意义如下
第一个参数:表示关联的对象
第二个参数:表示关联的key,返回值就是上面方法中相同key的value对象
使用方式如下:
先创建一个NSSObjcet
的分类
然后为分类添加属性name,然后利用runtime
代码设置name的getter
和setter
方法
NSObject+myobj.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (myobj)
@proprery (nonatomic, strong) NSString *name;
@end
NS_ASSUME_NONNULL_END
NSObject+myobj.m
#import "NSObject+myobj.h"
#import <objc/runtime.h>
static NSString * const key = @"myName";
@implementation** NSObject (myobj)
- (NSString *)name
{
return objc_getAssociatedObject(self, &key);
}
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, &key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
这样,就能给生成的NSObject
实例添加名字和读取名字了
NSObject *objc = [[NSObject alloc] init];
objc.name = @"hello world";
NSLog(@"%@",objc.name);
2.交换方法
假如我想打印一下用户的app使用路径,我想每个VC的appear和disappear相关函数调用时都打印一下,那么就可以拦截系统的这些方法,然后用我写好的方法进行交换实现
这里需要用到
获取方法函数class_getInstanceMethod
交换函数method_exchangeImplementations
其定义如下
使用方式如下:
先创建一个UIViewController
的分类
然后在UIViewController+mylog.m
文件里写入下面的代码
#import "UIViewController+mylog.h"
#import <objc/runtime.h>
@implementation UIViewController (mylog)
+(void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//交换appear
Method org_viewDidAppear = class_getInstanceMethod(**self**, **@selector**(viewDidAppear:));
Method new_viewDidAppear = class_getInstanceMethod(**self**, **@selector**(myViewDidAppear:));
method_exchangeImplementations(org_viewDidAppear, new_viewDidAppear);
//交换disappear
Method org_viewDidDisappear = class_getInstanceMethod(**self**, **@selector**(viewDidDisappear:));
Method new_viewDidDisappear = class_getInstanceMethod(**self**, **@selector**(myViewDidDisappear:));
method_exchangeImplementations(org_viewDidDisappear, new_viewDidDisappear);
});
}
- (void)myViewDidAppear:(BOOL)animated{
NSLog(@"%@ viewDidAppear",self);
[self myViewDidAppear:animated];//重定向回到vc的viewDidAppear方法
}
- (void)myViewDidDisappear:(BOOL)animated{
NSLog(@"%@ viewDidDisappear",self);
[self myViewDidDisappear:animated];//重定向回到vc的viewDidDisappear方法
}
@end
四、总结
关于runtime
代码的使用,我只列出了目前最常用的2个,其实还有很多用法,大家可以上网搜索学习一下,有空我也会补充本文,另外我也会找时间写一下我对runtime
运行时消息转发流程的理解,谢谢。