🪶
Combine FWを使う場面
subscribeするもの
subscribeとは、購読するという意味。Stream
でデータを扱うビジネスロジックで使う場面がある。通信するものもあれば、タイマーだったり。常にデータがリアルタイムの状態のもの。ずっと読み込んでいるので、購読するという意味なのでしょう。
動画を見てみると、Streamを使ってデータを出力すると紹介されている。Streamを開始すると、サブスクリプションを購読する。途中で停止することもできる。今回だとこれをメモリに保存して、動画で紹介しているような、購読した値はパブリッシャーから受け取り、配列に保存する。
以下の例だと、Combineフレームワークを使用して、Streamでデータを時間を開始して、読み込み、状態を管理するクラスを作成しました。ObservableObjectプロトコルに準拠する必要があるので、class StopwatchManager の後につける。
import Foundation
import Combine
class StopwatchManager: ObservableObject {
@Published var elapsedTime: TimeInterval = 0
@Published var savedTimes: [TimeInterval] = []
private var timer: AnyCancellable?
private var startTime: Date?
// start timer
func start() {
startTime = Date()
timer = Timer.publish(every: 0.1, on: .main, in: .common)
.autoconnect()
.sink { [weak self] _ in
guard let self = self, let startTime = self.startTime else { return }
self.elapsedTime = Date().timeIntervalSince(startTime)
}
}
// stop timer
func pause() {
timer?.cancel()
startTime = nil
}
// reset timer
func reset() {
timer?.cancel()
startTime = nil
elapsedTime = 0
}
// array.append(value)
func saveTime() {
savedTimes.append(elapsedTime)
}
}
UIに表示するときは、@StateObject
を使って呼び出す。これで、タイマーの購読ができる。
import SwiftUI
struct ContentView: View {
@StateObject private var stopwatch = StopwatchManager()
var body: some View {
VStack {
Text(timeString(stopwatch.elapsedTime))
.font(.system(size: 50, weight: .bold, design: .monospaced))
.padding()
HStack(spacing: 20) {
Button(action: stopwatch.start) {
Image(systemName: "play.circle.fill")
.resizable()
.frame(width: 50, height: 50)
}
Button(action: stopwatch.pause) {
Image(systemName: "pause.circle.fill")
.resizable()
.frame(width: 50, height: 50)
}
Button(action: stopwatch.reset) {
Image(systemName: "stop.circle.fill")
.resizable()
.frame(width: 50, height: 50)
}
Button(action: stopwatch.saveTime) {
Image(systemName: "square.and.arrow.down.fill")
.resizable()
.frame(width: 50, height: 50)
}
}
.padding()
List {
ForEach(stopwatch.savedTimes, id: \.self) { time in
Text(timeString(time))
}
}
}
}
private func timeString(_ time: TimeInterval) -> String {
let hours = Int(time) / 3600
let minutes = Int(time) / 60 % 60
let seconds = Int(time) % 60
let centiseconds = Int((time.truncatingRemainder(dividingBy: 1)) * 100)
return String(format: "%02d:%02d:%02d.%02d", hours, minutes, seconds, centiseconds)
}
}
#Preview {
ContentView()
}
動作はこんな感じですね。
まとめ
Combineフレームワークの使い所は、リアルタイムのデータをサブスクライブするときですね。非同期にデータを読み込んで、1度しかデータを取得する必要がなければ、async/awaitの方が良い。
Discussion