RxSwiftでdeinitまで処理をdeferする

Swiftでdeinitまで処理をdeferする – TOKOROM BLOGという記事を見かけたので、RxSwiftで同様の事を行うコードを書いてみる。

まずdeinitで処理を行いたいクラス等にDisposeBagを持たせる。

class SampleViewController: UIViewController {
let disposeBag = DisposeBag()

ロガーを登録するところは同様に行う。

let logger = DDASLLogger.sharedInstance()
DDLog.addLogger(logger)

RxSwiftのAnonymousDisposableを作成して、disposeするときに実行するアクションを記述する。そしてそのAnonymousDisposableをdisposeBagに登録する。

let disposable = AnonymousDisposable {
DDLog.removeLogger(logger)
}
disposeBag.addDisposable(disposable)

こうする事で、SampleViewControllerのdeinitでdisposeBagがリリースされる時にAnonymousDisposableがdisposeされ、登録したアクションが実行されることにより、loggerがremoveされることになる。

すべてのコードは以下のようになる。

class SampleViewController: UIViewController {

let disposeBag = DisposeBag()

override func viewDidLoad() {
super.viewDidLoad()

let logger = DDASLLogger.sharedInstance()
DDLog.addLogger(logger)
let disposable = AnonymousDisposable {
DDLog.removeLogger(logger)
}
disposeBag.addDisposable(disposable)
}
}

で、これを簡単に書く為の関数を作成してみた。

        func actionAndDefer<T>(action: () -> T, deferAction: T -> ()) -> Disposable {
            return create { observer in
                let obj = action()

                observer.onNext("")

                let disposable = AnonymousDisposable {
                    deferAction(obj)
                }

                return disposable
                }.subscribeNext { (value: String) -> Void in }
        }

        actionAndDefer({ () -> DDLogger in
            let logger = DDASLLogger.sharedInstance()
            DDLog.addLogger(logger)
            return logger
            }, deferAction: { logger -> () in
                DDLog.removeLogger(logger)
        }).addDisposableTo(disposeBag)

RxSwiftの事をまだ把握しきれていないのでもっと良い書き方があると思うけど、とりあえずこれでactionAnDeferを実行してdisposeBagに登録すると利用できる。

[Swift] リフレクションを利用してEnumのcase名を文字列として利用する方法

*Swift2によるコードです。

SortConditionというソート条件の列挙型を考えます。ソートに使うプロパティと昇降順の情報を持たせたいので以下のようになります。

enum SortCondition {
case Name(Bool)
case Date(Bool)
}

let condition = SortCondition.Name(true)
switch condition {
case .Name(let ascending):
collection.sort(property: "name", ascending: ascending)
case .Date(let ascending):
collection.sort(property: "date", ascending: ascending)
}

“name”や”date”という情報はenum内で判断させることも出来ますが、結局switch等で値を決定する必要があることには違いありません。ですのでcaseが増えるとそのつどswitchを更新する必要があります。

そこでcase名を文字列として取得してプロパティの値に利用できれば、caseを増やしてもswitchをメンテナンスする必要がなくなります。やってみましょう。

enum SortCondition {
case name(Bool)
case date(Bool)

var property: String {
let mirror = Mirror(reflecting: self)
return mirror.children.first?.0 ?? ""
}

var ascending: Bool {
let mirror = Mirror(reflecting: self)
return (mirror.children.first?.1 as? Bool) ?? true
}
}

let condition = SortCondition.name(true)
collection.sort(property: condition.property, ascending: condition.ascending)

リフレクションを使いenumの情報を取得して利用しています。Mirrorはクラス・構造体・列挙型の情報をあらわすリフレクションのための構造体です。このようにMirrorを利用するとcase名や関連値を取得することができるので、case名を文字列として利用することができます。(case名は小文字からはじまっていますが、頭文字を大文字にしておいて小文字に変換しても良いかもしれません。)