Runtime简介
Runtime 是 C,C++ 汇编一起写成的 API,有两个版本 Modern 和 Legacy,OC 2.0 之后用的是 Modern Version 版本,可以运行在 iOS2.0 和 macOS 10.5 之后的系统中。
Runtime 是一个用C、C++、汇编
编写的运行时库,包含了很多 C 语言的 API,封装了很多动态性相关的函数。
Objective-C 也因为Runtime
的存在成为一门动态运行时语言,允许很多操作推迟到程序运行时再进行。OC
的动态性就是由Runtime
来支撑和实现的,Rumtime
就是它的核心,我们平时编写的OC
代码,底层都是转换成了Runtime API
进行调用。
都说 OC 是一门动态运行时语言,那什么是运行时?什么是编译时?
- 运行时:代码跑起来之后会装载到内存中,提供运行时功能
- 编译时:正在编译的时间,就是把源代码(高级语言)翻译成机器能识别的语言->机器语言->二进制
Runtime有哪些应用
Runtime 可以做的事情有很多,如
- 动态交换方法
- KVO 实现
- 关联对象:给分类添加属性
- 消息转发:项目中一些防崩溃处理
- 遍历类的所有成员变量(字典转模型、自动归档解档)
关联对象(Objective-C Associated Objects)给分类增加属性
我们都是知道分类是不能自定义属性和变量的。下面通过关联对象实现给分类添加属性。
关联对象 Runtime 提供了下面几个接口:
objc
//设置关联对象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
//获取关联的对象
id objc_getAssociatedObject(id object, const void *key)
//移除关联的对象
void objc_removeAssociatedObjects(id object)
参数解释
objc
id object:被关联的对象
const void *key:关联的key,要求唯一
id value:关联的对象
objc_AssociationPolicy policy:内存管理的策略
内存管理的策略
objc
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
举个例子
objc
/*! runtime set */
#define C4Kit_Objc_setObj(key, value) objc_setAssociatedObject(self, key, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
/*! runtime get */
#define C4Kit_Objc_getObj objc_getAssociatedObject(self, _cmd)
@interface WKWebView (C4Kit)
@property (nonatomic, assign) CGFloat webViewHeigt;
/// 判断是否添加了观察者
@property (nonatomic, assign) BOOL hadAddObserver;
@end
@implementation WKWebView (C4Kit)
- (void)setWebViewHeigt:(CGFloat)webViewHeigt
{
C4Kit_Objc_setObj(@selector(webViewHeigt), @(webViewHeigt));
}
- (CGFloat)webViewHeigt
{
return [C4Kit_Objc_getObj floatValue];
}
方法添加
实际上添加方法刚才在讲消息转发的时候,动态方法解析的时候就提到了。
objc
//class_addMethod(Class _Nullable __unsafe_unretained cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)
class_addMethod([self class], sel, (IMP)fooMethod, "v@:");
- cls 被添加方法的类
- name 添加的方法的名称的SEL
- imp 方法的实现。该函数必须至少要有两个参数,self,_cmd
- 类型编码
如动态方法解析时,动态添加的实现SEL
objc
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(run)) {
SEL readBookSel = @selector(readBook);
Method readM = class_getInstanceMethod(self, readBookSel);
IMP readImp = class_getMethodImplementation(self, readBookSel);
const char *type = method_getTypeEncoding(readM);
return class_addMethod(self, sel, readImp, type);
}
return [super resolveInstanceMethod:sel];
}
方法替换
下面实现一个替换ViewController
的viewDidLoad
方法的例子。
objc
@implementation ViewController
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewDidLoad);
SEL swizzledSelector = @selector(jkviewDidLoad);
Method originalMethod = class_getInstanceMethod(class,originalSelector);
Method swizzledMethod = class_getInstanceMethod(class,swizzledSelector);
//judge the method named swizzledMethod is already existed.
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
// if swizzledMethod is already existed.
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)jkviewDidLoad {
NSLog(@"替换的方法");
// 此处调用的是系统方法 viewDidLoad
[self jkviewDidLoad];
}
- (void)viewDidLoad {
NSLog(@"自带的方法");
[super viewDidLoad];
}
@end
需要注意的是,jkviewDidLoad
的实现里调用[self jkviewDidLoad];
实际调用的是系统方法viewDidLoad
。
KVO
KVO
的底层实现大致可以描述如下:
- 动态创建子类
NSKVONotifying_XXX
, - 创建
class
方法,class
指向原类,为了隐藏这个子类 - 创建
setter
方法,内部把消息转发给父类,并触发回调observeValueForKeyPath
- 创建
dealloc
方法 - 创建
isKVOA
- 处理
removeObserver
操作isa
指回父类- 不会移除这个子类,即把这个子类缓存下来,为防止下次监听时再次创建耗费性能,这里用空间换时间
详细请看iOS KVO详解
实现NSCoding的自动归档和自动解档
原理描述:用runtime
提供的函数遍历Model
自身所有属性,并对属性进行encode
和decode
操作。 核心方法:在Model
的基类中重写方法:
objc
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
unsigned int outCount;
Ivar * ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int outCount;
Ivar * ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[aCoder encodeObject:[self valueForKey:key] forKey:key];
}
}
实现字典和模型的自动转换(MJExtension)
原理描述:用runtime
提供的函数遍历Model
自身所有属性,如果属性在json
中有对应的值,则将其赋值。 核心方法:在NSObject
的分类中添加方法
objc
- (instancetype)initWithDict:(NSDictionary *)dict {
if (self = [self init]) {
//(1)获取类的属性及属性对应的类型
NSMutableArray * keys = [NSMutableArray array];
NSMutableArray * attributes = [NSMutableArray array];
/*
* 例子
* name = value3 attribute = T@"NSString",C,N,V_value3
* name = value4 attribute = T^i,N,V_value4
*/
unsigned int outCount;
objc_property_t * properties = class_copyPropertyList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
objc_property_t property = properties[i];
//通过property_getName函数获得属性的名字
NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
[keys addObject:propertyName];
//通过property_getAttributes函数可以获得属性的名字和@encode编码
NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
[attributes addObject:propertyAttribute];
}
//立即释放properties指向的内存
free(properties);
//(2)根据类型给属性赋值
for (NSString * key in keys) {
if ([dict valueForKey:key] == nil) continue;
[self setValue:[dict valueForKey:key] forKey:key];
}
}
return self;
}