美文网首页
RxSwift拓展:异步事件追踪定位工具的研发历程

RxSwift拓展:异步事件追踪定位工具的研发历程

作者: 时光啊混蛋_97boy | 来源:发表于2021-11-26 09:39 被阅读0次

原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容

目录

  • 一、背景:RxSwift之痛
  • 二、Dump Observable Link
  • 三、Hook Swift动态和静态方法
  • 四、Hook RxSwift的方法

文章概要

本文主要从分析RxSwift操作符的实现原理入手,然后介绍了Swift反射机制、Swift的函数派发机制及命名空间机制,同时我们设计了一套实现Hook Swift的动态及静态方法的解决方案,希望对广大iOS开发者有所帮助。

一、背景:RxSwift之痛

RxSwift是GitHub的ReactiveX团队研发的一套函数响应式编程框架,其主要思想是把事件封装成信号流并采用观察者模式来实现监听。

当你使用RxSwift来实现一些简单的功能如发送一次网络请求、监听按钮点击事件等会让你的代码看起来非常直观简洁,但是如果你使用RxSwift实现了一个异步热流且在不同的类之间层层传递和加工转换之后代码的可读性就大大降低,甚至因为抓不到异步事件产生的堆栈而出现难以调试的情况。

为解决RxSwift的调试难题,我们通过阅读源码分析RxSwift操作符实现原理,然后利用Swift反射机制来dump “Observable Link”,最后又根据Swift语言的函数派发机制和命名空间机制设计了一套安全高效的hook Swift的动态及静态方法的方案,通过这套hook方案完成了对流事件传递链上的关键函数的拦截处理从而顺利实现了精准定位和调试RxSwift中异步事件的目。


二、Dump Observable Link

1、RxSwift操作符实现原理简析

一个Observable使用操作符可以转换成一个新的Observable,而这个源Observable经过一些连续的操作符转换之后就形成了一条Observable Link,要追踪一个异步事件的源头首先需要找到整个Observable LinkHead节点。

阅读RxSwift的源码之后发现RxSwift的各种操作符的基本原理就是当你使用某个操作符对一个Observable A进行转换的时候,这个操作符都会生成一个新的Observable B,并且在这个新的Observable B内部持有原来的那个Observable A,当有其他人订阅Observable B的时候,Observable B内部同时也会订阅Observable A以此来实现整个Observable Link的“联动”效果。此时你也许会有了一些思路,既然每个操作符都会在其内部持有上一个Observable,那我们根据这个规律沿着一个操作符Observable一直往上回溯直到根Observable是不是就可以dump出整个Observable Link了?这个思路是正确的,然而现实却很残酷——所有操作符Observable用于持有其源Observable的属性都是Private的,这也就意味着你根本无法直接获取到这些属性!然而天无绝人之路,所幸的是我们还可以利用Swift的反射机制来到达目的。

2、Swift反射机制

尽管 Swift一直都在强调强类型、编译时安全并推荐使用静态调度,但它的标准库仍然提供了一个基于MirrorStruct来实现的反射机制。简单来说,例如你有一个Class A并创建了一个A的实例对象a,此时你就可以通过Mirror(reflecting: a)来生成一个Mirror对象m,然后遍历m.children就可以获取到a对象的所有属性。看到这里你应该知道如何去dump一个Observable Link了吧,话不多说,先上代码为敬:

3、为已有的类动态添加存储型属性

dump出的Observable Link上的所有Observable都是我们需要在运行时重点观察的对象,那么我们该如何对这些Observable与其它Observable做出区分呢?我们可以为Observable添加一个tag属性,在运行时如果发现某个Observabletag不为空就监控这个Observable上产生的event。不过这里有一个关联类型问题,any类型可以转换为某种协议类型,但无法转换为关联类型协议的类型,因为关联的具体类型是未知的。为解决这个问题,我们设计了一个无关联类型的协议RxEventTrackType,在这个协议的extension里面为其添加eventTrackerTag属性,然后让Obseverble遵守此协议。为了给一个协议类型在extension中添加一个存储型属性,这里我选择了一个在OC时代经常使用的实现方案: objc_setAssociatedObject


三、Hook Swift动态和静态方法

1、Swift的函数派发机制

函数派发就是处理如何去调用一个函数的问题。编译型语言有三种常见的函数派发方式:直接派发(Direct Dispatch)、函数表派发(Table Dispatch)和消息派发(Message Dispatch)。Swift同时支持这三种函数派发方式。

直接派发(Direct Dispatch)是最快的,不止是因为需要调用的指令集会更少,并且编译器还能够有很大的优化空间,例如函数内联等。然而静态调用对于编程来说也就意味着因为缺乏动态性而无法支持继承。

函数表派发(Table Dispatch)是编译型语言实现动态行为最常见的实现方式。函数表使用了一个数组来存储类声明的每一个函数的指针。大部分语言把这个称为“virtual table”(虚函数表),Swift里称为 “witness table”。每一个类都会维护一个函数表,里面记录着类所有需要通过函数表派发的函数,如果在本类中override了父类函数的话表里面只会保存被override之后的函数。一个子类在声明体内新添加的函数都会被插入到这个函数表的后面,运行时会根据这一个表去决定实际要被调用的函数。

消息机制(Message Dispatch)是调用函数最动态的方式,这样的机制催生了KVOUIAppearenceCoreData等功能。这种运作方式的关键在于开发者可以在运行时改变函数的行为,不止可以通过swizzling来改变,甚至可以用isa-swizzling修改对象的继承关系,可以在面向对象的基础上实现自定义派发。

