什么是RunTime
RunTime是一套比较底层的纯C语言API,属于一个C语言库,包含了很多底层C语言API.我们平时编写的OC代码,程序运行过程中,其实最终都是转成RunTime的C语言代码,RunTime算是OC的幕后工作者.
比如:
OC中的代码
[[Student alloc] init];
其底层实现实际是消息发送机制,使用runtime表示为:
objc_msgSend(objc_msgSend(“Student” , “alloc”), “init”)
RunTime在什么时候使用
使用RunTime实现OC无法实现的,不好实现的一些非常底层的操作:
- 在程序运行过程中,动态的创建一个类(比如KVO的底层实现)
- 在程序运行过程中,动态的为某个类添加属性或者方法,修改属性的值或者方法
- 遍历一个类的所有成员变量(属性)或者方法(比如实现归档和反归档操作,有一个类就对其进行一次这样的操作比较麻烦,我们就可以给NSObject对象添加一个类目,这样就可以很方便的实现一键归档和反归档)
接下来我们用具体的代码来看一下怎样在项目中使用RunTime
1 使用RunTime实现归档和反归档
首先给NSObject写一个类目,遵守NSCoding协议,然后实现归档和反归档的两个方法
#import "NSObject+Coding.h"
// 需要导入RunTime的头文件
#import <objc/objc-runtime.h>
@implementation NSObject (Coding)
// 遍历类中所有的实例变量,逐个进行归档,反归档
-(void)encodeWithCoder:(NSCoder *)aCoder
{ /*class_copyIvarList函数,它返回一个指向成员变量信息的数组,数组中每个元素是指向该成员变量信息的objc_ivar结构体的指针。这个数组不包含在父类中声明的变量。outCount指针返回数组的大小。需要注意的是,我们必须使用free()来释放这个数组。*/
// 遍历实例变量
unsigned int ivarCount = 0;
Ivar *vars = class_copyIvarList(object_getClass(self), &ivarCount);
for (int i = 0; i < ivarCount; i++) {
// 获取实例变量名字
Ivar var = vars[i];
NSString *varName = [NSString stringWithUTF8String:ivar_getName(var)];
// KVC
id value = [self valueForKey:varName];
// 进行归档
[aCoder encodeObject:value forKey:varName];
}
// 释放指针
free(vars);
}
-(instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [self init];
if (self) {
// 遍历实例变量链表,逐个反归档
unsigned int ivarCount = 0;
Ivar *vars = class_copyIvarList(object_getClass(self), &ivarCount);
for (int i = 0; i < ivarCount; i++) {
Ivar var = vars[i];
NSString *varName = [NSString stringWithUTF8String:ivar_getName(var)];
// 反归档
id value = [aDecoder decodeObjectForKey:varName];
// KVC
[self setValue:value forKey:varName];
}
free(vars);
}
return self;
}
@end
我们在使用的时候,创建一个Student类,在类中声明自己需要的属性,在要使用此类的控制器中实现归档和反归档操作,控制器中具体代码如下:
Student *stu1 = [[Student alloc] init];
stu1.name = @"zhangsan";
stu1.age = 23;
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:stu1];
Student *stu2 = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSLog(@"%@", stu2.name);
2 使用RunTime实现多播委托
2.1 什么是多播委托
简单的说是指允许创建方法的调用列表或者链表的能力.当多播委托调用的时候,列表中的方法均自动执行.
在IOS中我就以我们平常用的最多的delagate为例,普通的delegate只能是一对一的回调,无法做到一对多的回调。而多播委托正式对delegate的一种扩展和延伸,多了一个注册和取消注册的过程,任何需要回调的对象都必须先注册。
如何在IOS中实现多播委托?老外早就已经写好了,而且相当的好用。我最初接触IOS多播委托是我在研究XMPPframework的时候,而多播委托可以说是XMPPframework架构的核心之一。具体的类名就是GCDMulticastDelegate,从名字就可以看出,这是一个支持多线程的多播委托。那为什么要支持多线程呢?我的理解是多个回调有可能不是在同一个线程的,比如我注册回调的时候是在后台线程,但是你回调的时候却在UI线程,那就有可能出问题了。因此必须保证你注册的时候在哪个线程上注册的,那么回调的时候必须还是在那个线程上回调的。
2.2 多播委托的本质,消息转发
如果一个对象收到一条无法处理的消息,运行时系统会在抛出错误前,给该对象发送一条forwardInvocation:消息,该消息的唯一参数是个NSInvocation类型的对象——该对象封装了原始的消息和消息的参数。
2.2 使用RunTim怎么实现多播委托
给NSObject添加一个类目,添加两个方法,一个增加代理,一个移除代理.
NSObject+MultiDelegate.h
#import <Foundation/Foundation.h>
@interface NSObject (MultiDelegate)
- (void)addDelegate:(id)delegate;
- (void)removeDelegate:(id)delegate;
@end
NSObject+MultiDelegate.m
#import "NSObject+MultiDelegate.h"
#import <objc/objc-runtime.h>
// 将数组关联对象时用的KEY
NSString *const kMultiDelegateKey = @"multiDatagateKey";
@implementation NSObject (MultiDelegate)
// 添加
- (void)addDelegate:(id)delegate
{
/*我们可以把关联对象想象成一个Objective-C对象(如字典),这个对象通过给定的key连接到类的一个实例上。不过由于使用的是C接口,所以key是一个void指针(const void *)。我们还需要指定一个内存管理策略,以告诉Runtime如何管理这个对象的内存。*/
// 设置代理数组,为对象关联对象
NSMutableArray *delegateArray = objc_getAssociatedObject(self, (__bridge const void *)(kMultiDelegateKey));
// 若当前对象没有关联的数组,创建并设置
if (!delegateArray) {
delegateArray = [NSMutableArray new];
objc_setAssociatedObject(self, (__bridge const void *)(kMultiDelegateKey), delegateArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
// 添加到数组中
[delegateArray addObject:delegate];
}
// 删除
- (void)removeDelegate:(id)delegate
{
NSMutableArray *delegateArray = objc_getAssociatedObject(self, (__bridge const void *)(kMultiDelegateKey));
if (!delegateArray) {
@throw [NSException exceptionWithName:@"MultiDelegate error" reason:@"数组为空是不对的" userInfo:nil];
}
[delegateArray removeObject:delegate];
}
// 消息转发给代理数组中的元素
- (void)doNothing
{
}
// 消息转发
/*消息转发机制使用从下面这个方法中获取的信息来创建NSInvocation对象。因此我们必须重写这个方法,为给定的selector提供一个合适的方法签名。*/
// 获取方法标识
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
// 获取代理的数组
NSMutableArray *delegateArray = objc_getAssociatedObject(self, (__bridge const void *)(kMultiDelegateKey));
// 变量数组
for (id aDelegate in delegateArray) {
// 取出每个元素对应aSelector的方法标识
NSMethodSignature *sig = [aDelegate methodSignatureForSelector:aSelector];
// 如果sig 不为空,返回
if (sig) {
return sig;
}
}
return [[self class] instanceMethodSignatureForSelector:@selector(doNothing)];
}
/*运行时系统会在这一步给消息接收者最后一次机会将消息转发给其它对象。对象会创建一个表示消息的NSInvocation对象,把与尚未处理的消息有关的全部细节都封装在anInvocation中,包括selector,目标(target)和参数。我们可以在forwardInvocation方法中选择将消息转发给其它对象。*/
// 消息转发给其他对象
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSMutableArray *delegateArray = objc_getAssociatedObject(self, (__bridge const void *)(kMultiDelegateKey));
for (id aDelegate in delegateArray) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 异步转发消息
[anInvocation invokeWithTarget:aDelegate];
});
}
}
2.4 使用
引入头文件 #import "NSObject+MulticastDelegate.h"
以Student类为例
// 创建student对象
Student *stu = [Student new];
// 添加代理为控制器
[stu addDelegate :self];
// 调用代理方法
[stu performSelector:NSSelectorFromString(@"doSomething") withObject:nil];
// 实现代理方法
- (void)doSomething {
NSLog(@"doNothing");
}
3 使用RunTime实现KVO
3.1 KVO的内部实现
当你观察一个对象时,一个新的类会动态被创建。这个类继承自该对象的原本的类,并重写了被观察属性的 setter 方法。自然,重写的 setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象值的更改。最后把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。
3.2 使用RunTime自己实现KVO
首先创建 NSObject 的 Category,并在头文件中添加两个 API:
@interface NSObject (KVO)
// 添加观察者
- (void)addObserver:(id)observer forKey:(NSString *)key withBlock:(void(^)(id, NSString *, id, id))block;
// 移除观察者
- (void)removeObserver:(NSObject *)observer forKey:(NSString *)key;
@end
接下来实现方法:
1、检查对象的类有没有相应的 setter 方法。如果没有抛出异常;
2、检查对象 isa 指向的类是不是一个 KVO 类。如果不是,新建一个继承原来类的子类,并把 isa 指向这个新建的子类;
3、检查对象的 KVO 类重写过没有这个 setter 方法。如果没有,添加重写的 setter 方法;
4、添加这个观察者
@implementation NSObject (KVO)
- (void)addObserver:(id)observer forKey:(NSString *)key withBlock:(void(^)(id, NSString *, id, id))block
{
// 获取setterName
NSString *setName = setterName(key);
SEL setSelector = NSSelectorFromString(setName);
// 通过SEL获取方法
Method setMethod = class_getInstanceMethod(object_getClass(self), setSelector);
if (!setMethod) {
@throw [NSException exceptionWithName:@"KVO Error" reason:@"若无setter方法,无法KVO" userInfo:nil];
}
// 获取当前类
// 判断是否已经创建衍生类
Class thisClass = object_getClass(self);
NSString *thisClassName = NSStringFromClass(thisClass);
if (![thisClassName hasPrefix:KVOClassPrefix]) {
thisClass = [self makeKVOClassWithOriginalClassName:thisClassName];
// 改变类的标识
object_setClass(self, thisClass);
}
// 判断衍生类是否实现了setter方法
if (![self hasSelector:setSelector]) {
const char *setType = method_getTypeEncoding(setMethod);
class_addMethod(object_getClass(self), setSelector, (IMP)setter, setType);
}
// 将observer 添加到观察者数组
NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(KVOServerAssociatedKey));
if (!observers) {
observers = [NSMutableArray new];
objc_setAssociatedObject(self, (__bridge const void *)(KVOServerAssociatedKey), observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
// 创建观察者info类
KVObServerInfo *info = [[KVObServerInfo alloc] initWithObserver:observer forKey:key withBlock:block];
[observers addObject:info];
}
// 重写setter方法,新的setter在调用原来的setter方法后,通知每个观察者(调用之前传入的block)
- void setter(id objc_self, SEL cmd_p, id newValue)
{
// setterName 转为name
NSString *setName = NSStringFromSelector(cmd_p);
NSString *key = nameWithSetterName(setName);
// 通过kvc获取key对应的value
id oldValue = [objc_self valueForKey:key];
// 将setter消息转发给父类
struct objc_super selfSuper = {
.receiver = objc_self,
.super_class = class_getSuperclass(object_getClass(objc_self))
};
objc_msgSendSuper(&selfSuper, cmd_p, newValue);
// 调用block
NSMutableArray *observers = objc_getAssociatedObject(objc_self, (__bridge const void *)(KVOServerAssociatedKey));
for (KVObServerInfo *info in observers) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if ([info.key isEqualToString:key]) {
info.block(objc_self, key, oldValue, newValue);
}
});
}
}
// 从setterName转回name
NSString *nameWithSetterName(NSString *setName)
{
if (setName.length <= 4 || ![setName hasPrefix:@"set"] || ![setName hasSuffix:@":"]) {
@throw [NSException exceptionWithName:@"KVO Error" reason:@"set方法not available" userInfo:nil];
}
NSString *Name = [setName substringWithRange:NSMakeRange(3, setName.length - 4)];
NSString *firstCharacter = [Name substringToIndex:1];
return [[firstCharacter lowercaseString] stringByAppendingString:[Name substringFromIndex:1]];
}
// 在衍生类中判断set方法是否存在
- (BOOL)hasSelector:(SEL)aSelector
{
unsigned int mCount = 0;
Method *methods = class_copyMethodList(object_getClass(self), &mCount);
for (int i = 0; i < mCount; i++) {
Method method = methods[i];
SEL setSelector = method_getName(method);
if (setSelector == aSelector) {
free(methods);
return YES;
}
}
free(methods);
return NO;
}
// 通过runTime创建类
- (Class)makeKVOClassWithOriginalClassName:(NSString *)className
{
NSString *kvoClassName = [KVOClassPrefix stringByAppendingString:className];
Class KVOClass = NSClassFromString(kvoClassName);
if (KVOClass) {
return KVOClass;
}
// objc_allocateClassPair 创建类
KVOClass = objc_allocateClassPair(object_getClass(self), kvoClassName.UTF8String, 0);
return KVOClass;
}
// 通过key获取对应的setterName
NSString *setterName(NSString *key) {
if (key.length == 0) {
@throw [NSException exceptionWithName:@"KVO Error" reason:@"没有对应的key" userInfo:nil];
}
NSString *firstCharacter = [key substringToIndex:1];
NSString *Name = [[firstCharacter uppercaseString] stringByAppendingString:[key substringFromIndex:1]];
return [NSString stringWithFormat:@"set%@:", Name];
}
最后一步,把这个观察的相关信息存在 associatedObject 里。观察的相关信息(观察者,被观察的 key, 和传入的 block )封装在 KVObServerInfo 类里。
// 创建一个用于存放观察者info的类
@interface KVObServerInfo: NSObject
// 观察者属性
@property (nonatomic, weak) id observer;
// key属性
@property (nonatomic, copy) NSString *key;
// 回调block
@property (nonatomic, copy) ObsverserBlock block;
@end
@implementation KVObServerInfo
// 初始化方法
- (instancetype)initWithObserver:(id)observer forKey:(NSString *)key withBlock:(ObsverserBlock)block {
self = [super init];
if (self) {
_observer = observer;
_key = key;
_block = block;
}
return self;
}
@end
3.3 使用
在控制器中引入头文件 #import "NSObject+KVO.h"
创建Student的对象
Student *stu1 = [[Student alloc] init];
// 给对象添加观察者
[stu1 addObserver:self forKey:@"name" withBlock:^(id observerObject, NSString *key, id oldValue, id newValue) {
NSLog(@"%@", oldValue);
NSLog(@"%@", newValue);
}];
stu1.name = @"张三";
stu1.name = @"李四";
到此我们自己手动完成了KVO的实现。
想要学习更多关于runtime的知识,可以参考博客
Objective-C Runtime 运行时之一:类与对象
Objective-C Runtime 运行时之二:成员变量与属性
Objective-C Runtime 运行时之二:成员变量与属性
Objective-C Runtime 运行时之三:方法与消息
Objective-C Runtime 运行时之四:Method Swizzling
Objective-C Runtime 运行时之五:协议与分类
Objective-C Runtime 运行时之六:拾遗