zoukankan      html  css  js  c++  java
  • 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有了更深入的了解。

  • 相关阅读:
    kkfileview v2.0 发布,文件在线预览项目方案
    margin:0 auto 与 text-align:center 的区别
    div页面居中(上下左右)
    jquery 实现页面拖拽并保存到cookie
    移动开发框架,第【一】弹:QuoJs 官方文档(汉化版)
    javascript 匿名函数的理解,js括号中括function 如(function(){})
    百度,淘宝,腾讯三大巨头HTML页面规范分解
    Javascript触屏手势库-JTouch(更新V1.1)
    移动设备、手机浏览器Javascript滑动事件代码
    jQuery 实现上下,左右滑动
  • 原文地址:https://www.cnblogs.com/lijianming180/p/12389466.html
Copyright © 2011-2022 走看看