はじめに
Appleのドキュメント"Replacing Foundation Timers with Timer Publishers"(https://developer.apple.com/documentation/combine/replacing-foundation-timers-with-timer-publishers)に該当する内容です。Timerの処理をCombineを使って置き換えるサンプルを作ったので記事に残します。
開発環境
- macOS Big Sur 11.5.2
- Xcode 12.5.1
- Swift 5.4.2
サンプルイメージ
本題
Combineを使わない場合
"scheduledTimer(withTimeInterval:repeats:block:)"を使う。ContentViewModelのstartCountingが実装箇所。
※参考:https://developer.apple.com/documentation/foundation/timer/2091889-scheduledtimer
ContentView.swift
import SwiftUI struct ContentView: View { @ObservedObject private var viewModel: ContentViewModel init(viewModel: ContentViewModel) { self.viewModel = viewModel } var body: some View { VStack { Text("\(viewModel.count)") .font(.title) .fontWeight(.bold) .padding() Button("Start") { viewModel.startCounting() } .disabled(viewModel.isTimerRunning) Button("Stop") { viewModel.stopCounting() } .disabled(!viewModel.isTimerRunning) .padding() Button("Reset") { viewModel.resetCount() } } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView(viewModel: ContentViewModel()) } }
ContentViewModel.swift
import Foundation final class ContentViewModel: ObservableObject { @Published var count = 0 @Published var isTimerRunning = false private var timer: Timer? func startCounting() { isTimerRunning = true timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in self.count += 1 } } func stopCounting() { isTimerRunning = false timer?.invalidate() } func resetCount() { count = 0 } }
Combineを使う場合
"Timer.TimerPublisher"を使って"scheduledTimer(withTimeInterval:repeats:block:)"を置き換える。
※参考:https://developer.apple.com/documentation/foundation/timer/timerpublisher
※ContentView.swiftの実装内容はCombineを使わない場合と同じです。
ContentViewModel.swift
import Foundation import Combine final class ContentViewModel: ObservableObject { @Published var count = 0 @Published var isTimerRunning = false private var cancellable: AnyCancellable? func startCounting() { isTimerRunning = true cancellable = Timer.publish(every: 1.0, on: .main, in: .common) .autoconnect() .sink { _ in self.count += 1 } } func stopCounting() { isTimerRunning = false cancellable?.cancel() } func resetCount() { count = 0 } }
おわりに
今回のような簡単なサンプルではわかりにくいのですが、Combineの場合は複雑な処理にもOperatorsを使って柔軟に対応できることがメリットだと思いました。
参考
-
https://developer.apple.com/documentation/combine/replacing-foundation-timers-with-timer-publishers
-
https://developer.apple.com/documentation/foundation/timer/timerpublisher
-
https://developer.apple.com/documentation/combine/connectablepublisher/autoconnect()
-
https://developer.apple.com/documentation/foundation/timer/2091889-scheduledtimer