본문 바로가기
iOS/iOS

[iOS] DispatchSourceTimer를 사용한 타이머

by 원만사 2022. 1. 11.
반응형

 

타이머를 기반으로 이벤트 핸들러 블록을 제출하는 디스패치 소스

 

protocol DispatchSourceTimer

 

 DispatchSourceTimer를 사용할 때 사용자는 자신의 객체에 이 프로토콜을 직접 채택하지 않아도 된다. 대신 makeTimerSource(flags:queue:) 메서드를 사용하여 이 프로토콜을 채택하는 객체를 만든다.

 

DispatchSource.makeTimerSource(flags:queue:)

타이머 이벤트를 모니터링 하기 위한 새 디스패치 소스 객체를 만든다.

 

class func makeTimerSource(flags: DispatchSource.TimerFlags = [], queue: DispatchQueue? = nil) 
	-> DispatchSourceTimer

 

[flags]

 타이머의 동작을 나타내는 추가 flag이다. 가능한 값 목록은 DispatchSource.TimerFlags를 참조.

 

[queue]

 설치된 핸들러를 실행할 디스패치 큐를 나타낸다.

 

 

 

 

타이머 생성하기

 위의 사진에서 시작버튼을 누르면 timer에 다음과 같은 방법으로 DispatchSourceTimer를 만들어준다. 

private func startTimer() {
        if self.timer != nil {
            return
        }
        
        // flag는 따로 주지 않고 핸들러는 main 스레드에서 실행하도록 설정
        self.timer = DispatchSource.makeTimerSource(flags: [], queue: .main)
        
        // 지정된 기한, 반복 간격으로 타이머를 스케줄한다.
        // now는 바로 실행한다는 것을 의미하고 1초마다 반복한다.
        self.timer?.schedule(deadline: .now(), repeating: 1)
        
        // 핸들러 설정
        self.timer?.setEventHandler(handler: { [weak self] in
            
            guard let self = self else { return }
            
            self.currentSeconds -= 1
            
            let hour = self.currentSeconds / 3600
            let minute = (self.currentSeconds % 3600) / 60
            let seconds = (self.currentSeconds % 3600) % 60
            
            self.timerLabel.text = String(format: "%02d:%02d:%02d", hour, minute, seconds)
            
            self.progressView.progress = Float(self.currentSeconds) / Float(self.duration)
            
            if self.currentSeconds <= 0 {
                self.stopTimer()
            }
        })
        
        // 타이머 시작
        self.timer?.resume()
    }

 

  1. makeTimerSource를 통해 객체를 생성한다.
  2. schedule()로 타이머의 schedule을 설정한다.
  3. setEventHandler를 통해 이벤트를 설정한다.
  4. 마지막에 resume()을 통해 타이머를 시작한다. 

 

타이머 일시정지

 일시정지 버튼을 누르면 다음과 같이 suspend()를 통해 타이머를 정지한다. 

    @IBAction func tapPauseButton(_ sender: UIButton) {
        switch self.status {
        case .start:
            self.status = .pause
            self.timer?.suspend()
            
        default:
            break 
        }
    }

 

타이머 종료하기

 취소 버튼을 누르면 다음과 같이 cancel()을 통해 타이머를 취소한다.

    private func stopTimer() {
        if self.status == .pause {
            self.timer?.resume()
        }
        
        self.status = .end
        self.timer?.cancel()
        
        self.timerLabel.text = "00:00:00"
        self.progressView.progress = 1.0
        
        self.timer = nil // 메모리에서 해제시켜줘야 함.
    }

 

 주의할 것은 일시정지(suspend) 상태에서는 cancel을 할 수 없다. 그렇기 때문에 만약 현재 타이머의 상태가 일시정지 상태라면 먼저 resume()을 통해 타이머를 시작시켜 cancel이 가능한 상태로 바꿔주는 작업이 필요하다. 

 또한 타이머를 메모리에서 해제시켜주는 작업도 해줘야 한다. 그렇지 않으면 화면을 벗어나도 타이머가 계속 동작할 수 있다.

 

동작은 다음과 같이 진행된다.

 

 

 

 전체 코드는 다음과 같다.

import UIKit

enum Status {
    case start, pause, end
}

class ViewController: UIViewController {
    
    @IBOutlet weak var countDownPicker: UIDatePicker!
    
    @IBOutlet weak var timerLabel: UILabel!
    @IBOutlet weak var progressView: UIProgressView!
    
    var status: Status = .end
    var duration = 60
    var timer: DispatchSourceTimer?
    var currentSeconds = 0 // 현재 카운트다운 되고 있는 시간을 초 형태로 저장
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func tapCancelButton(_ sender: UIButton) {
        switch self.status {
        case .pause, .start:
            self.stopTimer()
            
        default:
            break
        }
    }
    
    @IBAction func tapPauseButton(_ sender: UIButton) {
        switch self.status {
        case .start:
            self.status = .pause
            self.timer?.suspend()
            
        default:
            break 
        }
    }
    
    @IBAction func tapStartButton(_ sender: UIButton) {
        self.duration = Int(self.countDownPicker.countDownDuration)
        
        switch self.status {
        case .end:
            self.status = .start
            self.currentSeconds = self.duration
            self.startTimer()
            
        case .pause:
            self.status = .start
            self.timer?.resume()
            
        default:
            break
        }
    }
}

extension ViewController {
    private func startTimer() {
        if self.timer != nil {
            return
        }
        
        // flag는 따로 주지 않고 핸들러는 main 스레드에서 실행하도록 설정
        // 원하는 설정이 UI와 관련되어 있다면 main 스레드에서 실행되도록 해야 한다.
        self.timer = DispatchSource.makeTimerSource(flags: [], queue: .main)
        
        // 지정된 기한, 반복 간격으로 타이머를 스케줄한다.
        // now는 바로 실행한다는 것을 의미하고 1초마다 반복한다.
        self.timer?.schedule(deadline: .now(), repeating: 1)
        
        // 핸들러 설정
        self.timer?.setEventHandler(handler: { [weak self] in
            
            guard let self = self else { return }
            
            self.currentSeconds -= 1
            
            let hour = self.currentSeconds / 3600
            let minute = (self.currentSeconds % 3600) / 60
            let seconds = (self.currentSeconds % 3600) % 60
            
            self.timerLabel.text = String(format: "%02d:%02d:%02d", hour, minute, seconds)
            
            self.progressView.progress = Float(self.currentSeconds) / Float(self.duration)
            
            if self.currentSeconds <= 0 {
                self.stopTimer()
            }
        })
        
        // 타이머 시작
        self.timer?.resume()
    }
    
    private func stopTimer() {
        if self.status == .pause {
            self.timer?.resume()
        }
        
        self.status = .end
        self.timer?.cancel()
        
        self.timerLabel.text = "00:00:00"
        self.progressView.progress = 1.0
        
        self.timer = nil // 메모리에서 해제시켜줘야 함.
    }
}

 

 

REFERENCES

- https://developer.apple.com/documentation/dispatch/dispatchsourcetimer

- https://onelife2live.tistory.com/44

 

반응형

댓글