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:)を噛ませたら流れこなくなります。どうしてなのでしょう?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 のオーバーヘッドはなくなる。