import SwiftUI
/// Actor + AsyncStream ベースの共有マネージャ
actor BaseManager<T> {
private var continuation: AsyncStream<Result<T, Error>>.Continuation
let stream: AsyncStream<Result<T, Error>>
init() {
var cont: AsyncStream<Result<T, Error>>.Continuation!
self.stream = AsyncStream { continuation in
cont = continuation
}
self.continuation = cont
}
func send(_ value: T) {
continuation.yield(.success(value))
}
func sendError(_ error: Error) {
continuation.yield(.failure(error))
}
func finish() {
continuation.finish()
}
}
@MainActor
final class BaseViewModel<T>: ObservableObject {
@Published var latestValue: T?
@Published var lastError: String?
private let manager: BaseManager<T>
private var task: Task<Void, Never>?
init(manager: BaseManager<T>) {
self.manager = manager
startObserving()
}
private func startObserving() {
task = Task {
for await result in await manager.stream {
switch result {
case .success(let value):
latestValue = value
case .failure(let error):
lastError = error.localizedDescription
}
}
}
}
func stopObserving() {
task?.cancel()
task = nil
}
}
struct BaseView<T: CustomStringConvertible>: View {
@StateObject private var viewModel: BaseViewModel<T>
init(manager: BaseManager<T>) {
_viewModel = StateObject(wrappedValue: BaseViewModel(manager: manager))
}
var body: some View {
VStack(spacing: 20) {
if let value = viewModel.latestValue {
Text("最近値: \(value.description)")
.font(.headline)
} else {
Text("まだデータなし")
.foregroundColor(.secondary)
}
if let error = viewModel.lastError {
Text("エラー: \(error)")
.foregroundColor(.red)
}
Button("購読停止") {
viewModel.stopObserving()
}
.padding()
.background(Color.blue.opacity(0.2))
.cornerRadius(8)
}
.padding()
}
}
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
}
}
#Preview {
ContentView()
}
@main
struct ActorStreamMVVMComplitedTemplateApp: App {
private static let manager = BaseManager<String>()
// OtherBaseManager
// private static let otherManager = BaseManager<Int>()
var body: some Scene {
WindowGroup {
BaseView(manager: Self.manager)
.task {
// デモ用: 成功と失敗を交互に送信
for i in 1...10 {
if i % 3 == 0 {
await Self.manager.sendError(NSError(domain: "DemoError", code: i, userInfo: nil))
} else {
await Self.manager.send("データ #\(i)")
}
try? await Task.sleep(nanoseconds: 1_000_000_000)
}
await Self.manager.finish()
}
}
}
}
関連
コメント