일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- UICollectionViewFlowLayout
- weatherKit
- UIAlertAction
- SwiftUI Boolean 값
- MKMapItem
- xcode로 날씨앱 만들기
- Protocol
- Startign Assignments
- 한국어 개인정보처리방침
- Required Reason API
- CoreLocation
- WeatherManager
- 러닝기록앱
- 러닝타이머
- Timer
- font book
- swift
- 클로저의 캡슐화
- RunningTimer
- addannotation
- 서체관리자
- CLLocationManagerDelegate
- MKMapViewDelegate
- weak var
- dispatchsource
- 영문 개인정보처리방침
- 단일 책임원칙
- App Store Connect
- Xcode
- AnyObject
- Today
- Total
VesselWheel
러닝기록 타이머 만들기(1/3)(with thread, RunLoop) 본문
결론부터 말하자면, dispatchsource 클래스를 활용하여 러닝기록 타이머를 구현한다.
이 글의 마지막으로 내려가서 (3/3) 의 글을 보시고, 이해를 위해서 현재 보고 있는 글을 참고하시라.
공식문서를 읽기 전에 참고 블로그로 대략적인 흐름에 대해 이해하려했다.
https://please-amend.tistory.com/35?category=1172099
그리고 나서, 다시 공식문서을 읽었다.
https://developer.apple.com/documentation/foundation/timer
러닝기록 타이머를 구현하기 위해서 Timer 클래스의 scheduledTimer를 사용하면 된다고한다.
https://developer.apple.com/documentation/foundation/timer/1415941-scheduledtimer
또한
DispatchSourceTimer를 활용해서 타이머를 만들 수 있다고 한다.
-> 해당 내용은 (3/3)에서 다루고자 한다.
https://developer.apple.com/documentation/dispatch/dispatchsourcetimer/
우선 Timer 클래스와 RunLoop에 대해서 알아보았다. 그리고나서, 기능 구현을 진행해나아갔다.
1. 공식문서에 의하면 Run Loop에 타이머를 등록해야한다. 이 때 사용되는 코드가 아래와 같다.
scheduledTimer(timeInterval:invocation:repeats:) 혹은
scheduledTimer(timeInterval:target:selector:userInfo:repeats:)
-> 위 2가지 매소드의 차이점은 아래와 같다.
- scheduledTimer(timeInterval:invocation:repeats:) 메서드:
- 이 메서드는 NSInvocation 객체를 사용하여 타이머가 트리거될 때 실행되는 메서드를 지정합니다.
- NSInvocation은 Objective-C 런타임에서 메서드 호출을 나타내는 객체로, 메서드의 선택자(selector), 대상(target), 인자(arguments) 등을 포함하고 있습니다.
- Swift에서는 NSInvocation을 지원하지 않아, 이 메서드는 Swift에서 사용할 수 없습니다.
- scheduledTimer(timeInterval:target:selector:userInfo:repeats:) 메서드:
- 이 메서드는 대상 객체와 선택자를 직접 지정하여 타이머가 트리거될 때 실행되는 메서드를 지정합니다.
- target은 메서드가 있는 객체를 나타내고, selector는 실행할 메서드를 나타냅니다.
- Swift에서는 #selector 문법을 사용하여 메서드를 지정할 수 있습니다.
따라서, Swift에서 타이머를 사용하려면 scheduledTimer(timeInterval:target:selector:userInfo:repeats:) 메서드를 사용해야 합니다.
-> 공식문서를 읽어서는 알 수 없는 내용을, 결국 Chat GPT에게 물어보았다.
그리하여,
scheduledTimer(timeInterval:target:selector:userInfo:repeats:) 를 사용해보자.
https://developer.apple.com/documentation/foundation/timer/1412416-scheduledtimer
시작하기 전에 Timer 클래스에는 내부 매소드가 있다. 알아보고 지나가자.
Swift의 `Timer` 클래스는 다양한 메서드를 가지고 있습니다. 여기에는 `fire()`, `invalidate()` 등이 포함되어 있습니다.
주요 메서드들에 대해 설명하겠습니다.
1. `fire()`: 이 메서드는 타이머를 즉시 실행합니다. 일반적으로 타이머는 설정된 간격에 따라 실행되지만, `fire()` 메서드를 호출하면 해당 시점에 타이머의 액션을 즉시 실행할 수 있습니다.
2. `invalidate()`: 이 메서드는 타이머를 무효화하고, 더 이상 메시지를 보내지 않도록 합니다. 타이머를 중지하거나 제거할 때 사용합니다.
3. `scheduledTimer(timeInterval:target:selector:userInfo:repeats:)`: 이 메서드는 주어진 시간 간격으로 타이머를 생성하고, 현재의 런루프에 스케줄링합니다. 타이머가 트리거될 때 실행할 메서드를 지정할 수 있습니다.
4. `scheduledTimer(withTimeInterval:repeats:block:)`: 이 메서드는 주어진 시간 간격으로 타이머를 생성하고, 현재의 런루프에 스케줄링합니다. 타이머가 트리거될 때 실행할 클로저를 지정할 수 있습니다.
5. `init(timeInterval:target:selector:userInfo:repeats:)`: 이 메서드는 주어진 시간 간격으로 타이머를 생성하지만, 런루프에 자동으로 추가하지 않습니다. 만들어진 타이머를 수동으로 런루프에 추가해야 합니다.
6. `init(timeInterval:invocation:repeats:)`: 이 메서드는 `NSInvocation` 객체를 사용하여 타이머를 생성합니다. 하지만 Swift에서는 지원하지 않는 기능입니다. 위의 메서드 외에도 `Timer` 클래스는 `timeInterval`, `isValid`, `userInfo` 등의 속성을 제공하여 타이머의 동작을 더욱 세밀하게 제어할 수 있습니다. -> 사용할 수 없다.
//
// MyTimer.swift
// Run-It
//
// Created by Jason Yang on 2/26/24.
//
import Foundation
class MyTimer {
var timer: Timer?
var time = 0
var distance = 0.0
var pace = 0.0
var pausedTime = 0 // 일시정지된 시간을 저장할 변수
var pausedDistance = 0.0 // 일시정지된 거리를 저장할 변수
var pausedPace = 0.0 // 일시정지된 페이스를 저장할 변수
var isPaused = false
var updateUI: (() -> Void)?
init(updateUI: @escaping () -> Void) {
self.updateUI = updateUI
startTimer()
}
@objc func timerFired() {
// 이 메서드는 타이머가 동작할 때마다 호출됩니다.
DispatchQueue.main.async {
self.time += 1
self.distance += 0.01 // RunningTimerManager과 연계해서 변경 필요
// 거리가 0.05km, 즉 50m 이상일 때만 페이스를 계산
self.pace = self.distance >= 0.05 ? Double(self.time) / self.distance : 0
// print(self.time)
// print(self.distance)
self.updateUI?() // 상태가 변경될 때마다 UI 업데이트
}
}
public func startTimer() {
DispatchQueue.main.async {
self.timer = Timer(timeInterval: 1.0, target: self, selector: #selector(self.timerFired), userInfo: nil, repeats: true)
RunLoop.current.add(self.timer!, forMode: .common)
self.timer?.fire()
}
}
public func pauseTimer() {
pausedTime = time // 일시정지 시점의 시간 저장
pausedDistance = distance // 일시정지 시점의 거리 저장
pausedPace = pace // 일시정지 시점의 페이스 저장
timer?.invalidate()
}
public func resumeTimer() {
// 일시정지 했던 시점의 시간, 거리, 페이스를 복원
time = pausedTime
distance = pausedDistance
pace = pausedPace
startTimer()
}
public func stopTimer() {
timer?.invalidate()
timer = nil
}
}
1. 러닝기록 타이머를 책임질 class MyTimer 를 생성하였다. - Timer 클래스를 사용하기 위해 인스턴스화 해주고 나서,
var timer: Timer?
2. Timer 클래스에서 이용한 인스턴스를 생성한다.
var time = 0
var distance = 0.0
var pace = 0.0
var pausedTime = 0 // 일시정지된 시간을 저장할 변수
var pausedDistance = 0.0 // 일시정지된 거리를 저장할 변수
var pausedPace = 0.0 // 일시정지된 페이스를 저장할 변수
var isPaused = false
var updateUI: (() -> Void)?
3. MyTimer 클래스가 초기화 될 때, 뷰에서 러닝기록과 관련된 UI 프로퍼티가 최신화 되어야하기 때문에, 클로져함수로 UI를 업데이트하면서 Timer 클래스의 내장 타이머 기능 매소드를 실행해준다.
init(updateUI: @escaping () -> Void) {
self.updateUI = updateUI
startTimer()
}
4. timerFired()를 통해서 시간, 거리에 따른 페이스가 UI에서 변경되어야하기때문에 Main에서 비동기로 최신화해주고, UI를 업데이트해준다.
- startTimer()매소드를 통해서 timerFired매소드는 화면이 꺼지더라도 계속 실행되어 러닝기록을 유지해야기 때문에, RunLoop로 current에 .common모드로 타이머를 가동한다.
- self.timer?.fire()를 통해, 타이머를 작동시켜준다.
- startTimer()는 view에서 호출하여 UI 버튼과 연계하여 사용한다.
@objc func timerFired() {
// 이 메서드는 타이머가 동작할 때마다 호출됩니다.
DispatchQueue.main.async {
self.time += 1
self.distance += 0.01 // RunningTimerManager과 연계해서 변경 필요
// 거리가 0.05km, 즉 50m 이상일 때만 페이스를 계산
self.pace = self.distance >= 0.05 ? Double(self.time) / self.distance : 0
// print(self.time)
// print(self.distance)
self.updateUI?() // 상태가 변경될 때마다 UI 업데이트
}
}
public func startTimer() {
DispatchQueue.main.async {
self.timer = Timer(timeInterval: 1.0, target: self, selector: #selector(self.timerFired), userInfo: nil, repeats: true)
RunLoop.current.add(self.timer!, forMode: .common)
self.timer?.fire()
}
}
public func pauseTimer() {
pausedTime = time // 일시정지 시점의 시간 저장
pausedDistance = distance // 일시정지 시점의 거리 저장
pausedPace = pace // 일시정지 시점의 페이스 저장
timer?.invalidate()
}
public func resumeTimer() {
// 일시정지 했던 시점의 시간, 거리, 페이스를 복원
time = pausedTime
distance = pausedDistance
pace = pausedPace
startTimer()
}
public func stopTimer() {
timer?.invalidate()
timer = nil
}
}
- 추가적으로, 러닝기록을 일시정지, 재실행, 정지 기능을 public으로 view에서 호출할 수 있도록 한다.
-> Timer 클래스에는 fire() 매소드와 invalidate() 매소드만 존재하기 때문에, 타이머를 재실행하기 위해 resume이나 일시정지하는 pause를 직접구현할 수 없었다. 따라서 커스텀 매소드를 만들어 구현하였다.
이로써, 코드단에서 다소 복잡하고 코드가 길어진다는 문제점을 발견하였다.
따라서, dispatchsource 클래스를 활용해서 타이머를 구현해보고자 한다.
[아래의 이어지는 블로그 참조]
https://vesselwheel.tistory.com/204
'Xcode Study' 카테고리의 다른 글
러닝기록 타이머 만들기(2/3)(with thread, RunLoop) (0) | 2024.02.27 |
---|---|
TIL의 중요성(feat. 능소부능대(能小復能大)) (0) | 2024.02.27 |
UIPageController(작성중) (0) | 2024.02.22 |
MapKit을 활용한 킥보드 앱 지도화면 만들기 (0) | 2024.02.21 |
TIL 열심히 하자! (0) | 2024.02.20 |