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閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內容