RxSwift-deallocating探索

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

推荐阅读更多精彩内容