Swift函数派发规则总结
  • 值类型声明作用域里的函数总是会使用直接派发
  • Class声明作用域里的函数都会使用函数表进行派发(某些特殊情况下编译器会优化为直接派发)
  • 而协议和类的extension都会使用直接派发
  • 协议里声明的,并且带有默认实现的函数会使用函数表进行派发
  • dynamic修饰的函数会通过运行时进行消息机制派发

2、静态语言Swift的Hook难点

相比于动态语言OC,静态语言Swift的方法Hook变得异常困难。主要原因如下:

1. 目标函数查找难

在OC中我们可以通过一个Selector(你可以简单理解为一个字符串)查找到对应的method,这个method内部的imp字段存储的即是函数指针。而Swift中的动态方法利用witness table或者protocol witness table通过偏移寻址来查找对应函数指针,Swift中的静态方法的地址更是在编译期就已经确定。

2.强行直接替换函数指针比较危险

如果非要Hook Swift中的动态方法,我们还是可以利用Xcode的lldb调试工具在运行时通过反汇编观察并记录某个函数对应的在witness table中的偏移量,然后找到这个类的meta data并根据这些偏移量找到对应的函数指针来进行Hook。然而这是一个非常危险的做法,如果某天Swift调整了其类对象的内存模型,我们通过固有偏移来实现的Hook将一触及崩!

3、移花接木——巧用命名空间

在Swift中每个module都代表了一个单独的命名空间,在不同的module里面可以定义相同的类型名称或者方法名称。例如Swift为我们提供的基本数据类型String里面定义了一个lowercased方法,如果此时我们在自己的module里面利用extensionString再增加一个lowercased方法,此时这两个lowercased方法是可以共存的,而且当你在自己的module里面调用Stringlowercased方法时候默认优先调用的是你自己module里面的lowercased方法。

现在,你是不是感觉在Swift中Hook方法似乎有了一些眉目,然而目前还有一个更重要的问题亟待解决:如何在我们自己的lowercased方法中调用原生的lowercased方法呢?答案同样是利用命名空间。我们可以另外再建一个B moduledemo中利用创建一个pod库的方式实现),在这个B module中给String增加一个originalLowercased方法,这个方法的内部实现很简单就是直接调用一下String的原生lowercased方法。然后就可以在我们自己modulelowercased方法中调用originalLowercased从而间接实现对String的原生lowercased方法的调用。

稍微有些遗憾的是,利用上面所述的这种方案Hook的方法只在我们自己的module里面有效,不过对于一般的Hook需求来说已经足够使用了。


四、Hook RxSwift的方法

上面关于Hook的介绍已经给我们提高了充分的理论基础,下面我们就可以用理论来指导实践了。如果要追踪一个流事件产生的源头,关键要做的就是监听ObserverTypeonNextonErroronComplete方法和BehaviorRelayaccept方法。然后当一个ObserverType的对象的onNext等方法被调用的时候如果发现这个对象带有observerTypeTrackerTag就认为这是一个需要被重点观察和监控的对象并作出相应的处理,我们也可以同时在这里加上一个条件断点方便调试,代码截图如下:


五、总结

在此次RxSwift异步事件追踪定位工具的研发过程中,最为关键也是难点之一的就是如何实现hook Swift的动态及静态方法,我们在尝试了两三种方案之后才最终确定了这种利用Swift语言的函数派发机制和命名空间机制来安全高效的hook Swift的动态及静态方法的方案,相信我们的这套hook方案也会给你在以后的开发中在处理类似问题时带来更多的思路和灵感。

相关文章

  • RxSwift拓展:异步事件追踪定位工具的研发历程

    原创:知识点总结性文章创作不易,请珍惜,之后会持续更新,不断完善个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈...

  • RxSwift(一)----Observables

    RxSwift(一)----Observables RxSwift作为异步编程和事件驱动的iOS三方库.在平时的a...

  • RxSwift基础讲解

    RxSwift:RxSwift是一个使用可观察序列和函数式操作符编写异步和基于事件的代码的库。RxSwift常用的...

  • RxSwift-搞事情(一)

    一、初步了解 RxSwift的实现概括为4个流程,在RxSwift中,所有异步操作(事件)和数据流均被抽象为可观察...

  • 学习RxSwift笔记(一)

    RxSwift 使用详解系列 Rx Rx是ReactiveX的缩写,简单来说就是基于异步事件序列的响应式编程。Rx...

  • RxSwift1-简介

    关于rx系列,想必不用再过多的解释.简单来说就是基于异步 Event(事件)序列的响应式编程.理解RxSwift,...

  • 面向响应式编程 RP

    RxSwift RxSwift中文解释 RxSwift核心角色 Observable: 负责发送事件(Event)...

  • RxSwift学习(二)

    前言RxSwift的目的是让数据/事件流和异步任务能够更方便的序列化处理,能够使用Swift进行响应式编程。 本文...

  • RxSwift(一)编程思想&&上手

    RxSwift ReactiveX(简写: Rx) 是一个可以帮助我们简化异步编程的框架。 RxSwift 是 R...

  • 菜鸟学习RxSwift(-)

    学习RxSwift 啦!!目前版本3.4.0 RxSwift 是用 Swift 来实现的一个响应式拓展。为了让自己...

网友评论

      本文标题:RxSwift拓展:异步事件追踪定位工具的研发历程

      本文链接:https://www.haomeiwen.com/subject/quqztrtx.html