RxSwift之深入解析特殊序列deallocating与deallocated的源码实现
Posted Forever_wj
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RxSwift之深入解析特殊序列deallocating与deallocated的源码实现相关的知识,希望对你有一定的参考价值。
一、引言
- 在 RxSwfit 中,有两个特殊序列:deallocating 与 deallocated,deinit 等价于 dealloc。在 deallocating 与 deallocated 两个序列被订阅时,那么当 deinit 调用时会触发这两个序列发送信号,它们的执行顺序为:deallocating -> deinit -> deallocated。
- 现有如下的测试代码:
override func viewDidLoad() {
_ = rx.deallocating.subscribe(onNext: { () in
print("准备撤退")
})
_ = rx.deallocated.subscribe(onNext: { () in
print("已经撤退")
})
}
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: T
if 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:target
forObserving:selector
error: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:RXObjCRuntimeErrorDomain
code:RXObjCRuntimeErrorSelectorNotImplemented
userInfo:nil], nil);
}
if (selector == @selector(class)
|| selector == @selector(forwardingTargetForSelector:)
|| selector == @selector(methodSignatureForSelector:)
|| selector == @selector(respondsToSelector:)) {
RX_THROW_ERROR([NSError errorWithDomain:RXObjCRuntimeErrorDomain
code:RXObjCRuntimeErrorObservingPerformanceSensitiveMessages
userInfo: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:selector
ofClass:class
newImplementationGenerator:newImplementationGenerator
replacementImplementationGenerator:replacementImplementationGenerator
error:error];
}
- 代码看上去比较繁琐,可以将参数一一对比,能够看到:内部实际是重新组合了一个方法,参数为当前界面对象的类 deallocSwizzingTarget,实现了一个闭包并返回 IMP 函数指针:
-
- replacementImplementationGenerator 代码块保存原始 dealloc 的函数地址,并在内部调用;
-
- 在代码块中调用了 imp_implementationWithBlock 函数,获取代码块的函数指针。
⑤ imp_implementationWithBlock
- 该函数接收一个 block 将其拷贝到堆区,返回一个 IMP 函数指针,把 block 当做 OC 中类的方法实现来使用。如下所示,用 block 代替原有方法实现:
- (void)myMethod {
NSLog(@"来了");
}
……
// 创建block
void (^myblock)(int val) = ^(int val){
NSLog(@"myblock");
};
// 获取block的IMP
IMP myblockImp = imp_implementationWithBlock(myblock);
// 获取要替换的方法的IMP
Method method = class_getInstanceMethod(self.class, @selector(myMethod));
// 替换函数指针,指向block
method_setImplementation(method, myblockImp);
// 执行原始方法
[self myMethod];
- 使用该函数是为了用代码块来替换一个需要替换的方法。以上宏定义的函数最后调用了 ensureSwizzledSelector 方法。
⑥ ensureSwizzledSelector
- 搜索 ensureSwizzledSelector,查看代码:
- (BOOL)ensureSwizzledSelector:(SEL __nonnull)selector
ofClass:(Class __nonnull)class
newImplementationGenerator:(IMP(^)(void))newImplementationGenerator
replacementImplementationGenerator:(IMP (^)(IMP originalImplementation))replacementImplementationGenerator
error:(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_RESOURCES
atomic_fetch_add(&numberOInterceptedMethods, 1);
#endif
DLOG(@"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 targetClass
Method 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:以上是关于RxSwift之深入解析特殊序列deallocating与deallocated的源码实现的主要内容,如果未能解决你的问题,请参考以下文章