Runtime - 基于isa-swizzling实现消息监听,扩展响应式框架

前言

在上一篇文章《函数式编程 - 实现响应式框架》中,我实现了一个非常简单小巧的函数式响应式框架,并对它做了与Cocoa相关的一些扩展,比如支持将UIControl的用户触发事件以及Notification转换成响应式的流,供我们进行流转换以及订阅。在其中有一个比较重要的扩展我还没有去实现,那就是对Runtime的适配。通过对Runtime的适配,我们就能监听某个方法的调用,包括协议的方法(尽管此时方法还没有被实现)。由于此部分技术更多是偏向于Runtime,所以这篇文章并不归纳于“函数式编程”范畴。本文的重点将放在对Objective-CRuntime的探讨上,在最后才将响应式框架与适配好的Runtime结合起来。

这篇文章的主要思想及实现,参考自ReactiveCocoa

目标

我们的目标就是要完成一件事:监听,并且针对的是方法的调用(消息发送)监听:每次方法被调用时,我们就能收到监听的回调,并且得到当时传入方法中的参数值。其能带给我们的价值是非常大的,我们能在方法不改变其原有的工作流程、返回数据的基础上,对方法进行特定的扩展。

这是一种非常暗黑的魔法,它能给方法动态提供了一种二次的实现,不仅只是单纯地增添方法的功能,还能做到从实现埋点统计、Log输出到AOP(面向切面编程)的运用,甚至它还能实现我们自己的KVO(监听setter方法)。

另外,我们还需要考虑的一点是代理模式。Cocoa中代理模式使用得非常频繁,不过这种模式使用起来并不是十分简便:我们需要让特定类去实现代理接口,并提供相应抽象方法的实现,而通过对Runtime进行适配后,我们不需要代理类去做相关实现就能对相应的代理抽象方法进行调用监听。

最终效果

我们来看下适配后的最终效果。这里我们展示的是通过闭包回调的情况,而关联了响应式框架的效果在后面才提到。

Objective-C

[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);
}];

Swift

// 普通方法调用监听
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系统会进入消息派发的流程中,如果我们什么都不做,消息派发流程最终就会找到相应的方法实现,从而调用实现得到结果返回,若我们要监听方法的调用,则需要在消息派发的过程中动点手脚,将方法调用的事件从里面回调出来。

消息派发原理

当消息发送时,在消息派发的流程中我们不仅需要调用原来相应的方法实现,还需要回调信息来通知外界。要实现这个过程,我们用到了一个十分巧妙的方法。

这里我列出方法的步骤:

  1. 创建一个新的方法,指定一个新的Selector给它,并将原始方法(被监听的方法)的Implementation(实现)赋予给这个新的方法。

    补充:在OC中,方法由Selector(选择器)以及Implementation(实现)构成。在OC中发送消息,首先是利用选择器找到对应的方法,将其中的方法实现提取出来,然后再调用方法实现从而得到最终结果。

  2. 将原始方法的方法实现替换成_objc_msgForward

    补充:OC中方法实现的类型其实都是函数指针,而_objc_msgForward的类型也是函数指针,它的作用就是触发完整的消息转发过程。当我们利用方法选择器往对象发送消息时,Runtime会先后在方法缓存列表、类对象方法列表、派生类对象及上层若干派生类对象的方法列表中查找方法,若找到方法,即可提取方法实现进行调用,若最终依旧找不到方法,则运行时会直接调用_objc_msgForward,此时就进入消息的转发流程中。将原始方法的方法实现替换成_objc_msgForward,当我们用原始方法的Selector发送消息时,Runtime会直接进入消息转发流程。

  3. 重写类对象的forwardInvocation:方法,在里面做两件事情:①提取方法调用时传入的参数,向外界回调。②将消息转发给在第一步创建的新方法。

    补充:因为在第二步中,我们已经将原始方法的实现替换成了_objc_msgForward,当我们通过原始方法的选择器发送消息时,会走方法转发的流程,由于我们并没有重写resolveInstanceMethod:forwardingTargetForSelector:方法,所以最终我们会进入forwardInvocation:方法,而在里面我们需要做的,是向外界回调信息以及将消息转发到新方法中,从而调用原始的方法实现。

