Skip to content

Instantly share code, notes, and snippets.

@YusukeHosonuma
Created December 1, 2022 00:16
Show Gist options
  • Save YusukeHosonuma/d8381a8d5fa2e6be31c47c3c9afd958d to your computer and use it in GitHub Desktop.
Save YusukeHosonuma/d8381a8d5fa2e6be31c47c3c9afd958d to your computer and use it in GitHub Desktop.
Actor / AsyncStream を使ったカウントダウン的な(とくに意味はない)
import SwiftUI
import Combine
struct ContentView: View {
@State var id: Int = 0
var body: some View {
VStack {
CounterView(from: 10).id(id)
Button("RESET") { id += 1 }
}
}
}
struct CounterView: View {
@StateObject private var viewModel: CounterViewModel
init(from: Int) {
_viewModel = .init(wrappedValue: .init(from: from))
}
var body: some View {
VStack {
Text("\(viewModel.count)")
.font(.largeTitle)
}
}
}
@MainActor
final class CounterViewModel: ObservableObject {
private let countdownTimer: CountdownModel
private var cancellables = Set<AnyCancellable>()
@Published var count: Int
init(from: Int) {
_count = .init(initialValue: from)
countdownTimer = .init(from: from)
Task { [weak self] in
guard let countSequence = await self?.countdownTimer.count else { return }
for await count in countSequence {
self?.count = count
}
}
.store(in: &cancellables)
}
deinit { print("🧹 CounterViewModel") }
}
actor CountdownModel {
private let countSubject = PassthroughSubject<Int, Never>()
private let from: Int
private var countTask: Task<Void, Never>?
private var cancellables: Set<AnyCancellable> = []
lazy var count = AsyncStream<Int> { continuation in
countSubject
.sink { count in
continuation.yield(count)
}
.store(in: &cancellables)
continuation.onTermination = { _ in
Task { await self.onTermination() }
}
}
init(from: Int) {
self.from = from
Task { await start() }
}
func start() {
countTask = Task {
for n in (0...from).reversed() {
countSubject.send(n)
try? await Task.sleep(nanoseconds: 1_000_000_000)
}
}
}
func onTermination() {
countTask?.cancel()
}
deinit { print("🧹 CountdownModel") }
}
extension Task {
func store(in cancellables: inout Set<AnyCancellable>) {
AnyCancellable { self.cancel() }.store(in: &cancellables)
}
private func asCancellable() -> AnyCancellable {
.init { self.cancel() }
}
}
@YusukeHosonuma
Copy link
Author

YusukeHosonuma commented Dec 1, 2022

メモリ回収の流れ(想定):

[class] CounterViewModel.deinit
→ [Task] CounterViewModel の init 内の Task の cancel
  → [Task] CountdownModel.count(AsyncStream) の cancel
    → [Task] CountdownModel.countTask の cancel
      → [actor] CountdownModel.deinit

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment