一、引言

  • 在 RxSwfit 中,有两个特殊序列:deallocating 与 deallocated,deinit 等价于 dealloc。在 deallocating 与 deallocated 两个序列被订阅时,那么当 deinit 调用时会触发这两个序列发送信号,它们的执行顺序为:deallocating -> deinit -> deallocated。
  • 现有如下的测试代码:
 override func viewDidLoad() {_ = rx.deallocating.subscribe(onNext: { () inprint("准备撤退")})_ = rx.deallocated.subscribe(onNext: { () inprint("已经撤退")})}override func viewDidAppear(_ animated: Bool) {print("进来了")}deinit {print("\(self.classForCoder) 销毁")}
  • 运行如下:
 进来了准备撤退TestViewController 销毁已经撤退
  • 从上面的测试代码可以看出,RxSwift 对 deinit(dealloc)做了处理,通常通过黑魔法就能够达到该效果,在 OC 中,经常使用 runtime 来交换方法,在方法内部处理需要做的事情,那么 RxSwift 是如何实现的呢?

二、deallocating 的源码分析

① deallocating 序列的创建

  • deallocating 是 Reactive 的扩展方法,继承自 AnyObject,相当于 OC 中的 NSObject,如下所示:
 extension Reactive where Base: AnyObject {public var deallocating: Observable<()> {return self.synchronized {do {let proxy: DeallocatingProxy = try self.registerMessageInterceptor(deallocSelector)return proxy.messageSent.asObservable()}catch let e {return Observable.error(e)}}}}
  • 源码分析:
    • 使用同步锁来保证线程安全;
    • 内部通过 self.registerMessageInterceptor 传入 deallocSelector 来初始化一个 DeallocatingProxy 对象;
    • 通过 messageSent 获取一个 ReplaySubject 序列。
  • deallocSelector,顾名思义,明显是一个方法选择器,它的实现是使用 NSSelectorFromString 方法来获取 dealloc 选择器,如下所示:
 private let deallocSelector = NSSelectorFromString("dealloc")
  • 由此可以看出,RxSwift 确实是在 dealloc(即 Swfit 中的 deinit)上做了处理,这里只是初始化了 proxy 对象,具体消息如何传出来的,继续往下探究。

② proxy 对象的创建

  • 继续查看 registerMessageInterceptor 方法:
 fileprivate func registerMessageInterceptor<T: MessageInterceptorSubject>(_ selector: Selector) throws -> T {let rxSelector = RX_selector(selector)let selectorReference = RX_reference_from_selector(rxSelector)let subject: Tif let existingSubject = objc_getAssociatedObject(self.base, selectorReference) as? T {subject = existingSubject}else {subject = T()objc_setAssociatedObject(self.base,selectorReference,subject,.OBJC_ASSOCIATION_RETAIN_NONATOMIC)}if subject.isActive {return subject}var error: NSError?let targetImplementation = RX_ensure_observing(self.base, selector, &error)if targetImplementation == nil {throw error?.rxCocoaErrorForTarget(self.base) ?? RxCocoaError.unknown}subject.targetImplementation = targetImplementation!return subject}
  • 源码分析:
    • selector 外部传入的 dealloc 的方法选择器;
    • RX_selector 方法通过 dealloc 方法名构建了另外一个方法选择器,如下:
 SEL __nonnull RX_selector(SEL __nonnull selector) {NSString *selectorString = NSStringFromSelector(selector);return NSSelectorFromString([RX_PREFIX stringByAppendingString:selectorString]);}
  • 由上可以看出,代码进入到 OC 区了,使用了 OC 的方法来满足需求。沿着想要的结果去找方法,前面提到 dealloc 可能被替换了,通过代码中的 targetImplementation,貌似是一个目标实现,进入代码查看:
 IMP __nullable RX_ensure_observing(id __nonnull target, SEL __nonnull selector, NSErrorParam error) {__block IMP targetImplementation = nil;@synchronized(target) {@synchronized([target class]) {[[RXObjCRuntime instance] performLocked:^(RXObjCRuntime * __nonnull self) {targetImplementation = [self ensurePrepared:targetforObserving:selectorerror:error];}];}}return targetImplementation;}
  • 源码分析:
    • RX_ensure_observing 返回一个 IMP 函数指针;
    • [RXObjCRuntime instance] 实际上是一个 NSObject 的一个单例,内部采用互斥锁,向外部提供当前单例对象;
    • ensurePrepared 消息发送的入口点。