消息派发原理1
消息派发原理2

方法重写原理

我们要实现对消息派发流程的修改,则需要对forwardInvocation:方法进行重写,然而,这种重写并不是像平时一样简单地在类或扩展中提供自己重写实现后的方法,我们需要使用到一个技术:isa-swizzling

我们知道,Runtime是利用isa-swizzling技术来实现KVO的,在运行时重写相应属性的setter方法,而我们这里也是利用isa-swizzling去重写forwardInvocation:方法。

isa-swizzling

如上图所示,isa-swizzling其实就是在运行时动态创建了一个中间层的类对象,这个类对象继承自旧的类对象(isa),然后重写相应的方法,最后,运行时将实例中的类对象(isa)替换成这个中间层类对象。经过isa-swizzling处理过的实例,它的isa已经替换了,所以此时向它发送消息,方法首先是在新的中间层类对象的方法列表中进行查找,若中间层类对象重写了方法,Runtime则会调用这个重写方法的实现。

这个抛出一个问题:

为什么监听方法调用不直接使用method-swizzling(方法交换)?

为了监听方法的调用,上面所述的各种原理略为复杂,而使用method-swizzling依然能够向方法提供二次的实现、监听方法调用,且这样子写起来更为简便,为什么不直接使用方法交换,还要进行isa-swizzling跟方法重写?这里有两个原因:

  1. 方法交换实现起来其实并不简便,对于每一个需要二次实现的方法,我们都要编写一套交换的代码(包括新的交换方法),若方法量多,代码则会变得冗长(当然可以考虑使用宏)。而基于上述的isa-swizzling原理,我们可以将一系列操作封装起来,最终只需通过一个方法即可完成对方法的调用监听。
  2. 防止类对象被污染:我们使用方法交换,需要在原始的类对象中进行操作,如一般来说我们会重写类对象的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。此时这个协议方法并不做任何事,它的作用只为了在这个方法被调用时发送回调。

isa-swizzling

// 关联对象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,我们就需要手动创建一个中间层类对象,这个类对象继承自原来的类对象,且名字也是在旧类对象之上进行稍微的修饰。

接着,我们就对中间层类对象的某些方法进行重写,这里主要有四个方法需要重写:

  • forwardInvocation: 重写的原因在上面的原理中已经提到。

  • respondsToSelector: 考虑到中间层类对象中虽然没有实现指定的方法,但是在我们传入的协议中确实找到了它,所以已经在中间层类对象动态添加了,此时我们就需要重写responseToSelector,使得我们通过responseToSelector能够得知对象已经实现了此方法。举个例子,我们平时调用代理方法时,总会加上一句判断:

    if ([_delegate respondsToSelector: @selector(XXX)]) {
        [_delegate XXX];
    }
    

    若我们只是简单地为中间层类对象动态添加协议的方法,却没有重写respondsToSelector,那个此时这个方法也不可能会被调用。

  • methodSignatureForSelector: 为了能让动态添加的方法也能通过此方法获取到方法签名,我们需要重写。

  • getClass: 为了欺骗表层、欺骗世界,我们需要重写此方法,让这个方法返回的类对象非实际的中间层类对象,而是虚假的旧类对象。

重写getClass

// 混淆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类型,代表此时发送消息的实例,后面紧接着的是方法的实际参数。而方法实现的类型中,前两个参数为idSEL,代表发送消息的实例以及选择器,后面才接方法的实际参数。

重写respondsToSelector

// 混淆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

// 混淆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

// 混淆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

本文纯属个人见解,若大家发现文章部分有误,欢迎在评论区提出。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容