Swift・iOS

Swiftを中心に学んだことを記録に残すブログです。技術に関係ない記事もたまに書いています。

【Combine】Timerの処理をCombineを使って置き換える

 

はじめに

Appleのドキュメント"Replacing Foundation Timers with Timer Publishers"https://developer.apple.com/documentation/combine/replacing-foundation-timers-with-timer-publishers)に該当する内容です。Timerの処理をCombineを使って置き換えるサンプルを作ったので記事に残します。

 

開発環境

 

サンプルイメージ

f:id:hfoasi8fje3:20210822161726g:plain

 

本題

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を使って柔軟に対応できることがメリットだと思いました。

 

参考