Created
August 6, 2025 21:00
-
-
Save treastrain/eaf03702c6e489c2c9eac01025c40c6f to your computer and use it in GitHub Desktop.
https://gist.github.com/treastrain/9ffa5fbc000a2b8b3d355b17ddebfa23 の Swift Observation 版(Swift 6.2)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ContentView.swift | |
// Playground | |
// | |
// Created by treastrain on 2025/08/07. | |
// | |
public import Observation | |
import SwiftUI | |
// MARK: - ObservableValue | |
@Observable | |
public final class ObservableValue<Value> { | |
private(set) var value: Value | |
public init(_ value: Value) { | |
self.value = value | |
} | |
public func update(_ newValue: Value) { | |
value = newValue | |
} | |
} | |
extension ObservableValue: @unchecked Sendable where Value: Sendable {} | |
// MARK: - MainActorObservableValue | |
@Observable @MainActor @dynamicMemberLookup | |
public final class MainActorObservableValue<Value> { | |
public private(set) var value: Value | |
@ObservationIgnored private var task: Task<(), Never>? | |
public init<O: Observable>(base: O, for keyPath: KeyPath<O, Value>) where Value: Sendable { | |
value = base[keyPath: keyPath] | |
task = Task { [weak self] in | |
for await newValue in Observations({ base[keyPath: keyPath] }) { | |
self?.value = newValue | |
} | |
} | |
} | |
isolated deinit { | |
task?.cancel() | |
} | |
public subscript<T>(dynamicMember keyPath: KeyPath<Value, T>) -> T { | |
value[keyPath: keyPath] | |
} | |
} | |
// MARK: - StateMachine | |
public protocol StateMachine<State>: Actor { | |
associatedtype State: Sendable | |
nonisolated var state: ObservableValue<State> { get } | |
} | |
// MARK: - MainActorObservableValue initializer for StateMachine | |
extension MainActorObservableValue { | |
public convenience init<S: StateMachine>(base: S) where S.State == Value { | |
self.init(base: base.state, for: \.value) | |
} | |
} | |
// MARK: - Examples | |
enum ExampleState: Sendable { | |
case state1(message: String) | |
case state2(message: String) | |
case state3 | |
var isStable: Bool { | |
switch self { | |
case .state1, .state2: true | |
case .state3: false | |
} | |
} | |
var message: String { | |
switch self { | |
case .state1(let message): message | |
case .state2(let message): message | |
case .state3: "Loading..." | |
} | |
} | |
} | |
protocol ExampleObjectProtocol: StateMachine<ExampleState> { | |
nonisolated func eventA() | |
nonisolated func eventB(param: Int) | |
} | |
actor ExampleObject: ExampleObjectProtocol { | |
nonisolated let state: ObservableValue<ExampleState> | |
init(initialState: ExampleState) { | |
state = ObservableValue(initialState) | |
} | |
nonisolated func eventA() { | |
Task { | |
await eventAInternal() | |
} | |
} | |
private func eventAInternal() async { | |
state.update(.state3) | |
try? await Task.sleep(for: .seconds(1)) | |
state.update(.state1(message: "event-a")) | |
} | |
nonisolated func eventB(param: Int) { | |
Task { | |
await eventBInternal(param: param) | |
} | |
} | |
private func eventBInternal(param: Int) async { | |
state.update(.state3) | |
try? await Task.sleep(for: .seconds(1)) | |
state.update(.state2(message: "event-b-\(param)")) | |
} | |
} | |
struct ExampleView<Object: ExampleObjectProtocol>: View { | |
private let object: Object | |
private let state: MainActorObservableValue<Object.State> | |
init(observing object: Object) { | |
self.object = object | |
self.state = MainActorObservableValue(base: object) | |
} | |
var body: some View { | |
VStack { | |
Text(state.message) | |
Text("State: \(state.isStable ? "Stable" : "Unstable")") | |
Button("Event A") { | |
object.eventA() | |
} | |
Button("Event B") { | |
object.eventB(param: Int(Date.now.timeIntervalSince1970)) | |
} | |
} | |
.disabled(!state.isStable) | |
} | |
} | |
struct ContentView: View { | |
let model = ExampleObject(initialState: .state1(message: "state1")) | |
var body: some View { | |
ExampleView(observing: model) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment