在上一篇文章《函数式编程 - 实现响应式框架》中,我实现了一个很是简单小巧的函数式响应式框架,并对它作了与Cocoa相关的一些扩展,好比支持将UIControl
的用户触发事件以及Notification
转换成响应式的流,供咱们进行流转换以及订阅。在其中有一个比较重要的扩展我尚未去实现,那就是对Runtime
的适配。经过对Runtime
的适配,咱们就能监听某个方法的调用,包括协议的方法(尽管此时方法尚未被实现)。因为此部分技术更可能是偏向于Runtime
,因此这篇文章并不概括于“函数式编程”范畴。本文的重点将放在对Objective-C
中Runtime
的探讨上,在最后才将响应式框架与适配好的Runtime
结合起来。git
这篇文章的主要思想及实现,参考自ReactiveCocoa
。github
咱们的目标就是要完成一件事:监听
,而且针对的是方法的调用(消息发送)监听:每次方法被调用时,咱们就能收到监听的回调,而且获得当时传入方法中的参数值。其能带给咱们的价值是很是大的,咱们能在方法不改变其原有的工做流程、返回数据的基础上,对方法进行特定的扩展。编程
这是一种很是暗黑的魔法,它能给方法动态提供了一种二次的实现,不只只是单纯地增添方法的功能,还能作到从实现埋点统计、Log输出到AOP(面向切面编程)
的运用,甚至它还能实现咱们本身的KVO
(监听setter方法)。数组
另外,咱们还须要考虑的一点是代理模式。Cocoa中代理模式使用得很是频繁,不过这种模式使用起来并非十分简便:咱们须要让特定类去实现代理接口,并提供相应抽象方法的实现,而经过对Runtime
进行适配后,咱们不须要代理类去作相关实现就能对相应的代理抽象方法进行调用监听。缓存
咱们来看下适配后的最终效果。这里咱们展现的是经过闭包回调的状况,而关联了响应式框架的效果在后面才提到。闭包
[self listen: @selector(touchesBegan:withEvent:) in: nil with: ^(NSArray * _Nonnull parameters) {
NSLog(@"Touches began");
}];
[self listen: @selector(tableView:didSelectRowAtIndexPath:) in: @protocol(UITableViewDelegate) with: ^(NSArray * _Nonnull parameters) {
if (parameters.count != 2) return;
NSIndexPath *indexPath = parameters[1];
NSLog(@"Did selected row %ld", (long)indexPath.row);
}];复制代码
// 普通方法调用监听
listen(#selector(ViewController.touchesBegan(_:with:)), in: nil) { _ in
print("Touches began")
}
// 代理方法调用监听
// 注:此时self所属类并不须要实现`tableView(_:didSelectRowAt:)`方法
listen(#selector(UITableViewDelegate.tableView(_:didSelectRowAt:)), in: UITableViewDelegate.self) { parameters in
// parameters则为调用特定方法时所传入的参数,以`[Any]`数组的形式呈现
guard
parameters.count == 2,
let indexPath = parameters[1] as? IndexPath
else { return }
print("Did selected row \(indexPath.row)")
}
// 设置`TableView`代理
_tableView.delegate = self复制代码
首先来讲下监听回调的基本原理,下面是一张原理示意图:框架
咱们自顶向下看,首先咱们能够经过performSelector
或者[obj message]
的形式用特定的Selector
向对象发送消息,此时Runtime
系统会进入消息派发的流程中,若是咱们什么都不作,消息派发流程最终就会找到相应的方法实现,从而调用实现获得结果返回,若咱们要监听方法的调用,则须要在消息派发的过程当中动点手脚,将方法调用的事件从里面回调出来。函数式编程
当消息发送时,在消息派发的流程中咱们不只须要调用原来相应的方法实现,还须要回调信息来通知外界。要实现这个过程,咱们用到了一个十分巧妙的方法。函数
这里我列出方法的步骤:ui
Selector
给它,并将原始方法(被监听的方法)的Implementation(实现)
赋予给这个新的方法。
补充:在OC中,方法由
Selector(选择器)
以及Implementation(实现)
构成。在OC中发送消息,首先是利用选择器找到对应的方法,将其中的方法实现提取出来,而后再调用方法实现从而获得最终结果。
_objc_msgForward
。
补充:OC中方法实现的类型其实都是函数指针,而
_objc_msgForward
的类型也是函数指针,它的做用就是触发完整的消息转发过程。当咱们利用方法选择器往对象发送消息时,Runtime会前后在方法缓存列表、类对象方法列表、派生类对象及上层若干派生类对象的方法列表中查找方法,若找到方法,便可提取方法实现进行调用,若最终依旧找不到方法,则运行时会直接调用_objc_msgForward
,此时就进入消息的转发流程中。将原始方法的方法实现替换成_objc_msgForward
,当咱们用原始方法的Selector
发送消息时,Runtime会直接进入消息转发流程。
forwardInvocation:
方法,在里面作两件事情:①提取方法调用时传入的参数,向外界回调。②将消息转发给在第一步建立的新方法。
补充:由于在第二步中,咱们已经将原始方法的实现替换成了
_objc_msgForward
,当咱们经过原始方法的选择器发送消息时,会走方法转发的流程,因为咱们并无重写resolveInstanceMethod:
和forwardingTargetForSelector:
方法,因此最终咱们会进入forwardInvocation:
方法,而在里面咱们须要作的,是向外界回调信息以及将消息转发到新方法中,从而调用原始的方法实现。
咱们要实现对消息派发流程的修改,则须要对forwardInvocation:
方法进行重写,然而,这种重写并非像平时同样简单地在类或扩展中提供本身重写实现后的方法,咱们须要使用到一个技术:isa-swizzling
。
咱们知道,Runtime是利用isa-swizzling
技术来实现KVO
的,在运行时重写相应属性的setter
方法,而咱们这里也是利用isa-swizzling
去重写forwardInvocation:
方法。
如上图所示,isa-swizzling
其实就是在运行时动态建立了一个中间层的类对象,这个类对象继承自旧的类对象(isa),而后重写相应的方法,最后,运行时将实例中的类对象(isa)替换成这个中间层类对象。通过isa-swizzling
处理过的实例,它的isa
已经替换了,因此此时向它发送消息,方法首先是在新的中间层类对象的方法列表中进行查找,若中间层类对象重写了方法,Runtime则会调用这个重写方法的实现。
这个抛出一个问题:
为何监听方法调用不直接使用method-swizzling(方法交换)?
为了监听方法的调用,上面所述的各类原理略为复杂,而使用method-swizzling
依然可以向方法提供二次的实现、监听方法调用,且这样子写起来更为简便,为何不直接使用方法交换,还要进行isa-swizzling跟方法重写?这里有两个缘由:
isa-swizzling
原理,咱们能够将一系列操做封装起来,最终只需经过一个方法便可完成对方法的调用监听。load
方法,在里面实现方法交换的相关逻辑,这样作会对原始类对象形成污染。举个例子,若咱们经过方法交换重写了类A中的方法α,那么在整个项目中,当咱们向全部属于类A的实例发送方法α的消息,最终都会走重写后的实现。假如咱们只须要监听某个指定实例的方法调用时,咱们对这个方法进行方法交换,那么最终改变行为的不仅是这个指定的实例,而是全部与此同类的实例。为此,咱们须要利用isa-swizzling
,它作的,就是修改某个指定实例的类对象(isa),到最后,改变行为的只是这个实例,由于此时它已经不属于本来所属旧类对象的实例了。由于这里涉及较多Runtime
的API,因此整个实现我使用的是Objective-C
语言。
整个实现是在NSObject
的扩展NSObject+Runtime
里面的,因此这里我建立了这个扩展,并提供如下的接口:
typedef void (^MessageDidSendCallback) (NSArray * _Nonnull);
@interface NSObject (Runtime)
- (void)listen:(nonnull SEL)selector in:(nullable Protocol *)protocol with:(nonnull MessageDidSendCallback)callback;
@end复制代码
MessageDidSendCallback
就是方法调用回调的block类型,它具备一个数组类型的参数,做用是传递方法被调用时传入的参数。NSObject
添加了监听方法,方法有三个参数,第一个就是咱们须要监听的方法的选择器,第二个则为可空的协议类型(当指定的方法属于协议方法,传入协议对象,反之则传nil),第三个就是监听回调的block。当指定的方法被调用时,callback就会被调用,咱们能够从它的数组参数中获取到方法被调用时传入的参数值。- (void)listen:(SEL)selector in:(Protocol *)protocol with:(MessageDidSendCallback)callback {
SEL runtimeSelector = _modifySelector(selector);
// 引用闭包
objc_setAssociatedObject(self, runtimeSelector, callback, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// isa-swizzling
Class interlayerClass = _swizzleClass(self);
Method originalMethod = class_getInstanceMethod(interlayerClass, selector);
IMP originalImplementation = method_getImplementation(originalMethod);
// 判断是否具备该方法
// 若是没有,试图在指定的协议中寻找
if (!originalMethod) {
if (!protocol) return;
struct objc_method_description des = protocol_getMethodDescription(protocol, selector, YES, YES);
if (!des.name)
des = protocol_getMethodDescription(protocol, selector, NO, YES);
if (des.types)
class_addMethod(interlayerClass, selector, _objc_msgForward, des.types);
}
// 若是原始方法没有作替换
// 则将原始方法的实现改成_objc_msgForward
else if (originalImplementation != _objc_msgForward) {
const char *typeEncoding = method_getTypeEncoding(originalMethod);
class_addMethod(interlayerClass, runtimeSelector, originalImplementation, typeEncoding);
class_replaceMethod(interlayerClass, selector, _objc_msgForward, typeEncoding);
}
}复制代码
从上往下看,首先调用_modifySelector
在原来的方法选择器基础上通过修饰,获得新的方法的方法选择器,修饰的过程比较简单:
// 用于在原有的基础上标示Selector以及中间层类对象的名字,便于区分
static NSString * const _prefixName = @"_Runtime_";
// 修饰Selector,返回通过前缀名拼接的Selector
static SEL _Nonnull _modifySelector(SEL _Nonnull selector) {
NSString *originalName = NSStringFromSelector(selector);
return NSSelectorFromString([_prefixName stringByAppendingString:originalName]);
}复制代码
咱们经过拼接一个修饰字符串到原来选择器的字符串上,并利用这个拼接后的字符串经过NSSelectorFromString
转换成新的选择器。
接下来经过使用这个新的方法选择器做为key,将listen
方法传入的block对象设置成实例本身的关联对象,目的是维持callback block的存活,以及便于咱们后期在forwardInvocation:
方法中获取到这个block。
接着,咱们经过函数_swizzleClass
进行isa-swizzling
操做(后面会说到),这个函数返回的是刚建立好的中间层类对象。
拿到这个中间层类对象后,咱们就可以在里面以指定的旧方法为基础,建立一个新的方法(利用旧的方法实现以及新的方法选择器),并将旧方法的方法实现替换成_objc_msgForward
。此时操做的是中间层类对象,因此不会污染到本来的类对象。在这里须要注意的有:
listen
传入的协议并不为空,则在协议里面查找方法,若协议中确实具备此方法,那咱们就动态往中间层类对象中添加这个协议方法,且方法的实现为_objc_msgForward
。此时这个协议方法并不作任何事,它的做用只为了在这个方法被调用时发送回调。// 关联对象Key,是否已经存在中间层类对象
static void *_interlayerClassExist = &_interlayerClassExist;
// isa-swizzling
static Class _Nullable _swizzleClass(id _Nonnull self) {
Class originalClass = object_getClass(self);
// 若是在以前已经替换了isa,则只需直接返回
if ([objc_getAssociatedObject(self, _interlayerClassExist) boolValue])
return originalClass;
Class interlayerClass;
Class presentClass = [self class];
// 若以前没有手动替换过isa,可是两种方式获取到的Class不一样
// 说明此对象在以前被动态地替换isa,(多是涉及到了KVO)
// 这时候咱们使用的中间层类对象就不须要动态建立一个了,直接使用以前动态建立的就行
if (presentClass != originalClass) {
// 重写方法
_swizzleForwardInvocation(originalClass);
_swizzleRespondsToSelector(originalClass);
_swizzleMethodSignatureForSelector(originalClass);
interlayerClass = originalClass;
}
else {
const char *interlayerClassName = [_prefixName stringByAppendingString:NSStringFromClass(originalClass)].UTF8String;
// 首先判断Runtime中是否已经注册过此中间层类
// 若没有注册,则动态建立中间层类而且重写其中的指定方法,最后进行注册
interlayerClass = objc_getClass(interlayerClassName);
if (!interlayerClass) {
// 基于原始的类对象建立新的中间层类对象
interlayerClass = objc_allocateClassPair(originalClass, interlayerClassName, 0);
if (!interlayerClass) return nil;
// 重写方法
_swizzleForwardInvocation(interlayerClass);
_swizzleRespondsToSelector(interlayerClass);
_swizzleMethodSignatureForSelector(interlayerClass);
_swizzleGetClass(interlayerClass, presentClass);
// 注册中间层类对象
objc_registerClassPair(interlayerClass);
}
}
// isa替换
object_setClass(self, interlayerClass);
objc_setAssociatedObject(self, _interlayerClassExist, @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return interlayerClass;
}复制代码
咱们以_interlayerClassExist
做为key,为对象设置了一个布尔类型的关联对象值,用于标示咱们在以前是否已经为此对象进行过isa-swizzling
,若isa已经替换过,则直接用object_getClass
返回中间层类对象。
接下来作的事情比较微妙,咱们比较了使用getClass
方法以及object_getClass
获取到的两个类对象,这样作的目的是为了判别这个对象在以前是否也在其余地方进行过isa-swzzling
,由于object_getClass
获取到的类对象是实际的类对象(isa),而getClass
方法可能已经被重写了,获取到的类对象多是虚假的,其中,最典型的isa-swizzling
莫过于KVO
了。当咱们判别到对象在以前已经进行过isa-swizzling
了,咱们就不必再本身建立一个中间层类对象了,直接使用现有的就行,反正也不会污染到本来的类对象。当咱们判别到对象在以前没有进行过isa-swizzling
,咱们就须要手动建立一个中间层类对象,这个类对象继承自原来的类对象,且名字也是在旧类对象之上进行稍微的修饰。
接着,咱们就对中间层类对象的某些方法进行重写,这里主要有四个方法须要重写:
respondsToSelector: 考虑到中间层类对象中虽然没有实现指定的方法,可是在咱们传入的协议中确实找到了它,因此已经在中间层类对象动态添加了,此时咱们就须要重写responseToSelector,使得咱们经过responseToSelector可以得知对象已经实现了此方法。举个例子,咱们平时调用代理方法时,总会加上一句判断:
if ([_delegate respondsToSelector: @selector(XXX)]) {
[_delegate XXX];
}复制代码
若咱们只是简单地为中间层类对象动态添加协议的方法,却没有重写respondsToSelector
,那个此时这个方法也不可能会被调用。
// 混淆getClass方法
static void _swizzleGetClass(Class _Nonnull class, Class _Nonnull expectedClass) {
SEL selector = @selector(class);
Method getClassMethod = class_getInstanceMethod(class, selector);
id newImp = ^(id self) {
return expectedClass;
};
class_replaceMethod(class, selector, imp_implementationWithBlock(newImp), method_getTypeEncoding(getClassMethod));
}复制代码
能够看到,咱们重写方法使用的不是方法交换技术,而是直接经过class_replaceMethod
,将新的方法实现替换进方法中,而新的方法实现咱们将利用block来建立。这里须要注意的是,经过imp_implementationWithBlock
函数,咱们能够利用block建立方法实现,而这个block的类型有所约束:返回类型跟方法实现的同样,而在参数中,第一个参数必须为id
类型,表明此时发送消息的实例,后面紧接着的是方法的实际参数。而方法实现的类型中,前两个参数为id
和SEL
,表明发送消息的实例以及选择器,后面才接方法的实际参数。
// 混淆respondsToSelector方法
static void _swizzleRespondsToSelector(Class _Nonnull class) {
SEL originalSelector = @selector(respondsToSelector:);
Method method = class_getInstanceMethod(class, originalSelector);
BOOL (*originalImplementation)(id, SEL, SEL) = (void *)method_getImplementation(method);
id newImp = ^(id self, SEL selector) {
Method method = class_getInstanceMethod(class, selector);
if (method && method_getImplementation(method) == _objc_msgForward) {
if (objc_getAssociatedObject(self, _modifySelector(selector)))
return YES;
}
return originalImplementation(self, originalSelector, selector);
};
class_replaceMethod(class, originalSelector, imp_implementationWithBlock(newImp), method_getTypeEncoding(method));
}复制代码
经过method_getImplementation
函数,咱们能够直接获取到本来的方法实现,方法实现的类型为函数指针,这让咱们能够在后面直接调用它。
中间判断的意义是:此方法是中间层类对象动态建立的,由于此时方法多是类没有实现但协议声明了,若此时实例对这个方法有进行监听,respondsToSelector
则返回YES
,反之则返回NO
,由于这个动态添加的方法只是为了实现方法调用的监听回调,既然实例没有对其进行监听,那么respondsToSelector
直接返回NO
就行。
// 混淆methodSignatureForSelector方法
static void _swizzleMethodSignatureForSelector(Class _Nonnull class) {
SEL msfsSelector = @selector(methodSignatureForSelector:);
Method method = class_getInstanceMethod(class, msfsSelector);
id newIMP = ^(id self, SEL selector) {
Method method = class_getInstanceMethod(class, selector);
if (!method) {
struct objc_super super = {
self,
class_getSuperclass(class)
};
NSMethodSignature *(*sendToSuper)(struct objc_super *, SEL, SEL) = (void *)objc_msgSendSuper;
return sendToSuper(&super, msfsSelector, selector);
}
return [NSMethodSignature signatureWithObjCTypes: method_getTypeEncoding(method)];
};
class_replaceMethod(class, msfsSelector, imp_implementationWithBlock(newIMP), method_getTypeEncoding(method));
}复制代码
这新的实现中,咱们先经过传入的方法选择器找到对应的方法,若此时方法存在,咱们经过方法的类型编码建立方法签名并返回,若此时方法不存在,咱们则调用父类的methodSignatureForSelector
方法。咱们知道,在平时咱们经过[super XXX]
向父类发送消息时,最终都是转换成objc_msgSendSuper
的形式,而此时咱们是使用block来建立新的方法实现,不能使用到[super XXX]
这种形式,因此咱们直接经过objc_msgSendSuper
来向父类发送消息。
// 混淆forwardInvocation方法
static void _swizzleForwardInvocation(Class _Nonnull class) {
SEL fiSelector = @selector(forwardInvocation:);
Method fiMethod = class_getInstanceMethod(class, fiSelector);
void (*originalFiImp)(id, SEL, NSInvocation *) = (void *)method_getImplementation(fiMethod);
id newFiImp = ^(id self, NSInvocation *invocation) {
SEL runtimeSelector = _modifySelector(invocation.selector);
MessageDidSendCallback callback = (MessageDidSendCallback)objc_getAssociatedObject(self, runtimeSelector);
if (!callback) {
if (originalFiImp)
originalFiImp(self, fiSelector, invocation);
else
[self doesNotRecognizeSelector: invocation.selector];
} else {
if ([self respondsToSelector: runtimeSelector]) {
invocation.selector = runtimeSelector;
[invocation invoke];
}
callback(_getArguments(invocation));
}
};
class_replaceMethod(class, fiSelector, imp_implementationWithBlock(newFiImp), method_getTypeEncoding(fiMethod));
}复制代码
在新的实现中,咱们经过NSInvocation
转发消息到新的方法中,方式就是直接设置invocation的selector为新方法的选择器。另外,咱们经过_getArguments
函数从invocation中把传入方法的参数提取出来,传入在以前设置好的callback block关联对象进行调用,这样咱们就可以将方法调用回调到外界了。
咱们看下_getArguments
函数:
static NSArray * _Nonnull _getArguments(NSInvocation * _Nonnull invocation) {
NSUInteger count = invocation.methodSignature.numberOfArguments;
// 除去开头的两个参数(id, SEL),表明实例本身以及方法的选择器
NSMutableArray *arr = [NSMutableArray arrayWithCapacity:count - 2];
for (NSUInteger i = 2; i < count; i ++)
[arr addObject:_getArgument(invocation, i)];
return arr;
}
// 获取参数,copy from `ReactiveCocoa`
static id _Nonnull _getArgument(NSInvocation * _Nonnull invocation, NSUInteger index) {
const char *argumentType = [invocation.methodSignature getArgumentTypeAtIndex:index];
#define RETURN_VALUE(type) \
else if (strcmp(argumentType, @encode(type)) == 0) {\
type val = 0; \
[invocation getArgument:&val atIndex:index]; \
return @(val); \
}
// Skip const type qualifier.
if (argumentType[0] == 'r') {
argumentType++;
}
if (strcmp(argumentType, @encode(id)) == 0
|| strcmp(argumentType, @encode(Class)) == 0
|| strcmp(argumentType, @encode(void (^)(void))) == 0
) {
__unsafe_unretained id argument = nil;
[invocation getArgument:&argument atIndex:index];
return argument;
}
RETURN_VALUE(char)
RETURN_VALUE(short)
RETURN_VALUE(int)
RETURN_VALUE(long)
RETURN_VALUE(long long)
RETURN_VALUE(unsigned char)
RETURN_VALUE(unsigned short)
RETURN_VALUE(unsigned int)
RETURN_VALUE(unsigned long)
RETURN_VALUE(unsigned long long)
RETURN_VALUE(float)
RETURN_VALUE(double)
RETURN_VALUE(BOOL)
RETURN_VALUE(const char *)
else {
NSUInteger size = 0;
NSGetSizeAndAlignment(argumentType, &size, NULL);
NSCParameterAssert(size > 0);
uint8_t data[size];
[invocation getArgument:&data atIndex:index];
return [NSValue valueWithBytes:&data objCType:argumentType];
}
}复制代码
_getArguments
函数中,咱们获取到参数数量,并过滤掉前面两个参数(由于前面两个参数分别表明调用方法的实例以及此方法的选择器,并非实际传入方法的参数),再一个个经过_getArgument
函数获取到最终的值。_getArgument
函数的机理有些复杂,我直接拷贝自ReactiveCocoa
的源码。
#import "NSObject+Runtime.h"
#import <objc/runtime.h>
#import <objc/message.h>
static SEL _Nonnull _modifySelector(SEL _Nonnull selector);
static Class _Nullable _swizzleClass(id _Nonnull self);
@implementation NSObject (Runtime)
- (void)listen:(SEL)selector in:(Protocol *)protocol with:(MessageDidSendCallback)callback {
SEL runtimeSelector = _modifySelector(selector);
// 引用闭包
objc_setAssociatedObject(self, runtimeSelector, callback, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// isa-swizzling
Class interlayerClass = _swizzleClass(self);
Method originalMethod = class_getInstanceMethod(interlayerClass, selector);
IMP originalImplementation = method_getImplementation(originalMethod);
// 判断是否具备该方法
// 若是没有,试图在指定的协议中寻找
if (!originalMethod) {
if (!protocol) return;
struct objc_method_description des = protocol_getMethodDescription(protocol, selector, YES, YES);
if (!des.name)
des = protocol_getMethodDescription(protocol, selector, NO, YES);
if (des.types)
class_addMethod(interlayerClass, selector, _objc_msgForward, des.types);
}
// 若是原始方法没有作替换
// 则将原始方法的实现改成_objc_msgForward
else if (originalImplementation != _objc_msgForward) {
const char *typeEncoding = method_getTypeEncoding(originalMethod);
class_addMethod(interlayerClass, runtimeSelector, originalImplementation, typeEncoding);
class_replaceMethod(interlayerClass, selector, _objc_msgForward, typeEncoding);
}
}
@end
#pragma mark - Private 私有
// 用于在原有的基础上标示Selector以及中间层类对象的名字,便于区分
static NSString * const _prefixName = @"_Runtime_";
// 关联对象Key,是否已经存在中间层类对象
static void *_interlayerClassExist = &_interlayerClassExist;
// 获取参数
static id _Nonnull _getArgument(NSInvocation * _Nonnull invocation, NSUInteger index) {
const char *argumentType = [invocation.methodSignature getArgumentTypeAtIndex:index];
#define RETURN_VALUE(type) \
else if (strcmp(argumentType, @encode(type)) == 0) {\
type val = 0; \
[invocation getArgument:&val atIndex:index]; \
return @(val); \
}
// Skip const type qualifier.
if (argumentType[0] == 'r') {
argumentType++;
}
if (strcmp(argumentType, @encode(id)) == 0
|| strcmp(argumentType, @encode(Class)) == 0
|| strcmp(argumentType, @encode(void (^)(void))) == 0
) {
__unsafe_unretained id argument = nil;
[invocation getArgument:&argument atIndex:index];
return argument;
}
RETURN_VALUE(char)
RETURN_VALUE(short)
RETURN_VALUE(int)
RETURN_VALUE(long)
RETURN_VALUE(long long)
RETURN_VALUE(unsigned char)
RETURN_VALUE(unsigned short)
RETURN_VALUE(unsigned int)
RETURN_VALUE(unsigned long)
RETURN_VALUE(unsigned long long)
RETURN_VALUE(float)
RETURN_VALUE(double)
RETURN_VALUE(BOOL)
RETURN_VALUE(const char *)
else {
NSUInteger size = 0;
NSGetSizeAndAlignment(argumentType, &size, NULL);
NSCParameterAssert(size > 0);
uint8_t data[size];
[invocation getArgument:&data atIndex:index];
return [NSValue valueWithBytes:&data objCType:argumentType];
}
}
static NSArray * _Nonnull _getArguments(NSInvocation * _Nonnull invocation) {
NSUInteger count = invocation.methodSignature.numberOfArguments;
// 除去开头的两个参数(id, SEL),表明实例本身以及方法的选择器
NSMutableArray *arr = [NSMutableArray arrayWithCapacity:count - 2];
for (NSUInteger i = 2; i < count; i ++)
[arr addObject:_getArgument(invocation, i)];
return arr;
}
// 修饰Selector,返回通过前缀名拼接的Selector
static SEL _Nonnull _modifySelector(SEL _Nonnull selector) {
NSString *originalName = NSStringFromSelector(selector);
return NSSelectorFromString([_prefixName stringByAppendingString:originalName]);
}
// 混淆forwardInvocation方法
static void _swizzleForwardInvocation(Class _Nonnull class) {
SEL fiSelector = @selector(forwardInvocation:);
Method fiMethod = class_getInstanceMethod(class, fiSelector);
void (*originalFiImp)(id, SEL, NSInvocation *) = (void *)method_getImplementation(fiMethod);
id newFiImp = ^(id self, NSInvocation *invocation) {
SEL runtimeSelector = _modifySelector(invocation.selector);
MessageDidSendCallback callback = (MessageDidSendCallback)objc_getAssociatedObject(self, runtimeSelector);
if (!callback) {
if (originalFiImp)
originalFiImp(self, fiSelector, invocation);
else
[self doesNotRecognizeSelector: invocation.selector];
} else {
if ([self respondsToSelector: runtimeSelector]) {
invocation.selector = runtimeSelector;
[invocation invoke];
}
callback(_getArguments(invocation));
}
};
class_replaceMethod(class, fiSelector, imp_implementationWithBlock(newFiImp), method_getTypeEncoding(fiMethod));
}
// 混淆getClass方法
static void _swizzleGetClass(Class _Nonnull class, Class _Nonnull expectedClass) {
SEL selector = @selector(class);
Method getClassMethod = class_getInstanceMethod(class, selector);
id newImp = ^(id self) {
return expectedClass;
};
class_replaceMethod(class, selector, imp_implementationWithBlock(newImp), method_getTypeEncoding(getClassMethod));
}
// 混淆respondsToSelector方法
static void _swizzleRespondsToSelector(Class _Nonnull class) {
SEL originalSelector = @selector(respondsToSelector:);
Method method = class_getInstanceMethod(class, originalSelector);
BOOL (*originalImplementation)(id, SEL, SEL) = (void *)method_getImplementation(method);
id newImp = ^(id self, SEL selector) {
Method method = class_getInstanceMethod(class, selector);
if (method && method_getImplementation(method) == _objc_msgForward) {
if (objc_getAssociatedObject(self, _modifySelector(selector)))
return YES;
}
return originalImplementation(self, originalSelector, selector);
};
class_replaceMethod(class, originalSelector, imp_implementationWithBlock(newImp), method_getTypeEncoding(method));
}
// 混淆methodSignatureForSelector方法
static void _swizzleMethodSignatureForSelector(Class _Nonnull class) {
SEL msfsSelector = @selector(methodSignatureForSelector:);
Method method = class_getInstanceMethod(class, msfsSelector);
id newIMP = ^(id self, SEL selector) {
Method method = class_getInstanceMethod(class, selector);
if (!method) {
struct objc_super super = {
self,
class_getSuperclass(class)
};
NSMethodSignature *(*sendToSuper)(struct objc_super *, SEL, SEL) = (void *)objc_msgSendSuper;
return sendToSuper(&super, msfsSelector, selector);
}
return [NSMethodSignature signatureWithObjCTypes: method_getTypeEncoding(method)];
};
class_replaceMethod(class, msfsSelector, imp_implementationWithBlock(newIMP), method_getTypeEncoding(method));
}
// isa-swizzling
static Class _Nullable _swizzleClass(id _Nonnull self) {
Class originalClass = object_getClass(self);
// 若是在以前已经替换了isa,则只需直接返回
if ([objc_getAssociatedObject(self, _interlayerClassExist) boolValue])
return originalClass;
Class interlayerClass;
Class presentClass = [self class];
// 若以前没有手动替换过isa,可是两种方式获取到的Class不一样
// 说明此对象在以前被动态地替换isa,(多是涉及到了KVO)
// 这时候咱们使用的中间层类对象就不须要动态建立一个了,直接使用以前动态建立的就行
if (presentClass != originalClass) {
// 重写方法
_swizzleForwardInvocation(originalClass);
_swizzleRespondsToSelector(originalClass);
_swizzleMethodSignatureForSelector(originalClass);
interlayerClass = originalClass;
}
else {
const char *interlayerClassName = [_prefixName stringByAppendingString:NSStringFromClass(originalClass)].UTF8String;
// 首先判断Runtime中是否已经注册过此中间层类
// 若没有注册,则动态建立中间层类而且重写其中的指定方法,最后进行注册
interlayerClass = objc_getClass(interlayerClassName);
if (!interlayerClass) {
// 基于原始的类对象建立新的中间层类对象
interlayerClass = objc_allocateClassPair(originalClass, interlayerClassName, 0);
if (!interlayerClass) return nil;
// 重写方法
_swizzleForwardInvocation(interlayerClass);
_swizzleRespondsToSelector(interlayerClass);
_swizzleMethodSignatureForSelector(interlayerClass);
_swizzleGetClass(interlayerClass, presentClass);
// 注册中间层类对象
objc_registerClassPair(interlayerClass);
}
}
// isa替换
object_setClass(self, interlayerClass);
objc_setAssociatedObject(self, _interlayerClassExist, @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return interlayerClass;
}复制代码
自此咱们就完成了对Runtime的适配,如今咱们能够直接利用listen
方法来实现对指定方法的监听了。
有了前面的基础,将响应式框架向Runtime
扩展起来就十分简单了。
因为前面我编写响应式框架是使用的是Swift语言,因此这里我也是经过Swift语言进行扩展:
extension NSObject {
func listen(_ selector: Selector, in proto: Protocol? = nil) -> Signal<[Any]> {
return Signal { [weak self] observer in
self?.listen(selector, in: proto, with: observer.sendNext)
}
}
}复制代码
如今,咱们能够把玩一下通过Runtime扩展后的响应式框架了:
listen(#selector(UITableViewDelegate.tableView(_:didSelectRowAt:)), in: UITableViewDelegate.self)
.map { $0[1] as! IndexPath }
.map { [weak self] in self?._data[$0.row] }
.subscribe(next: { [weak self] in
guard let uid = $0 else { return }
self?.navigationController?.pushViewController(MyViewController(uid: uid), animated: true)
})
_tableView.delegate = self复制代码
文章主要思想及实现参考自ReactiveCocoa
,实现的代码可能存在某些缺漏或不足,若你们有兴趣可直接查看ReactiveCocoa
的源码:ReactiveCocoa。
本文纯属我的看法,若你们发现文章部分有误,欢迎在评论区提出。