RxSwiftのDelegateProxyによる拡張はLinuxでは使用できない


備忘録として。何か良い手段があれば教えていただきたいです。

まとめ

.rx によるアクセスのために必要な DelegateProxy の Method Swizzling のために Delegate method には @objc メソッドが,Delegate method の @objc func を呼ぶためにクラスには NSObject の継承が必要で,
それらはObjective-Cランタイムを利用するため,(ランタイムを持たない) Linuxでは使用できない

やりたかったこと

あるクラスを RxSwift を import しない状態で,extension によってrxプロパティ経由では変更を通知できるようにしたかった。

具体的には,
時間計測を行う IntervalTimer.swiftmilliseconds プロパティが変化するタイミングで,通常は現在の値しか取れないが,.rx.milliseconds 経由では BehaviorRelay<Int> として Observable に変更を通知できるようにしたい。また,その拡張を外側から実装したい。なお,通知方向は一方向とし,アクセス側からは変更されないものとする。

Non Rx class ---> Rx extension ---> Rx class recieved KVO

という意図から始まった。

class Timer {
    fileprivate(set) var milliseconds: Int
}

extension Timer: ReactiveCompatible {}
extension Reactive where Base: Timer {
    /// Reactive wrapper for `milliseconds` message.
    var milliseconds: BehaviorRelay<Int> {
        // ほげふが
    }
}

let timer = Timer()
timer.milliseconds // -> Int
timer.rx.milliseconds // -> BehaviorRelay<Int>

試したこと

通常の RxCocoa の実装では, DelegateProxy を利用して Delegate method を外側から Reactive struct に適合するように extension を生やし,
ReactiveCompatible によって .rx 記法を利用できるようにした。

なぜ利用できなかったのか

The Swift Linux Port | Swift.org によると,

  • Runtime Introspection: When a Swift class on Apple’s platforms is marked @objc or subclasses NSObject you can use the Objective-C runtime to enumerate available methods on an object or call methods using selectors. Such capabilities are absent because they depend on the Objective-C runtime.

@objcNSObject はObjective-Cランタイムを利用するため,(ランタイムを持たない) Linuxでは使用できない

ということらしい。

どこで利用していたのか

RxCocoa では Delegate method の解決のために NSObject+Rx.swift にて sentMessage() および methodInvoked() が利用できるものの,その引数は Selector でした。
Selector@objc method でしか利用できませんでした (最終的に Method Swizzling に利用され,NSObject が必要)。

.rx によるアクセスのために必要な DelegateProxy の Method Swizzling のために Delegate method には @objc メソッドが,Delegate method の @objc func を呼ぶためにクラスには NSObject の継承が必要で,
それらは Objective-C ランタイムを利用するため,(ランタイムを持たない) Linuxでは使用できない

ということでした。

結局どうしたのか

どうにかできれば登壇材料でしたが,僕の力ではできなかったので素直に rx property を元のクラスに生やしました。
何か良い方法があれば教えていただきたいです......

最後に,頑張って追加した DelegateProxy と,それを泣く泣く消したコミットを添えておきます。お酒のおつまみにでも利用してください。

参考