TCA v1.1 で throttle と debounce が Combine・Scheduler ベースの API に戻された
以前に TCA における debounce についての記事を書いていました。
上記の記事は Swift Concurrency と Clock を利用した debounce の表現方法についての話でしたが、v1.1 になったタイミングで throttle, debounce を利用するための API である Effect.throttle
と Effect.debounce
が復活しました。
リリースノートに書かれているように、この変更には以下のような理由があるようです。
Added: Effect.debounce and Effect.throttle, for debouncing and throttling effects on a scheduler (#2372, #2368). This functionality existed in past releases but was removed from 1.0 alongside other deprecated Combine code. Because there is no modern replacement for Effect.throttle we have brought this functionality back to 1.1.
元々 TCA は Combine ベースでしたが、1.0 になったタイミングで Combine ベースの API の多くが削除されました。
その際に、同じく Combine ベースであった throttle や debounce の機能も一度は削除されました。(長い間、それらの API は deprecated となっていました)
ただ、リリースノートに記載されているように、一度は削除したものの「Combine と Scheduler を利用していた Effect.throttle
や Effect.debounce
」のモダンな代替方法が現時点ではなさそうであるため、v1.1 のタイミングでこれらの API を復活させたようです。
実際、withTaskCancellation
を用いて debounce を実現しようとしていた際は、以下のようなコードを書かなければなりませんでしたが、
case let .textChanged(text)
enum CancelID { case search }
return .run { send in
await withTaskCancellation(id: CancelID.search, cancelInFlight: true) {
try await self.clock.sleep(for: .seconds(0.5))
await .send(
.debouncedResponse(TaskResult { try await self.apiClient.search(text) })
)
}
}
Effect.debounce
が復活したことによって、以下のようにスッキリとした記述ができるようになり、無駄な Action (Swift Concurrency ベースのもので言うと debouncedResponse
) を増やす必要もなくなりました。(isLoading
などの状態を変化させたいと言った場合は、また別の話かもしれないですが)
case let .textChanged(text):
enum CancelID { case search }
return self.apiClient.search(text)
.debounce(id: CancelID.search, for: 0.5, scheduler: self.mainQueue)
.map(Action.searchResponse)
今後 throttle や debounce で Swift Concurrency を利用するための模索は続くかもしれませんが、一旦は復活したものを利用するのが良さそうです。(きっと、Swift Concurrency ベースに変わるとなっても、大きな interface の変更がない形で提供してくれるはず)
Discussion