Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecate type-based cancel IDs #2091

Merged
merged 3 commits into from
May 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-clocks",
"state" : {
"revision" : "20b25ca0dd88ebfb9111ec937814ddc5a8880172",
"version" : "0.2.0"
"revision" : "f9acfa1a45f4483fe0f2c434a74e6f68f865d12d",
"version" : "0.3.0"
}
},
{
Expand All @@ -68,8 +68,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-dependencies",
"state" : {
"revision" : "98650d886ec950b587d671261f06d6b59dec4052",
"version" : "0.4.1"
"revision" : "ad0a6a0dd4d4741263e798f4f5029589c9b5da73",
"version" : "0.4.2"
}
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ struct Animations: ReducerProtocol {
@Dependency(\.continuousClock) var clock

func reduce(into state: inout State, action: Action) -> EffectTask<Action> {
enum CancelID {}
enum CancelID { case rainbow }

switch action {
case .alertDismissed:
Expand All @@ -59,7 +59,7 @@ struct Animations: ReducerProtocol {
try await self.clock.sleep(for: .seconds(1))
}
}
.cancellable(id: CancelID.self)
.cancellable(id: CancelID.rainbow)

case .resetButtonTapped:
state.alert = AlertState {
Expand All @@ -79,7 +79,7 @@ struct Animations: ReducerProtocol {

case .resetConfirmationButtonTapped:
state = State()
return .cancel(id: CancelID.self)
return .cancel(id: CancelID.rainbow)

case let .setColor(color):
state.circleColor = color
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ struct EffectsBasics: ReducerProtocol {

@Dependency(\.continuousClock) var clock
@Dependency(\.factClient) var factClient
private enum DelayID {}
private enum CancelID { case delay }

func reduce(into state: inout State, action: Action) -> EffectTask<Action> {
switch action {
Expand All @@ -51,7 +51,7 @@ struct EffectsBasics: ReducerProtocol {
try await self.clock.sleep(for: .seconds(1))
return .decrementDelayResponse
}
.cancellable(id: DelayID.self)
.cancellable(id: CancelID.delay)

case .decrementDelayResponse:
if state.count < 0 {
Expand All @@ -63,7 +63,7 @@ struct EffectsBasics: ReducerProtocol {
state.count += 1
state.numberFact = nil
return state.count >= 0
? .cancel(id: DelayID.self)
? .cancel(id: CancelID.delay)
: .none

case .numberFactButtonTapped:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,19 @@ struct EffectsCancellation: ReducerProtocol {
}

@Dependency(\.factClient) var factClient
private enum NumberFactRequestID {}
private enum CancelID { case factRequest }

func reduce(into state: inout State, action: Action) -> EffectTask<Action> {
switch action {
case .cancelButtonTapped:
state.isFactRequestInFlight = false
return .cancel(id: NumberFactRequestID.self)
return .cancel(id: CancelID.factRequest)

case let .stepperChanged(value):
state.count = value
state.currentFact = nil
state.isFactRequestInFlight = false
return .cancel(id: NumberFactRequestID.self)
return .cancel(id: CancelID.factRequest)

case .factButtonTapped:
state.currentFact = nil
Expand All @@ -50,7 +50,7 @@ struct EffectsCancellation: ReducerProtocol {
return .task { [count = state.count] in
await .factResponse(TaskResult { try await self.factClient.fetch(count) })
}
.cancellable(id: NumberFactRequestID.self)
.cancellable(id: CancelID.factRequest)

case let .factResponse(.success(response)):
state.isFactRequestInFlight = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ struct Refreshable: ReducerProtocol {
}

@Dependency(\.factClient) var factClient
private enum FactRequestID {}
private enum CancelID { case factRequest }

func reduce(into state: inout State, action: Action) -> EffectTask<Action> {
switch action {
case .cancelButtonTapped:
return .cancel(id: FactRequestID.self)
return .cancel(id: CancelID.factRequest)

case .decrementButtonTapped:
state.count -= 1
Expand All @@ -57,7 +57,7 @@ struct Refreshable: ReducerProtocol {
await .factResponse(TaskResult { try await self.factClient.fetch(count) })
}
.animation()
.cancellable(id: FactRequestID.self)
.cancellable(id: CancelID.factRequest)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ struct Timers: ReducerProtocol {
}

@Dependency(\.continuousClock) var clock
private enum TimerID {}
private enum CancelID { case timer }

func reduce(into state: inout State, action: Action) -> EffectTask<Action> {
switch action {
case .onDisappear:
return .cancel(id: TimerID.self)
return .cancel(id: CancelID.timer)

case .timerTicked:
state.secondsElapsed += 1
Expand All @@ -43,7 +43,7 @@ struct Timers: ReducerProtocol {
await send(.timerTicked, animation: .interpolatingSpring(stiffness: 3000, damping: 40))
}
}
.cancellable(id: TimerID.self, cancelInFlight: true)
.cancellable(id: CancelID.timer, cancelInFlight: true)
}
}
}
Expand Down
62 changes: 36 additions & 26 deletions Examples/CaseStudies/SwiftUICaseStudies/02-Effects-WebSocket.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ struct WebSocket: ReducerProtocol {

@Dependency(\.continuousClock) var clock
@Dependency(\.webSocket) var webSocket
private enum WebSocketID {}

func reduce(into state: inout State, action: Action) -> EffectTask<Action> {
switch action {
Expand All @@ -50,13 +49,13 @@ struct WebSocket: ReducerProtocol {
switch state.connectivityState {
case .connected, .connecting:
state.connectivityState = .disconnected
return .cancel(id: WebSocketID.self)
return .cancel(id: WebSocketClient.ID())

case .disconnected:
state.connectivityState = .connecting
return .run { send in
let actions = await self.webSocket
.open(WebSocketID.self, URL(string: "wss://echo.websocket.events")!, [])
.open(WebSocketClient.ID(), URL(string: "wss://echo.websocket.events")!, [])
await withThrowingTaskGroup(of: Void.self) { group in
for await action in actions {
// NB: Can't call `await send` here outside of `group.addTask` due to task local
Expand All @@ -69,11 +68,11 @@ struct WebSocket: ReducerProtocol {
group.addTask {
while !Task.isCancelled {
try await self.clock.sleep(for: .seconds(10))
try? await self.webSocket.sendPing(WebSocketID.self)
try? await self.webSocket.sendPing(WebSocketClient.ID())
}
}
group.addTask {
for await result in try await self.webSocket.receive(WebSocketID.self) {
for await result in try await self.webSocket.receive(WebSocketClient.ID()) {
await send(.receivedSocketMessage(result))
}
}
Expand All @@ -83,7 +82,7 @@ struct WebSocket: ReducerProtocol {
}
}
}
.cancellable(id: WebSocketID.self)
.cancellable(id: WebSocketClient.ID())
}

case let .messageToSendChanged(message):
Expand All @@ -103,12 +102,12 @@ struct WebSocket: ReducerProtocol {
let messageToSend = state.messageToSend
state.messageToSend = ""
return .task {
try await self.webSocket.send(WebSocketID.self, .string(messageToSend))
try await self.webSocket.send(WebSocketClient.ID(), .string(messageToSend))
return .sendResponse(didSucceed: true)
} catch: { _ in
.sendResponse(didSucceed: false)
}
.cancellable(id: WebSocketID.self)
.cancellable(id: WebSocketClient.ID())

case .sendResponse(didSucceed: false):
state.alert = AlertState {
Expand All @@ -121,7 +120,7 @@ struct WebSocket: ReducerProtocol {

case .webSocket(.didClose):
state.connectivityState = .disconnected
return .cancel(id: WebSocketID.self)
return .cancel(id: WebSocketClient.ID())

case .webSocket(.didOpen):
state.connectivityState = .connected
Expand Down Expand Up @@ -190,6 +189,19 @@ struct WebSocketView: View {
// MARK: - WebSocketClient

struct WebSocketClient {
struct ID: Hashable, @unchecked Sendable {
let rawValue: AnyHashable

init<RawValue: Hashable & Sendable>(_ rawValue: RawValue) {
self.rawValue = rawValue
}

init() {
struct RawValue: Hashable, Sendable {}
self.rawValue = RawValue()
}
}

enum Action: Equatable {
case didOpen(protocol: String?)
case didClose(code: URLSessionWebSocketTask.CloseCode, reason: Data?)
Expand All @@ -210,10 +222,10 @@ struct WebSocketClient {
}
}

var open: @Sendable (Any.Type, URL, [String]) async -> AsyncStream<Action>
var receive: @Sendable (Any.Type) async throws -> AsyncStream<TaskResult<Message>>
var send: @Sendable (Any.Type, URLSessionWebSocketTask.Message) async throws -> Void
var sendPing: @Sendable (Any.Type) async throws -> Void
var open: @Sendable (ID, URL, [String]) async -> AsyncStream<Action>
var receive: @Sendable (ID) async throws -> AsyncStream<TaskResult<Message>>
var send: @Sendable (ID, URLSessionWebSocketTask.Message) async throws -> Void
var sendPing: @Sendable (ID) async throws -> Void
}

extension WebSocketClient: DependencyKey {
Expand Down Expand Up @@ -252,10 +264,9 @@ extension WebSocketClient: DependencyKey {

static let shared = WebSocketActor()

var dependencies: [ObjectIdentifier: Dependencies] = [:]
var dependencies: [ID: Dependencies] = [:]

func open(id: Any.Type, url: URL, protocols: [String]) -> AsyncStream<Action> {
let id = ObjectIdentifier(id)
func open(id: ID, url: URL, protocols: [String]) -> AsyncStream<Action> {
let delegate = Delegate()
let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil)
let socket = session.webSocketTask(with: url, protocols: protocols)
Expand All @@ -274,15 +285,14 @@ extension WebSocketClient: DependencyKey {
}

func close(
id: Any.Type, with closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?
id: ID, with closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?
) async throws {
let id = ObjectIdentifier(id)
defer { self.dependencies[id] = nil }
try self.socket(id: id).cancel(with: closeCode, reason: reason)
}

func receive(id: Any.Type) throws -> AsyncStream<TaskResult<Message>> {
let socket = try self.socket(id: ObjectIdentifier(id))
func receive(id: ID) throws -> AsyncStream<TaskResult<Message>> {
let socket = try self.socket(id: id)
return AsyncStream { continuation in
let task = Task {
while !Task.isCancelled {
Expand All @@ -294,12 +304,12 @@ extension WebSocketClient: DependencyKey {
}
}

func send(id: Any.Type, message: URLSessionWebSocketTask.Message) async throws {
try await self.socket(id: ObjectIdentifier(id)).send(message)
func send(id: ID, message: URLSessionWebSocketTask.Message) async throws {
try await self.socket(id: id).send(message)
}

func sendPing(id: Any.Type) async throws {
let socket = try self.socket(id: ObjectIdentifier(id))
func sendPing(id: ID) async throws {
let socket = try self.socket(id: id)
return try await withCheckedThrowingContinuation { continuation in
socket.sendPing { error in
if let error = error {
Expand All @@ -311,15 +321,15 @@ extension WebSocketClient: DependencyKey {
}
}

private func socket(id: ObjectIdentifier) throws -> URLSessionWebSocketTask {
private func socket(id: ID) throws -> URLSessionWebSocketTask {
guard let dependencies = self.dependencies[id]?.socket else {
struct Closed: Error {}
throw Closed()
}
return dependencies
}

private func removeDependencies(id: ObjectIdentifier) {
private func removeDependencies(id: ID) {
self.dependencies[id] = nil
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ struct LoadThenNavigateList: ReducerProtocol {
}

@Dependency(\.continuousClock) var clock
private enum CancelID {}
private enum CancelID { case load }

var body: some ReducerProtocol<State, Action> {
Reduce { state, action in
Expand All @@ -44,7 +44,7 @@ struct LoadThenNavigateList: ReducerProtocol {
return .none

case .onDisappear:
return .cancel(id: CancelID.self)
return .cancel(id: CancelID.load)

case let .setNavigation(selection: .some(navigatedId)):
for row in state.rows {
Expand All @@ -54,14 +54,14 @@ struct LoadThenNavigateList: ReducerProtocol {
try await self.clock.sleep(for: .seconds(1))
return .setNavigationSelectionDelayCompleted(navigatedId)
}
.cancellable(id: CancelID.self, cancelInFlight: true)
.cancellable(id: CancelID.load, cancelInFlight: true)

case .setNavigation(selection: .none):
if let selection = state.selection {
state.rows[id: selection.id]?.count = selection.count
}
state.selection = nil
return .cancel(id: CancelID.self)
return .cancel(id: CancelID.load)

case let .setNavigationSelectionDelayCompleted(id):
state.rows[id: id]?.isActivityIndicatorVisible = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ struct NavigateAndLoadList: ReducerProtocol {
}

@Dependency(\.continuousClock) var clock
private enum CancelID {}
private enum CancelID { case load }

var body: some ReducerProtocol<State, Action> {
Reduce { state, action in
Expand All @@ -46,14 +46,14 @@ struct NavigateAndLoadList: ReducerProtocol {
try await self.clock.sleep(for: .seconds(1))
return .setNavigationSelectionDelayCompleted
}
.cancellable(id: CancelID.self, cancelInFlight: true)
.cancellable(id: CancelID.load, cancelInFlight: true)

case .setNavigation(selection: .none):
if let selection = state.selection, let count = selection.value?.count {
state.rows[id: selection.id]?.count = count
}
state.selection = nil
return .cancel(id: CancelID.self)
return .cancel(id: CancelID.load)

case .setNavigationSelectionDelayCompleted:
guard let id = state.selection?.id else { return .none }
Expand Down
Loading