RAC rac_signalForSelector 如何实现对象方法的hook

重温Objective-C的消息机制

消息转发机制:

  1. 首先在该类的缓存方法列表cache_method_list中查找,是否存在相关方法
  2. 上一步中若没有命中,则从方法列表 objc_method_list中查找
  3. 上一步中若没有命中,则从父类super的方法列表 objc_method_list中查找,直至根类NSObject
  4. 上一步中若没有命中,则进入消息转发流程,一共分为三步:类的动态方法解析、备用接收者对象、完整消息转发
  5. 动态方法解析:也就是 +(BOOL)resolveClassMethod:方法或+(BOOL)resolveInstanceMethod:(SEL)sel方法。该方法允许向当前对象添加方法实现。
1
2
3
4
5
6
7
8
9
+(BOOL)resolveInstanceMethod:(SEL)aSEL
{
if(aSEL == @selector(doFoo:))
{
class_addMethod([self class],aSEL,(IMP)fooMethod,"v@:");
return YES;
}
return [super resolveInstanceMethod];
}
  1. 备用接收者对象:– (id)forwardingTargetForSelector:(SEL)aSelector 方法,
    该方法提供一次机会引导Objective-C RunTime 到备用接收者对象上。
1
2
3
4
5
6
7
-(id)forwardingTargetForSelector:(SEL)aSelector{
if(aSelector == @selector(doFoo:)){
return alternateObject;
}
return [super forwardingTargetForSelector:aSelector];

}
  1. 完整消息转发:– (void)forwardInvocation:(NSInvocation *)anInvocation方法,NSInvocation 是Objective-C 消息的对象形式,它包含了消息的所有信息,这也就意味着,一旦有了NSInvocation 对象,你就可以改变这个消息的所有信息,包括目标对象、selector以及参数。例如可以这样做(当然RAC与Aspect所做的事情远远不止这么简单啦):
1
2
3
4
5
6
7
8
9
-(void)forwardInvocation:(NSInvocation *)invocation
{
SEL invSEL = invocation.selector;
if([altObject respondsToSelector:invSEL]) {
[invocation invokeWithTarget:altObject];
} else {
[self doesNotRecognizeSelector:invSEL];
}
}

rac_signalForSelector 源码走读

- (RACSignal *)rac_signalForSelector:(SEL)selector方法位于NSObject+RACSelectorSignal 这个category下。先来看一下.h 头文件。头文件只对外暴露了以下两个方法。

1
2
3
4
- (RACSignal *)rac_signalForSelector:(SEL)selector;


- (RACSignal *)rac_signalForSelector:(SEL)selector fromProtocol:(Protocol *)protocol;

再来看一下.m 文件

1
2
3
4
5
6
7
8
9
10
11
12
13

- (RACSignal *)rac_signalForSelector:(SEL)selector {
NSCParameterAssert(selector != NULL);

return NSObjectRACSignalForSelector(self, selector, NULL);
}

- (RACSignal *)rac_signalForSelector:(SEL)selector fromProtocol:(Protocol *)protocol {
NSCParameterAssert(selector != NULL);
NSCParameterAssert(protocol != NULL);

return NSObjectRACSignalForSelector(self, selector, protocol);
}

这两方法最终都调用了C函数NSObjectRACSignalForSelector

  1. 获取Selector的别名aliasSelector
  2. 是否存在关联对象,有则跳至步骤8
  3. 替换类RACSwizzleClass(self)
  4. 获取替换的类,这一步主要是替换了原类中forwardInvocation:的实现。
  5. 创建RACSubject对象,并设置关联对象
  6. 获取原方法class_getInstanceMethod(class, selector);
  7. 若原方法不存在,则向该类添加方法 class_addMethod(class, selector, _obj 大专栏  RAC rac_signalForSelector 如何实现对象方法的hookc_msgForward, typeEncoding)。值得注意的是,方法体为_objc_msgForward,即上一节中提到的完整消息转发方法的方法体。
  8. 若原方法存在,则向该类添加aliasSelector,其实现即为原方法的实现,并将原方法的实现替换为_objc_msgForward
  9. 返回RACSubject对象

到此为止,rac_signalForSelector 的全部工作便是将目标selector的实现替换成了消息转发。

接下来,看看消息转发的实现部分,也就是步骤2中的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

static void RACSwizzleForwardInvocation(Class class) {
SEL forwardInvocationSEL = @selector(forwardInvocation:);
Method forwardInvocationMethod = class_getInstanceMethod(class, forwardInvocationSEL);

// Preserve any existing implementation of -forwardInvocation:.
void (*originalForwardInvocation)(id, SEL, NSInvocation *) = NULL;
if (forwardInvocationMethod != NULL) {
originalForwardInvocation = (__typeof__(originalForwardInvocation))method_getImplementation(forwardInvocationMethod);
}


id newForwardInvocation = ^(id self, NSInvocation *invocation) {
BOOL matched = RACForwardInvocation(self, invocation);
if (matched) return;

if (originalForwardInvocation == NULL) {
[self doesNotRecognizeSelector:invocation.selector];
} else {
originalForwardInvocation(self, forwardInvocationSEL, invocation);
}
};

class_replaceMethod(class, forwardInvocationSEL, imp_implementationWithBlock(newForwardInvocation), "v@:@");
}

源码很简单,就是hook了forwardInvocation:方法,当触发完整消息转发时,首先交由RACForwardInvocation响应,若RACForwardInvocation响应了则结束消息转发,否则走原消息转发流程。

接下来看看RACForwardInvocation的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

static BOOL RACForwardInvocation(id self, NSInvocation *invocation) {
SEL aliasSelector = RACAliasForSelector(invocation.selector);
RACSubject *subject = objc_getAssociatedObject(self, aliasSelector);

Class class = object_getClass(invocation.target);
BOOL respondsToAlias = [class instancesRespondToSelector:aliasSelector];
if (respondsToAlias) {
invocation.selector = aliasSelector;
[invocation invoke];
}

if (subject == nil) return respondsToAlias;

[subject sendNext:invocation.rac_argumentsTuple];
return YES;
}
  1. 获取selector的别名 aliasSelector
  2. 获取关联对象subject
  3. 执行 aliasSelector,并通过subject将返回值以RACTuple的形式发送出去。

总结一下RAC实现原理:RAC利用RunTime机制将所要监听的方法,全部转发到forwardInvocation:,并像class添加了别名方法aliasSelector,其方法体即原方法的方法体。那么当外部调用原方法时,就会触发消息转发流程。而RAC拦截了forwardInvocation:,并执行别名方法aliasSelector,最后将返回结果发送出去。

RAC在实现过程中,对Runtime的使用相当的深入。针对各种情况的考虑也是相当的周全,其实现也相当严谨,特别值得学习。

深度改造Runtime的弊端

RAC 通过深度改造对象的消息机制以达到AOP的目的,对于日常开发来说相当便利。不过值得注意的是:当一个项目内存在多个库深度改造对象的消息机制,就会产生不可避免的冲突,比如ASpect这个库,它的实现原理有RAC完全一样,应该都是借鉴了KVO的实现方式,唯一的不同点在于ASpect的消息forwardInvocation:实现比RAC稍微多了一步:当对象无法响应selector时,会调用 doesNotRecognizeSelector: 抛出异常。

若同时使用这两个库对同一对象的同一方法Hook,那么该方法将无法被执行,并存在Crash隐患。

经过以上的研究,对Runtime有了更深入的了解。

上一篇:JavaScript中的getUserMedia()在浏览器中规范化.非法调用


下一篇:Dubbo 系列(07-5)集群容错 - Mock