运行时:
Runtime, 通过字面理解, 是系统运行时候的一些机制. 它是OC中一套用C语言写的API
runtime机制在我们实际开发过程中经常体现出来, 比如:
1. 一个btn, 他的触发方法我们没有定义, 编译过程中, 只会提示警告, 但是在真正运行的时候, 就会crash
2. 无论MVC, MVVM框架, 我们都要创建Model, 在BaseModel中我们经常会会写这样一个方法, 只有在运行的时候, 才知道具体哪一个对象会调用这个方法
+ (instancetype)baseModelWithDic:(NSDictionary *)dic {
id model = [[[self class] alloc] initWithDic:dic];
return model;
}
runtime功能非常强大, 在很多优秀的第三方框架中都有用到, 首先来了解一下runtime有哪些功能
/*
runtime常用方法总结:
1.1 获得一个类所有的成员变量
class_copyIvarList(Class cls , unsigned int *outCount)
1.2 获取类的成员变量的名字
ivar_getName(Ivar v)
1.3 获取类的成员变量的类型
ivar_getTypeEndcoding(Ivar v)
2.1 获取类的类方法
class_getClassMethod((Class cls , SEL name)
2.2 获取类的实例变量方法
class_getInstanceMethod(Class cls , SEL name)
2.3 交换两个方法
method_exchangeImplementations(Method m1 , Method m2)
3.1 将值存储到对象的object中
objc_setAssociatedObject(id object , const void *key ,id value ,objc_AssociationPolicy policy)
3.2 根据key 取出object中存储的值
objc_getAssociatedObject(id object , const void *key)
*/
一. runtime实现了哪些Obj-C实现不了的功能
1. 获得某个类所有的成员变量, 成员方法;
2. 为一个类动态添加成员变量, 成员方法;
3. 动态交换两个方法
4. 发送消息
二. 利用runtime我们可以做哪些工作
1. 实现一个自定义的对象完成本地存储, 我们需要一一的对每一个属性进行decodeObjectForKey: 和 encodeObject:
我们可以利用class_copyIvarList方法获取当前类的所有成员变量, 然后遍历, 再利用ivar_getName将成员变量的名字当做key进行归档解档, 从而实现自动归解档, 无需考虑实际有多少个变量(获取当前类的所有成员变量)
这样如果属性多了, 或者后期经常添加或修改属性, 那就会非常的麻烦, 所以, 我们可以利用runtime的'能获取类所有的成员变量'这个方法, 动态的对该对象进行自动归档,解档
2. 给Category动态的添加属性(为一个类添加成员变量成员方法)
之前提过, Category只能添加方法, 不能添加属性, 但是我们可以用runtime给Category添加属性, 这里我们利用的是objc_setAssociatedObject方法将值关联到设置的属性上
例如: 给UIViewController添加一个副标题属性
1->首先我们需要在声明文件中声明一个属性:
#import@interface UIViewController (Subhead)
@property(nonatomic, copy) NSString *subHead;
@end
2->接着在实现文件中引runtime头文件
set, get方法内部是根据键值来存取的, 我们需要给这个属性定义个key, 然后让set, get方法中根据这个key来存取属性的值
#import "UIViewController+Subhead.h"
#import <objc/runtime.h>
NSString *const kName = @"name";
@implementation UIViewController (Subhead)
// 重写set方法
- (void)setSubHead:(NSString *)subHead {
objc_setAssociatedObject(self, (__bridge const void *)(kName), subHead, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)subHead {
return objc_getAssociatedObject(self, (__bridge const void *)(kName));
}
@end
3. 开发中, 我们经常为了给运营统计数据, 会统计页面或按钮点击次数, 停留时间(方法的拦截, 用自己定义的方法与系统自带的方法交换)
首先我们要给UIViewController创建一个类目:Swizzling
#import "UIViewController+swizzling.h"
#import@implementation UIViewController (swizzling)
+ (void)load {
[super load];
// 通过class_getInstanceMethod()函数获取当前类的方法
Method fromMethod = class_getInstanceMethod([self class], @selector(viewDidLoad));
Method toMethod = class_getInstanceMethod([self class], @selector(swizzlingViewDidLoad));
// 交换方法的实现
if (!class_addMethod([self class], @selector(viewDidLoad), method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) {
method_exchangeImplementations(fromMethod, toMethod);
}
}
// 我们自己实现的方法,也就是和self的viewDidLoad方法进行交换的方法。
- (void)swizzlingViewDidLoad {
NSString *str = [NSString stringWithFormat:@"%@", self.class];
// 我们在这里加一个判断,将系统的UIViewController的对象剔除掉
if(![str containsString:@"UI"]){
NSLog(@"统计打点 : %@", self.class);
}
[self swizzlingViewDidLoad]; // 这里面实际调用的是原来的ViewDidLoad,因为此时已经被替换了
}
@end
结合runtime配合类目, 我们可以对归档解档进一步优化:
首先我们给NSObject写一个类目
声明文件中声明三个方法,忽略属性数组的get方法, 归档, 解档
#import@interface NSObject (Coder)
- (NSArray *)ignoreNames;
- (void)encode:(NSCoder *)enCode;
- (void)deCode:(NSCoder *)deCode;
@end
实现文件中, 只实现后两个方法
#import "NSObject+Coder.h"
#import <objc/runtime.h>
@implementation NSObject (Coder)
// 归档
- (void)encode:(NSCoder *)enCode {
// 从子类往父类进行查找,对父类的属性执行归解档方法
Class sClass = self.class;
while (sClass && sClass != [NSObject class]) {
unsigned int outCount = 0;
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)];
// 如果有需要忽略的Key 跳过归档
if ([self respondsToSelector:@selector(ignoredNames)]) {
if ([[self ignoreNames] containsObject:key]) continue;
}
id value = [self valueForKeyPath:key];
[enCode encodeObject:value forKey:key];
}
free(ivars);
sClass = [sClass superclass];
}
}
// 解档
- (void)deCode:(NSCoder *)deCode {
// 一层层父类往上查找,对父类的属性执行归解档方法
Class sClass = self.class;
while (sClass && sClass != [NSObject class]) {
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList(sClass, &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 如果有需要忽略的Key 跳过解档
if ([self respondsToSelector:@selector(ignoredNames)]) {
if ([[self ignoreNames] containsObject:key]) continue;
}
id value = [deCode decodeObjectForKey:key];
[self setValue:value forKey:key];
}
free(ivars);
sClass = [sClass superclass];
}
}
@end