コベリンの最近の取り組みとして、業務などで役立ちそうな知見を共有する会を開催することになりました。 そのついでに発表に使ったアジェンダもそのまま公開してしまおうという豪快な企画です。
※ アジェンダをそのままコピペして公開したものなので若干見にくい箇所もあるかもしれませんが、ご了承ください。
runCatching vs try-catch in kotlin @numa08
結論
どっちがベストってことはない。状況に応じて使い分けたら良いし、チーム内で合意をとっておく事が必要。lintで発見しにくい部分なので、PRのレビューのタイミングで話し合いができたら良い。
about try-catch
javaと似たようなシンタックスで例外をキャッチできるが、javaと違って式なので値を返す。
- Example
val result = try { api.getData() } (e: IOException) { Timber.e(e) null } ?: return print(result)
about runCatching
runCatching - Kotlin Programming Language
パラメータの無名関数の結果をResult
に包む。Resut.fold
等で結果を取得できる。
- Example
val result = runCatching { api.getData() }.fold( onSuccess : { it }, onFailure: { Timber.e(it) null } ) ?: return print(result)
which should be use ?
try-catchは例外をキャッチするにはややレガシーな記法で、例えば次の課題がある。
- 複数の処理が連続する場合にどの例外をキャッチするのか分かりにくい
- 例外クラスの継承関係を意識したcatchが必要
コードで示すと以下のような状態。
try { val classAtFirst = api.getClass(grade: 1, class: 1) api.getStudents(classAtFirst) } catch(e: Exception) { } catch(e: IOException){ }
try内で2つAPIアクセスを実行しているので例外をcatchするとどちらの操作で発生した例外かわかりにくい。またIOExceptionの前にExceptionでキャッチしているため、IOExceptionがスローされてもcatchされない(これはlintで怒られる気がする)
runCatchingによってある程度解決できる。
fun errorHandler(e: Exception, action: String) { when(e) { is IOException -> Timber.e("IOException at ${action}", e) is Exception -> Timber.e("some error at ${action}", e) } } val classAtFirst = runCatching { api.getClass(grade:1, class: 1) }.fold( onSuccess: { it }, onFailure { errorHandler(it, "getClasses") null } ) ?: return val students = runCatching { api.getStudents(classAtFirst) }.fold( onSuccess: { it }, onFailure: { errorHandler(it, "getStudents") null } ) ?: return
runCatchingを使うとtry-catchで実現可能な処理を楽に実装できるため、try-catchを廃してrunCatchingにするとプロジェクト内で統一感が出る。一方、try-catch内の処理が常に1つしか無いなど、runCatchingを使うメリットを感じられない場合はtry-catchでも問題ない。チーム内で話し合って結論を出すのが良い。
off topic
Arrow-ktを使うと例外発生時の処理中断を簡単に実装できるが、初見だと混乱する。
val classAtFirst = api.getClass(grade:1, class:1).bind() // getClassで例外がスローされたら↓の処理は実行されない val studens = api.getStudents(classAtFirst).bind() // getStudentsで例外がスローされたら↓は実行されず、Failureが返される return studens
最近の知見 @mironal
String → Data 変換
// これだと型が Data? なる let data1 = "AA".data(using: .utf8) // これだと型が Data になる let data2 = Data("AA".utf8) // "AA".data(using: .utf8) は絶対に nil にならない // このメソッドが nil になるのは // "ああ".data(using: .ascii) みたいな指定した encoding で表現出来ない文字を指定した場合 // UTF-8 ならすべての文字が表現できるので大丈夫.
UIApplication.sharedApplication.networkActivityIndicatorVisible 無くなってた
iOS 13 から非推奨。 iPhone X でノッチ付きの端末が出てから。
そういや見ないな... お前...
null_resettable
最近 null_resettable
なるものを見つけた。
Swift が導入されたときに Objc にも nullability の keyword (nonnull, nullable) が追加されたときに null_resettable も導入されてた。
nil を set すると nil じゃなくて特定のデフォルト値にリセットされる挙動を示している(以下のような動作)。
if (newValue == nil) { value = "default value" } else { value = newValue }
戻り値は必ず non-nil なので swift では tintColor: UIColor!
のような (Implicitly Unwrapped Optionals) な型で表現される。
ちなみにドキュメントには null_resettable
キーワードは出てこない。 header にだけ書かれている。
実行中のprogressを表示する (Rx) @takkumattsu
API叩いてる時にProgressを表示したい。結論的にはActionを使うのがいい。
https://github.com/RxSwiftCommunity/Action/blob/master/Demo/ViewController.swift
こんな感じでexecutingで取れる
let sharedAction = Action<SharedInput, String> { input in switch input { case .barButton: return Observable.just("UIBarButtonItem with 3 seconds delay").delaySubscription(.seconds(3), scheduler: MainScheduler.instance) case .button(let title): return .just("UIButton " + title) } } sharedAction.executing.debounce(.seconds(0), scheduler: MainScheduler.instance).subscribe(onNext: { [weak self] executing in if (executing) { self?.activityIndicator.startAnimating() } else { self?.activityIndicator.stopAnimating() } })
使うわない時(使わない理由はないけど)はRxSwfitのサンプルにあるようにActivityIndicator
というのを作成して利用するとよさそう。
ちなみにUIActivityIndicator
とは全く関係ないです
// // ActivityIndicator.swift // RxExample // // Created by Krunoslav Zaher on 10/18/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. // import RxSwift import RxCocoa private struct ActivityToken<E> : ObservableConvertibleType, Disposable { private let _source: Observable<E> private let _dispose: Cancelable init(source: Observable<E>, disposeAction: @escaping () -> Void) { _source = source _dispose = Disposables.create(with: disposeAction) } func dispose() { _dispose.dispose() } func asObservable() -> Observable<E> { _source } } /** Enables monitoring of sequence computation. If there is at least one sequence computation in progress, `true` will be sent. When all activities complete `false` will be sent. */ public class ActivityIndicator : SharedSequenceConvertibleType { public typealias Element = Bool public typealias SharingStrategy = DriverSharingStrategy private let _lock = NSRecursiveLock() private let _relay = BehaviorRelay(value: 0) private let _loading: SharedSequence<SharingStrategy, Bool> public init() { _loading = _relay.asDriver() .map { $0 > 0 } .distinctUntilChanged() } fileprivate func trackActivityOfObservable<Source: ObservableConvertibleType>(_ source: Source) -> Observable<Source.Element> { return Observable.using({ () -> ActivityToken<Source.Element> in self.increment() return ActivityToken(source: source.asObservable(), disposeAction: self.decrement) }) { t in return t.asObservable() } } private func increment() { _lock.lock() _relay.accept(_relay.value + 1) _lock.unlock() } private func decrement() { _lock.lock() _relay.accept(_relay.value - 1) _lock.unlock() } public func asSharedSequence() -> SharedSequence<SharingStrategy, Element> { _loading } } extension ObservableConvertibleType { public func trackActivity(_ activityIndicator: ActivityIndicator) -> Observable<Element> { activityIndicator.trackActivityOfObservable(self) } }
// viewへのprogress用のプロパティ let signingIn: Driver<Bool> : 略 : func init() { let signingIn = ActivityIndicator() self.signingIn = signingIn.asDriver() _ = input.loginTaps.withLatestFrom(usernameAndPassword) .flatMapLatest { pair in return API.signup(pair.username, password: pair.password) .trackActivity(signingIn) .asDriver(onErrorJustReturn: false) } .flatMapLatest { loggedIn -> Driver<Bool> in let message = loggedIn ? "Mock: Signed in to GitHub." : "Mock: Sign in to GitHub failed" return wireframe.promptFor(message, cancelAction: "OK", actions: []) // propagate original value .map { _ in loggedIn } .asDriver(onErrorJustReturn: false) } }