foo.eraseToAnyPublisher()
って AnyPublisher(foo)
するのと何か違うんでしょうか?eraseToAnySequence
とかないし、不思議な感じの API だと思って。eraseToAnySequence
もほしくないですか?map().map().eraseToAnyPublisher()
と書きたい、AnyPublisher(...map().map())
は見にくい、のかなと。 AnySequenceはよくわからないです。必要ならあってもいいと思います。ただ、これは利便性の問題なので一貫性とかはどうでもいいかと。 (edited)Array
でも AnySequence(array.map {}.map {})
より array.map {}.map {}.eraseToAnySequence()
と書きたいと。 (edited).let { ... }
があるんだと思うし。array.map {}.map {}.eraseToAnySequence()
これはだいたい変換後の型が欲しいんじゃないかな。そうでもない?array.lazy.map {}.map {}.eraseToAnySequence()
とかはありそう?array.lazy.map {}.map {}.to(AnySequence.self)
とか書ける方がいいのかな。.description
は例外的で、 他の言語だと toInt()
になりそうなやつが Int(x)
func =><X, R>(x: X, f: (X) -> R) -> R { f(x) }
AnyPublisher
については、 Opaque Result Type を早く associatedtype
指定できるようになることが望まれるなぁ。@Published
って、 @Published var foo: Foo
なら CurrentValueSubject
を公開するのと、 @Published private(set) var foo: Foo
なら内部で保持した CurrentValueSubject
を eraseToAnyPublisher()
で公開するのと、できること的には変わらないという理解で正しいですか?もちろん型の違いや実装の違いはあるし、 $foo
や foo.value
を使わないといけないというのはあるにしても。@Published
は (lldb) po viewController.thumbnailCollectionView.$images ▿ Publisher ▿ subject : <CurrentValueSubject<Array<PlayLogUploadParameter>, Never>: 0x6070000a3cc0> (lldb)
CurrentValueSubject使われてるみたいですし。CurrentValueSubject
は @Published
に寄せられても PassThroughSubject
が寄せられないのが型がそろわなくて気持ち悪いんですが、仕方ないですよね? (edited)@Published
と PassThroughSubject
を使い分けるんですが、 CurrentValueSubject
と PassThroughSubject
の使い分けなら両方とも Subject
として揃ってるのに、前者だけ @Published
に置き換えたときに気持ち悪さがあるなと。Failure
が Never
のときの話です。PassThroughSubject
は現在の値があるわけではないので、 Property Wrapper にできなくないですか?@Published
を PassThroughSubject
に書き換えるとき(そこそこある)は、気持ち悪さはないですけど、なんかそれっぽい書き方はないかなと思います。Foo<T>
にして T
は保持しないとかで作れないかなと考えたんですけどclass Person { var firstName: String var familyName: String var fullName { "\(firstName) \(familyName)" } }
fullName
が現在の値があるから @Published
でいいな。 (edited)button.$onTap
みたいな感じに」書きたいんですよね。Output
が Foo
で Failure
が FooError
な Publisher
を、 Output
が Result<Foo, FooError>
で Failure
が Never
な Publisher
に一発で変換できるメソッドってありますか?もしくは、一番簡単と思われる方法は何でしょうか?flatMap
じゃなくて flatMapError
がほしくないですか?Future
で考えてたけど、一般的な Publisher
で考えるとやりたいことが変な気がしてきました。replaceError(with:)
があるんだから、代わりに (Error) -> Output
を受け取るものがあってもいいのか。.map { Result<Foo, FooError>.success($0) }.catch { Just(.failure(...)) }
Future
って、非同期処理のコールバック代わりに使おうとすると、いちいち cancellable
をどこかに保持しておかないといけなくて面倒じゃないですか?キャンセルしないとき。extension Future { public func get(_ body: @escaping (Result<Output, Failure>) -> Void) { let keep: Keep<AnyCancellable> = .init() keep.value = sink(receiveCompletion: { completion in switch completion { case .finished: break case .failure(let error): body(.failure(error)) } keep.value = nil }, receiveValue: { output in body(.success(output)) }) } } private class Keep<Value> { var value: Value? }
let future = asyncFoo() future.get { foo in // cancellable は内部的に保持される // ... }
receive
使ったことなかったです( receive(on:)
しか)。 (edited)store
しても結局保持するための入れ物が必要じゃないですか?procotol Future { associatedtype T; func respond(_: (T) -> Void) }
cancellables
を持てるケースはいいんですけど、持てないケースが辛くて・・・。Future
を返す API 側で返ってこないことを通知するんじゃなくて、受けてがいなくなったらってことですね。 (edited)cancellable
を使って、それが難しいケースでコールバックと同じように気軽に使うには get
みたいなのがあると便利だと思うんですよね。Future
にしようとして、 cancellable 保持を考えるときついなということがあったので、そういうときに。
cancellables
使ってるとできるけど、↑の get
の実装だと、残っちゃいますね・・・。Future
を返す API 側が値が返ってこないことを知ったらそれを通知して解放するってことですよね。Future
でそれやるなら、値が返ってこないことがわかったら、値が返って来ないよという Error
で failure にする感じですかね。 completion は流れちゃいますが、一応エラーの種類で UncompletedError
だったら無視とか。get
でも解放されるはず。get
?get
は使い手側の問題だから、使い手が理解した上で導入って感じでしょうか。Future
にしちゃうと使いづらくなりかねなかったけど、使い手が get
みたいなのを導入すれば不便にはならないよと。combineLatest
の元に .subscribe(on:)
を噛ませたら流れこなくなります。どうしてなのでしょう? Publisherのsubscribeの起きるタイミングの問題とかですか var subscriptions = [AnyCancellable]() Just(2) .combineLatest(Just(1)) .sink( receiveCompletion: { print("works fine", $0) }, receiveValue: { print("value", $0) }) .store(in: &subscriptions) Just(2) .subscribe(on: DispatchQueue.main) .combineLatest(Just(1)) .sink( receiveCompletion: { print("you won't see me", $0) }, receiveValue: { print("never", $0) }) .store(in: &subscriptions) Just(2) .combineLatest(Just(1).subscribe(on: DispatchQueue.main)) .sink( receiveCompletion: { print("you won't see me", $0) }, receiveValue: { print("never", $0) }) .store(in: &subscriptions)
DispatchQueue.main
だとプログラム終了まで結果が返ってこないと思います。combineLatest
に関係なく↓だと返ってきません。 import Combine import Dispatch Just(2) .subscribe(on: DispatchQueue.main) .sink( receiveCompletion: { print("completion:", $0) semaphore.signal() }, receiveValue: { print("value:", $0) }) .store(in: &subscriptions) semaphore.wait()
(edited)import Combine import Dispatch Just(2) .subscribe(on: DispatchQueue.global()) .sink( receiveCompletion: { print("completion:", $0) semaphore.signal() }, receiveValue: { print("value:", $0) }) .store(in: &subscriptions)
combineLatest
を入れると DispatchQueue.global()
でも返ってきません。combineLatest
についてはまだ調べられてません。Just(2)
をDeffered Futureに変えて検証してみてたけど、subscrbeされてそう。謎です。tryFlatMap
が無いし、 SwiftNIO (EventLoopFuture) にも tryFlatMap
が無いんだけど、Publisher
のエラーで扱えってこととか?tryFlatMap
がほしくなるのは同意なんだけど、用意されてないのはエラーをどちらで扱うかというのがややこしいからじゃないかなぁ。Result
にも用意されてない。Result
は tryMap
すらない)flatMap { Result.init { try ... }.publisher }
にするのだと思うdo
記法の世界に行ってたのにモナドの世界に戻ってくる辛さはあるなぁ・・・。async/await
が来ても Publisher
から解放されるわけじゃないから。async/await
へのマイグレーションツールが用意されそうだから。Combine.Future
は map
しただけで Future
でなくなる罠が() throws -> Publisher<>
が型として良くない合成だから、tryFlatMap
のクロージャの型に望ましくない型が出てこないようにしてるというのは納得できる理由だなprotocol FutureLike { associatedtype Output associatedtype Failure: Error func asFuture() -> Future<Output, Failure> }
こういうの作って extension Publishers.Map: FutureLike where Upstream: FutureLike { func asFuture() -> Future<Output, Upstream.Failure> { Future(self) } }
こうした方が良いですね@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension Publishers.Map { public func map<T>(_ transform: @escaping (Output) -> T) -> Publishers.Map<Upstream, T> public func tryMap<T>(_ transform: @escaping (Output) throws -> T) -> Publishers.TryMap<Upstream, T> } @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension Publishers.TryMap { public func map<T>(_ transform: @escaping (Output) -> T) -> Publishers.TryMap<Upstream, T> public func tryMap<T>(_ transform: @escaping (Output) throws -> T) -> Publishers.TryMap<Upstream, T> }
(edited)Future
版を用意してくれたらそもそも困らなかった気が・・・。
Future
って class
じゃないんですか??struct
だと結果を書き込めないように思います。store(in:)
する先の話ですか?Map
とかのこと?Map
とかが struct
なのはおかしくないように思います。参照型の let
しか持たない struct
は class
と同じだし、そうするとヒープに乗るオブジェクトを一つ減らせるので。struct
なら型がネストしてお化けになっても最適化でないのと同じにできるし、将来的には some Publisher
として扱って、 SwiftUI の View
と似た感じになると思います。 (edited)futureMap
は Map
等のエコなオーバーロードと同じ位置づけな気もしてきたんですがどうでしょう?Future
の実装者であれば、 isKnownUniquelyReferenced
ならネストせずに自身を改造してフラットなまま返す map
とか作れそうですね・・・。FutureProtocol
があって、 some FutureProtocol
が使えて、かつ Map: FutureProtocol where Upstream: FutureProtocol
とかが一番 Swifty かもしれませんね。遠い道のりですが・・・。Just([1,2,3]) .sink(receiveValue: { elements in elements.forEach { element in print(element) } })
こちらのように修正したいです。 Just([1,2,3]) // do something for each的な何か .sink(receiveValue: { element in print(element) })
入れ子を1つでもなくした方が読みやすいだろうという目的です。よろしくお願いいたします。[1, 2, 3].publisher.sink { element in print(element) }
var cancellable = URLSession.shared .dataTaskPublisher(for: qiitaURL) .tryMap { element -> Data in guard let httpResponse = element.response as? HTTPURLResponse, httpResponse.statusCode == 200 else { throw URLError(.badServerResponse) } return element.data } .decode(type: [LGTM].self, decoder: JSONDecoder()) .sink(receiveCompletion: { print("complete! \($0)") }, receiveValue: { lgtms in lgtms.forEach { lgtm in print(lgtm) } })
.sink
の直前に .flatMap { lgtms in lgtms.publisher }
みたいなのを挟んでフラットにするのはいかがでしょう? (edited)ObservableObject
の objectWillChange
ってなんで did じゃなくて will なんだろうと思って調べてたら、中の人によるこんなツイートを見つけました。 https://twitter.com/luka_bernardi/status/1151633281982406656ObservableObject
の実装をするときに、 objectWillChange
を経由して View に反映したいようなものは @Published
でいいんですが、アニメーションやエフェクトの発火(たとえば画面を光らせるとか)については Publisher
として公開しておいて、それを直接 subscribe
する形にしておきたいことがあると思います。 そのようなときに、外部に send
を公開したくないので↓のように書いているんですが、 final class Foo: ObservableObject { private let _flashScreen: PassthroughSubject<Void, Never> = .init() let flashScreen: AnyPublisher<Void, Never> { _flashScreen.eraseToAnyPublisher() } ... }
PassthroughSubject
と AnyPublisher
を二重に宣言するのがボイラープレート的で嫌なんですが、もっと良い方法はありますか? send
の公開を気にせずに PassthroughSubject
を公開するという手もありますが・・・。private(set)
をうまく使うとか?ペアの片方を private
で保持するのはできるけど、それだと PassthroughSubject
形式とあまり変わらないような・・・。@propertyWrapper struct PrivateSubject<T, E: Swift.Error> { var wrappedValue: AnyPublisher<T, E> { projectedValue.eraseToAnyPublisher() } let projectedValue = PassthroughSubject<T, E>() } final class Foo: ObservableObject { @PrivateSubject var flashScreen: AnyPublisher<Void, Never> func foo() { $flashScreen.send() } }
こういう感じのを考えてましたFoo
を直接しってる人からは触れてしまうか・・var flashScreen: AnyPublisher<...> {get}
の粒度までしか知らない人に対しては隠蔽できますView.onChange(of:perform:)
もいいかもしれません View.onChange(of:perform:)
もいいかもしれません onChange
はちょっと合わないかもですね。たとえば Bool
にして true
にしても、もう一度そのエフェクトを実行したいときはどうなるのかとなってしまいますし。 Int
にしてインクリメントしていくことはできますが、 AnyPublisher<Void, Never>
に値を流して onReceive
で実行する方が素直なのかなと。catch
で川を殺さずにリトライなりエラー処理なりできると思いますcatch
で川を殺さずにリトライなりエラー処理なりできると思います catch
なり replaceError
は良さそうかなと思ってました!それを APIClient なり Service を利用する側で書くイメージですよねretry
では適切にリトライできないのでちょっと工夫が必要そうですねcatch
なり replaceError
は良さそうかなと思ってました!それを APIClient なり Service を利用する側で書くイメージですよね materialize
も良いんですが、あんまり Rx
の文脈を持ってきてしまうの懸念があってできればプレーンに行きたいなという気持ちがありますDeffered { apiClient,foo() }.retry(3)
とかやる感じかなwebSocketClient.receiveMessage { message, error in // this closure is called multiple times, everytime a message comes ... }
他のFRPライブラリ、例えばReactiveSwiftだと、SignalProducer.init(_ startHandler:)
、RxSwiftだとObservable.create
と、普通にinitializerでかけるのですが、Combineで相当するものが見つからず、Combineユーザーの方がどう対応しているのか知りたいです。 TCA (The Composable Architecture)の Effect.run
はそのようなユースケースにも使えて便利なのですが、内部実装はカスタムのPublisherとSubscriberを作っているようで、このようにカスタムのPublisherを作って対応しているのでしょうか? 割とよくあるシナリオな割にうまく書けなくて、Combine使っている方がどう処理しているのか気になっています。よろしくお願いします〜。 (edited)var onMassage = PassthroughSubject<String, Error>()
みたいにするのが一番簡単なコールバックの代替だと思います。 @Published
でも似たような感じです。let observable = Observable<String>.create { observer in let token = webSocketClient.receiveMessage { message in observer.on(.next(message)) } return Disposables.create { // close connection when disposed token.invalidate() } }
import Combine func f1() -> AnyPublisher<Int, Error> { Just(1) .setFailureType(to: Error.self) .eraseToAnyPublisher() } // 本当はこう書きたい func f2() -> Publisher<Int, Error> { Just(1) }
import Combine func f1() -> AnyPublisher<Int, Error> { Just(1) .setFailureType(to: Error.self) .eraseToAnyPublisher() } // 本当はこう書きたい func f2() -> Publisher<Int, Error> { Just(1) }
AnyPublisher<Int, Never>
ではダメですか?eraseToAnyPublisher
は将来的には↓のようにできるようになると思います。 func f1() -> some Publisher<.Output = Int, .Failure = Never> { Just(1) }
.Failure: Error
も書けるのであればfunc f1() -> some Publisher<.Output = Int, .Failure: Error> { Just(1) }
some Error
でいいのか?func f1() -> some Publisher<.Output = Int, .Failure = some Error> { Just(1) }
some Publisher<.Output = Int>
とおなじに見える (edited)extension Just { static func anyPublisher<T>(_ value: T) -> AnyPublisher<T, Error> { Just(value) .setFailureType(to: Error.self) .eraseToAnyPublisher() } } Just.anyPublisher(1)
eraseToAnyPublisher
は Opaque Result Type が完成するまでの間に合わせという認識です。将来的には不要になるはず。そして、現状では必要かと。 // 将来的にできるようになる予定の理想形 func f1() -> some Publisher<.Output = Int> { Just(1) }
Publisher
の Failure
は : Error
だから(そして Never: Error
だから) (edited)some Publisher<Int>
になる(なってしまう)可能性もちょっとある・・・ https://forums.swift.org/t/pitch-light-weight-same-type-constraint-syntax/52889 (edited)Failure
の詳細を隠蔽して使えるのでは?some Publisher<Int>
になる(なってしまう)可能性もちょっとある・・・ https://forums.swift.org/t/pitch-light-weight-same-type-constraint-syntax/52889 (edited)Output
だけ埋めるとか Failure
だけ埋めるとかできなさそう。Output
だけ埋めるとか Failure
だけ埋めるとかできなさそう。 <.Failure = FooError>
の記法と共存はできますね。Collection
とかいくつの associatedtype
があることか。some Foo<A, B, C>
が前から順番に適用されるとして、 protocol Foo
の associatedtype
の宣言順とかわかんなくない?Collection
くらいよく使うやつでも、最初は Element
だろうなくらいはわかるけど、 2 番目が何かわからない。protocol Collection<Element> { associatedtype Index: Comparable ... }
SubSequence
と Index
と Iterator
のどれが先かとかはまったくわからない。AnyPublisher<Int, Never>
ではダメですか? func f1() -> AnyPublisher<Int, Error> { if 何か条件1 { return Just(1) .setFailureType(to: Error.self) .eraseToAnyPublisher() } if 何か条件2 { return Just(2) .setFailureType(to: Error.self) .eraseToAnyPublisher() } return Fail(error: someError) .eraseToAnyPublisher() }
func f1() -> AnyPublisher<Int, Error> { if 何か条件1 { return Just(1) .setFailureType(to: Error.self) .eraseToAnyPublisher() } if 何か条件2 { return Just(2) .setFailureType(to: Error.self) .eraseToAnyPublisher() } return Fail(error: someError) .eraseToAnyPublisher() }
func f1() -> AnyPublisher<Int, Error> { if 何か条件1 { return Just(1) .setFailureType(to: Error.self) .eraseToAnyPublisher() } if 何か条件2 { return Just(2) .setFailureType(to: Error.self) .eraseToAnyPublisher() } return Fail(error: someError) .eraseToAnyPublisher() }
func f1() -> any Publisher<.Output == Int> { if 何か条件1 { return Just(1) } if 何か条件2 { return Just(2) } return Fail(error: someError) }
anyPublisher
を生やすとかはできますが、本質的に AnyPublisher<Int, Error>
に変換するのは欠かせないという意味です。func f1() -> any Publisher<.Output == Int> { if 何か条件1 { return Just(1) } if 何か条件2 { return Just(2) } return Fail(error: someError) }
any
の existentialへは暗黙にアップキャストできるので、いいですね。 (edited)eraseToAnyPublisher
が必要ないのでコードが簡潔になるのと、 Type Erasure の諸々のオーバーヘッドを考えると、直接の Existential の方がマシじゃないですか?let box: AnySequenceBox<Element> = SequenceBox<[Element]>(base)
みたいになったとして、 box
から SequenceBox
のメソッドを呼び出すところは動的ディスパッチだけどクラスの継承で vtable で済ませられて、 SequenceBox
が base
のメソッドを呼ぶところが specialize されるなら、そこに Existential Contiainer のオーバーヘッドはなくなる。