③ ensurePrepared 函数

  • 直接搜索 ensurePrepared 方法:
 -(IMP __nullable)ensurePrepared:(id __nonnull)target forObserving:(SEL __nonnull)selector error:(NSErrorParam)error {Method instanceMethod = class_getInstanceMethod([target class], selector);if (instanceMethod == nil) {RX_THROW_ERROR([NSError errorWithDomain:RXObjCRuntimeErrorDomaincode:RXObjCRuntimeErrorSelectorNotImplementeduserInfo:nil], nil);}if (selector == @selector(class)||  selector == @selector(forwardingTargetForSelector:)||  selector == @selector(methodSignatureForSelector:)||  selector == @selector(respondsToSelector:)) {RX_THROW_ERROR([NSError errorWithDomain:RXObjCRuntimeErrorDomaincode:RXObjCRuntimeErrorObservingPerformanceSensitiveMessagesuserInfo:nil], nil);}// For `dealloc` message, original implementation will be swizzled.// This is a special case because observing `dealloc` message is performed when `observeWeakly` is used.//// Some toll free bridged classes don't handle `object_setClass` well and cause crashes.//// To make `deallocating` as robust as possible, original implementation will be replaced.if (selector == deallocSelector) {Class __nonnull deallocSwizzingTarget = [target class];IMP interceptorIMPForSelector = [self interceptorImplementationForSelector:selector forClass:deallocSwizzingTarget];if (interceptorIMPForSelector != nil) {return interceptorIMPForSelector;}if (![self swizzleDeallocating:deallocSwizzingTarget error:error]) {return nil;}interceptorIMPForSelector = [self interceptorImplementationForSelector:selector forClass:deallocSwizzingTarget];if (interceptorIMPForSelector != nil) {return interceptorIMPForSelector;}}}
  • 源码分析:
    • class_getInstanceMethod 获取当前界面对象的 dealloc 方法,来判断该类是否存在该方法,容错处理,对方法替换没关系;
    • 注释中说明了替换原始的 dealloc 方法;
    • deallocSwizzingTarget 获取到要替换 dealloc 的目标类;
    • swizzleDeallocating 传入目标类准备替换 dealloc 为 deallocating。

④ swizzleDeallocating

  • 可以看到,有一个如下的函数宏定义:
 SWIZZLE_INFRASTRUCTURE_METHOD(void,swizzleDeallocating,,deallocSelector,DEALLOCATING_BODY)
  • 内部整理如下:
 #define SWIZZLE_INFRASTRUCTURE_METHOD(return_value, method_name, parameters, method_selector, body, ...)SWIZZLE_METHOD(return_value, -(BOOL)method_name:(Class __nonnull)class parameters error:(NSErrorParam)error{SEL selector = method_selector; , body, NO_BODY, __VA_ARGS__)// common base#define SWIZZLE_METHOD(return_value, method_prototype, body, invoked_body, ...)method_prototype__unused SEL rxSelector = RX_selector(selector);IMP (^newImplementationGenerator)(void) = ^() {__block IMP thisIMP = nil;id newImplementation = ^return_value(__unsafe_unretained id self DECLARE_ARGUMENTS(__VA_ARGS__)) {body(__VA_ARGS__)struct objc_super superInfo = {.receiver = self,.super_class = class_getSuperclass(class)};return_value (*msgSend)(struct objc_super *, SEL DECLARE_ARGUMENTS(__VA_ARGS__))= (__typeof__(msgSend))objc_msgSendSuper;@try {return msgSend(&superInfo, selector ARGUMENTS(__VA_ARGS__));}@finally { invoked_body(__VA_ARGS__) }};thisIMP = imp_implementationWithBlock(newImplementation);return thisIMP;};IMP (^replacementImplementationGenerator)(IMP) = ^(IMP originalImplementation) {__block return_value (*originalImplementationTyped)(__unsafe_unretained id, SEL DECLARE_ARGUMENTS(__VA_ARGS__) )= (__typeof__(originalImplementationTyped))(originalImplementation);__block IMP thisIMP = nil;id implementationReplacement = ^return_value(__unsafe_unretained id self DECLARE_ARGUMENTS(__VA_ARGS__) ) {body(__VA_ARGS__)@try {return originalImplementationTyped(self, selector ARGUMENTS(__VA_ARGS__));}@finally { invoked_body(__VA_ARGS__) }};thisIMP = imp_implementationWithBlock(implementationReplacement);return thisIMP;};return [self ensureSwizzledSelector:selectorofClass:classnewImplementationGenerator:newImplementationGeneratorreplacementImplementationGenerator:replacementImplementationGeneratorerror:error];}
  • 代码看上去比较繁琐,可以将参数一一对比,能够看到:内部实际是重新组合了一个方法,参数为当前界面对象的类 deallocSwizzingTarget,实现了一个闭包并返回 IMP 函数指针:
    • replacementImplementationGenerator 代码块保存原始 dealloc 的函数地址,并在内部调用;
    • 在代码块中调用了 imp_implementationWithBlock 函数,获取代码块的函数指针。

⑤ imp_implementationWithBlock

  • 该函数接收一个 block 将其拷贝到堆区,返回一个 IMP 函数指针,把 block 当做 OC 中类的方法实现来使用。如下所示,用 block 代替原有方法实现:
 - (void)myMethod {NSLog(@"来了");}……// 创建blockvoid (^myblock)(int val) = ^(int val){NSLog(@"myblock");};// 获取block的IMPIMP myblockImp = imp_implementationWithBlock(myblock);// 获取要替换的方法的IMPMethod method = class_getInstanceMethod(self.class, @selector(myMethod));// 替换函数指针,指向blockmethod_setImplementation(method, myblockImp);// 执行原始方法[self myMethod];
  • 使用该函数是为了用代码块来替换一个需要替换的方法。以上宏定义的函数最后调用了 ensureSwizzledSelector 方法。

⑥ ensureSwizzledSelector

  • 搜索 ensureSwizzledSelector,查看代码:
 - (BOOL)ensureSwizzledSelector:(SEL __nonnull)selectorofClass:(Class __nonnull)classnewImplementationGenerator:(IMP(^)(void))newImplementationGeneratorreplacementImplementationGenerator:(IMP (^)(IMP originalImplementation))replacementImplementationGeneratorerror:(NSErrorParam)error {if ([self interceptorImplementationForSelector:selector forClass:class] != nil) {DLOG(@"Trying to register same intercept at least once, this sounds like a possible bug");return YES;}#if TRACE_RESOURCESatomic_fetch_add(&numberOInterceptedMethods, 1);#endifDLOG(@"Rx is swizzling `%@` for `%@`", NSStringFromSelector(selector), class);Method existingMethod = class_getInstanceMethod(class, selector);ALWAYS(existingMethod != nil, @"Method doesn't exist");const char *encoding = method_getTypeEncoding(existingMethod);ALWAYS(encoding != nil, @"Encoding is nil");IMP newImplementation = newImplementationGenerator();if (class_addMethod(class, selector, newImplementation, encoding)) {// new method added, job done[self registerInterceptedSelector:selector implementation:newImplementation forClass:class];return YES;}imp_removeBlock(newImplementation);// if add fails, that means that method already exists on targetClassMethod existingMethodOnTargetClass = existingMethod;IMP originalImplementation = method_getImplementation(existingMethodOnTargetClass);ALWAYS(originalImplementation != nil, @"Method must exist.");IMP implementationReplacementIMP = replacementImplementationGenerator(originalImplementation);ALWAYS(implementationReplacementIMP != nil, @"Method must exist.");IMP originalImplementationAfterChange = method_setImplementation(existingMethodOnTargetClass, implementationReplacementIMP);ALWAYS(originalImplementation != nil, @"Method must exist.");// If method replacing failed, who knows what happened, better not trying again, otherwise program can get// corrupted.[self registerInterceptedSelector:selector implementation:implementationReplacementIMP forClass:class];if (originalImplementationAfterChange != originalImplementation) {THREADING_HAZARD(class);return NO;}return YES;}
  • 源码分析:
    • interceptorImplementationForSelector 查看 dealloc 是否存在对应的函数,如果继续往下执行,则开始对 dealloc 做替换;
    • class_addMethod,既然 dealloc 存在对应的函数,添加必然失败,继续向下执行;
    • method_setImplementation,开始设置 dealloc 的 IMP 指向上面提到的代码块 replacementImplementationGenerator 中。
  • 在此处即替换了系统方法,当系统调用了 dealloc 时就会触发 replacementImplementationGenerator 中的 block 方法:
 IMP (^replacementImplementationGenerator)(IMP) = ^(IMP originalImplementation) {__block return_value (*originalImplementationTyped)(__unsafe_unretained id, SEL DECLARE_ARGUMENTS(__VA_ARGS__) )= (__typeof__(originalImplementationTyped))(originalImplementation);__block IMP thisIMP = nil;id implementationReplacement = ^return_value(__unsafe_unretained id self DECLARE_ARGUMENTS(__VA_ARGS__) ) {body(__VA_ARGS__)@try {return originalImplementationTyped(self, selector ARGUMENTS(__VA_ARGS__));}@finally { invoked_body(__VA_ARGS__) }};thisIMP = imp_implementationWithBlock(implementationReplacement);return thisIMP;};
  • 可以看到存在一个 body 函数的调用,body 做了什么呢?

⑦ body-DEALLOCATING_BODY

  • 搜索找到宏 DEALLOCATING_BODY,整理如下:
 #define DEALLOCATING_BODY(...)id<RXDeallocatingObserver> observer = objc_getAssociatedObject(self, rxSelector);if (observer != nil && observer.targetImplementation == thisIMP) {[observer deallocating];}
  • 源码分析:
    • rxSelector 即是要替换的方法选择器即 deallocating 对应的选择器;
    • observer 序列在此处调用了 deallocating,此时 deallocating 就被调用,commpleted 结束序列,因此不需要在外部添加垃圾袋,如下:
 @objc func deallocating() {self.messageSent.on(.next(()))}deinit {self.messageSent.on(.completed)}
  • 此处即是向订阅发送消息,deallocating 调用后,body 调用后即调用代码块保存的原始 dealloc 函数:
 return originalImplementationTyped(self, selector ARGUMENTS(__VA_ARGS__));
  • 由上可知,originalImplementationTyped 是 dealloc 的原始函数,在此处调用了 dealloc。如果需要验证该处就是触发的 dealloc 方法,可以将次闭包的参数换成 viewDidAppear,在 RxCocoa -> _RXObjeCRuntime.m 中的 ensureSwizzledSelector 方法中替换:
 replacementImplementationGenerator(originalImplementation);// 替换为IMP viewdidAppear = class_getMethodImplementation(class, @selector(viewDidAppear:));IMP implementationReplacementIMP = replacementImplementationGenerator(viewdidAppear);
  • 替换为视图出现时调用的方法,如果在掉用 deallocating 后,viewdidAppear 被调用就能够验证上面所指之处就是触发的 dealloc 方法。
  • 上面的测试代码:
    • 替换前的运行结果:
 进来了准备撤退TestViewController 销毁已经撤退
  • 替换后的运行结果则为:
 进来了准备撤退进来了
  • 通过以上测试,能够确定 dealloc 是在代码块中调用的(注意:在修改源码后要 clean 一下工程,否则缓存会影响执行结果)。

三、deallocated 的源码分析

  • 继续看看 deallocated 序列是如何产生,又是如何在 dealloc 调用完成之后执行的:
 public var deallocated: Observable<Void> {return self.synchronized {if let deallocObservable = objc_getAssociatedObject(self.base, &deallocatedSubjectContext) as? DeallocObservable {return deallocObservable._subject}let deallocObservable = DeallocObservable()objc_setAssociatedObject(self.base, &deallocatedSubjectContext, deallocObservable, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)return deallocObservable._subject}}
  • 关联了创建的序列,保证当前控制器内的序列对象只有一个。
  • DeallocObservable 的源码如下:
fileprivate final class DeallocObservable {let _subject = ReplaySubject<Void>.create(bufferSize:1)init() {}deinit {self._subject.on(.next(()))self._subject.on(.completed)}
}
  • 源码分析:
    • 内部也初始化一个 ReplaySubject 序列,用来发送消息;
    • 在对象销毁时调用了 .next 和 .completed,发送一条消息,再发送一条完成消息终止序列,因此在外部创建序列不需要添加垃圾袋。

四、总结

  • 在 RxSwift 中提供了两个关于 dealloc(deinit)的序列,观察 dealloc 的调用,其中 deallocating 内部替换了原生的 dealloc 方法从而达到监听 dealloc 的调用;
  • deallocating 并不是交换方法,而是在 replacementImplementationGenerator 代码块中先保留了 dealloc 的函数地址,再通过 imp_implementationWithBlock 设置 dealloc 的 IMP,指向了 replacementImplementationGenerator 代码块;
  • 调用 dealloc 方法就会调用了代码块,在代码块内部通过 body 函数调用了 deallocating 方法,之后执行代码块中保留的原 dealloc 函数。

RxSwift之深入解析特殊序列deallocating与deallocated的源码实现相关推荐

  1. JavaScript实现shortestCommonSupersequence最短公共超序列算法(附完整源码)

    JavaScript实现shortestCommonSupersequence最短公共超序列算法(附完整源码) longestCommonSubsequence.js完整源代码 shortestCom ...

  2. C++shortest common supersequence最短公共超序列算法的实现(附完整源码)

    C++shortest common supersequence最短公共超序列算法的实现 C++shortest common supersequence最短公共超序列算法的实现的完整源码(定义,实现 ...

  3. 图文深度解析Linux内存碎片整理实现机制以及源码

    图文深度解析Linux内存碎片整理实现机制以及源码. 物理内存是以页为单位进行管理的,每个内存页大小默认是4K(大页除外).申请物理内存时,一般都是按顺序分配的,但释放内存的行为是随机的.随着系统运行 ...

  4. python flask源码解析_用尽洪荒之力学习Flask源码

    [TOC] 一直想做源码阅读这件事,总感觉难度太高时间太少,可望不可见.最近正好时间充裕,决定试试做一下,并记录一下学习心得. 首先说明一下,本文研究的Flask版本是0.12. 首先做个小示例,在p ...

  5. BIN,S19,M0T,SREC,HEX文件解析;FileParse(二)之源码解析

    简介 一.摘要 1.描述 2.关键字 二.为何选择C#解析 三.BIN文件解析 四.BIN文件生成 五.S19,M0T,SREC文件解析 六.S19,M0T,SREC文件生成 七.HEX文件解析 八. ...

  6. cloudreve win10 解析域名_Cloudreve 云盘直链获取源码

    Cloudreve 云盘直链获取源码 @虐ふ1999.版本 2 .支持库 spec .程序集 程序集1 .子程序 _启动子程序, 整数型, , 本子程序在程序启动后最先执行 调试输出 (获取Could ...

  7. 40. 实战:基于tkinter实现用户UI界面——对34小节的VIP音乐解析系统的全面升级(附源码)

    目录 前言 目的 思路 代码实现 1. 首先设计主页UI界面 2. 封装核心解析歌曲代码 3. 下载音乐到本地 4. 将界面居中,禁止修改窗口大小,等待关闭/退出指令 完整源码 运行效果 使用过程 菜 ...

  8. Crackme006 - 全新160个CrackMe深度解析系列(图文+视频+注册机源码)

    原文链接 CrackMe006 | 难度适中适合练手 |160个CrackMe深度解析(图文+视频+注册机源码) crackme006,依然是delphi的,而且没壳子,条线比较清晰,算法也不难,非常 ...

  9. Spring源码深度解析(郝佳)-学习-HttpInvoker使用及源码解析

      Spring开发小组意识到在RMI服务和基于HTTP的服务如(Hessian和Burlap)之间的空白,一方面,RMI使用Java标准对象序列化,很难穿越防火墙,另一方面,Hessian/Burl ...

最新文章

  1. 打一针就可修复受损心脏,“癌症克星”CAR-T跨界疗法登上Science封面
  2. C#调用Oracle存储过程分页
  3. 计算1至1000间的合数c语言,输出1000以内的素数的算法(实例代码)
  4. python练习题:给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度
  5. 20165105第三周学习总结
  6. 傅里叶变换的相关函数(笔记02)
  7. spreadsheet js中创建下拉列表_JS 中创建自定义排序方法
  8. 宏正ATEN发行最新款双滑轨PS/2-USB双界面LCD控制端
  9. 解决Teamviewer屏保锁屏、黑屏无法进入问题
  10. 李宏毅机器学习笔记【未完】
  11. GokeAudio是一款简约小巧的开源安卓SIP软电话客户端
  12. 编写脚本程序程序,将当前目录下所有的.txt文件更名为.doc文件。
  13. AWS 吹走了私有云天空中最后一片乌云
  14. 牛客网练习2-《网络基础》
  15. 超声波测距传感器认知
  16. VENC 通道属性配置参数理解
  17. 小米6无人直播详细教程+工具包
  18. davinci 达芬奇BI工具
  19. -牧野- OpenGL文章收集
  20. 算法提高 聪明的美食家

热门文章

  1. python学习笔记 day20 序列化模块(二)
  2. 微信开发接口调用(前端+.net服务端)
  3. 简单拨号器(Android)
  4. Axure RP Pro 6.0 原型设计工具(产品经理必备)
  5. ZOJ2091(贪心)
  6. 关于多线程的一个例子(UI实时显示)
  7. 求1+2+3+...+n的值。
  8. p20pro 鸿蒙,后置镜头变液态双摄?华为P50Pro再曝光,搭载鸿蒙OS传感器变1寸
  9. 7-2 停车场管理 (50分)
  10. mysql源码安装配置_MySQL源码安装及配置