美文网首页
RxSwift-deallocating探索

RxSwift-deallocating探索

作者: yahibo | 来源:发表于2019-08-11 17:46 被阅读0次
    deallocating.png

    RxSwfit中,有两个特殊序列

    • deallocating序列
    • deallocated序列

    RxSwiftdeinit等价于dealloc,在上面两个序列被订阅时,那么当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) 销毁")
    }
    

    打印如下:

    我来了
    准备走了
    SecondController 销毁
    已经走了
    

    从上面代码我们可以看出,RxSwiftdeinit(dealloc)动了手脚,通常通过黑魔法就能够达到该效果,在OC中我们经常使用runtime来交换方法,在方法内部处理我们需要做的事情。那么RxSwift是如何实现的呢?下面就看看源码都做了哪些事情。

    deallocating序列的创建

    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)
                }
            }
        }
    }
    
    • deallocatingReactive的扩展方法,继承自AnyObject相当于OC中的NSObject
    • 使用同步锁来保证线程安全
    • 内部通过self.registerMessageInterceptor传入deallocSelector来初始化一个DeallocatingProxy对象
    • 通过messageSent获取一个ReplaySubject序列

    deallocSelector一看就是一个方法选择器。实现如下:

    private let deallocSelector = NSSelectorFromString("dealloc")
    
    • 使用NSSelectorFromString方法来获取dealloc选择器

    由此可以看出,RxSwift确实是在dealloc(即Swfit中的deinit)上做文章。这里只是初始化了proxy对象,具体消息如何传出来的,还要继续代码追踪。

    proxy对象的创建

    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;
    }
    
    • 返回一个IMP函数指针
    • [RXObjCRuntime instance]实际上是一个NSObject的一个单例,内部采用互斥锁,向外部提供当前单例对象
    • ensurePrepared消息发送的入口点

    ensurePrepared函数

    搜索或直接cmd+点击定位代码:

    -(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传入目标类准备替换deallocdeallocating

    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函数的作用。

    imp_implementationWithBlock

    该函数接收一个block将其拷贝到堆区,返回一个IMP函数指针,把block当做OC中类的方法实现来使用。举例如下,用block代替原有方法实现:

    -(void)myMethod{
        NSLog(@"我来了");
    }
    ……
    //1、创建block
    void (^myblock)(int val) = ^(int val){
        NSLog(@"myblock");
    };
    //2、获取block的IMP
    IMP myblockImp = imp_implementationWithBlock(myblock);
    //3、获取要替换的方法的IMP
    Method method = class_getInstanceMethod(self.class, @selector(myMethod));
    //4、替换函数指针,指向block
    method_setImplementation(method, myblockImp);
    //5、执行原始方法
    [self myMethod];
    

    打印:我来了

    使用该函数是为了用代码块来替换一个需要替换的方法。

    以上宏定义的函数最后调用了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,开始设置deallocIMP指向上面提到的代码块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-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就被调用
    @objc func deallocating() {
        self.messageSent.on(.next(()))
    }
    deinit {
        self.messageSent.on(.completed)
    }
    
    • .commpleted结束序列,因此不需要在外部添加垃圾袋

    此处即是向订阅发送消息,这里前边文章都有代码追踪这里就不一一介绍了。deallocating调用后,上面有讲到,body调用后即调用代码块保存的原始dealloc函数:

    return originalImplementationTyped(self, selector ARGUMENTS(__VA_ARGS__));
    

    联系上面定义,可知originalImplementationTypeddealloc的原始函数,在此处调用了dealloc,由于代码比较繁琐,下面来证明一下该处就是触发dealloc的方法。我们可以将次闭包的参数换成viewDidAppear,在RxCocoa -> _RXObjeCRuntime.m中的ensureSwizzledSelector方法中替换:

    将如下:

    replacementImplementationGenerator(originalImplementation);
    

    替换为:

    IMP viewdidAppear = class_getMethodImplementation(class, @selector(viewDidAppear:));
        IMP implementationReplacementIMP = replacementImplementationGenerator(viewdidAppear);
    

    替换为视图出现时调用的方法,如果在掉用deallocating后,viewdidAppear被调用则能够证明上面所指之处就是我们触发dealloc的方法。

    替换前的打印:

    我来了
    准备走了
    SecondController 销毁
    已经走了
    

    替换后的打印:

    我来了
    准备走了
    我来了
    

    通过以上测试能够确定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中提供了两个关于deallocdeinit)的序列,观察dealloc的调用,其中deallocating内部替换了原生的dealloc方法从而达到监听dealloc的调用
    • 这里并不是交换方法,而是在replacementImplementationGenerator代码块中先保留了dealloc的函数地址,再通过imp_implementationWithBlock设置deallocIMP,指向了replacementImplementationGenerator代码块
    • 调用dealloc方法就会调用了代码块,在代码块内部通过body函数调用了deallocating方法,之后执行代码块中保留的原dealloc函数

    相关文章

      网友评论

          本文标题:RxSwift-deallocating探索

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