내배캠 토이 프로젝트로 ios 시계앱을 만들게 되었다.
4개의 탭 중 내가 타이머 부분을 맡게 되었다.
이하는 만들었던 타이머 객체와 메서드를 기록
timers
를 BehaviorRelay로 선언var timers = BehaviorRelay<[TimerModel]>(value: [])
이 코드에서 BehaviorRelay<[TimerModel]>
타입인 timers
는 타이머의 상태를 관리하고, 여러 뷰 컨트롤러와의 데이터 바인딩을 위해 활용된다.
현재 설정된 모든 타이머의 리스트를 관리하고 있다. BehaviorRelay
는 항상 최신 값을 유지하며, 새로운 구독자가 생길 때 마지막으로 방출된 값을 즉시 전달한다. 초기값으로는 빈 배열 []
이 설정되어 있으며, 타이머가 추가되거나 삭제될 때마다 이 배열이 업데이트된다.
timer의 id, remainingTime, isRunning을 관리한다.
struct TimerModel {
let id: UUID
let remainingTime: BehaviorRelay<TimeInterval>
let isRunning: BehaviorRelay<Bool>
}
모델 타입을 저렇게 지정하는 이유는 여기에서 다루기로.
TimeInterval
: 시간을 초 단위로 표현하는 Double 타입의 타입 별칭으로, 실수 값을 사용해 시간의 길이를 나타낸다.
- 단위: TimeInterval의 단위는 초(Seconds) 로,1분은 60.0, 1시간은 3600.0으로 표현된다.
- 타이머, 지연(딜레이) 처리, 날짜 및 시간 간의 차이 계산 등에서 편리하게 사용할 수 있다.
새로운 타이머를 설정할 때 setNewTimer(time:)
메서드를 통해 timers
에 새로운 TimerModel
이 추가된다. 이때 accept
메서드를 사용하여 기존 배열에 새로운 타이머를 추가하고, 업데이트된 배열을 방출한다.
func setNewTimer(time: TimeInterval) {
let newTimer = TimerModel(id: UUID(),
remainingTime: BehaviorRelay<TimeInterval>(value: time),
isRunning: BehaviorRelay<Bool>(value: true))
timers.accept(timers.value + [newTimer])
startTimer(id: newTimer.id)
}
timers.accept(timers.value + [newTimer])
: 기존의 타이머 리스트에 새로운 타이머를 추가한 후, timers
에 새로운 값을 전달하여 모든 구독자에게 업데이트된 타이머 리스트를 방출한다.
accept
메서드
: RxCocoa의 BehaviorRelay와 PublishRelay에서 값을 업데이트하고, 그 값을 구독자들에게 방출하는 데 사용됨
타이머 id를 통해 해당 타이머를 가져온다.
func getTimer(id: UUID) -> TimerModel? {
return timers.value.first(where: { $0.id == id })
}
특정 id의 타이머를 시작한다.
func startTimer(id: UUID) {
guard let timer = getTimer(id: id), timer.isRunning.value else { return }
if let previousSubscription = timerSubscription[id] {
previousSubscription.dispose()
timerSubscription.removeValue(forKey: id)
}
let subscription = Observable<Int>.interval(.milliseconds(100), scheduler: MainScheduler.instance)
.subscribe(onNext: { [weak self] _ in
guard let self = self else { return }
let currentTime = max(timer.remainingTime.value - 0.1, 0)
timer.remainingTime.accept(currentTime)
if currentTime <= 0 {
self.endTimer(id: id)
self.timerSubscription[id]?.dispose()
self.timerSubscription.removeValue(forKey: id)
}
})
timerSubscription[id] = subscription
}
getTimer(id:)
메서드를 통해 해당 타이머를 가져오고, 타이머가 이미 실행 중인지 확인한다(timer.isRunning.value
). 타이머가 실행 중이 아니면 메서드를 종료한다.
타이머가 이미 구독 중이라면(timerSubscription[id]
), 기존 구독을 해제하고(dispose
), 메모리에서 제거한다. 일시정지 후 다시 시작할 때 중복 처리되는 것을 방지할 수 있음.
Observable<Int>.interval
을 사용하여 100밀리초 간격으로 타이머를 업데이트한다. 남은 시간을 계산하고(timer.remainingTime.accept(currentTime)
)에 저장한다.
남은 시간이 0 이하가 되면 endTimer(id:)
메서드를 호출하여 타이머를 종료하고, 구독을 해제한다.
pauseTimer
메서드는 특정 타이머를 일시정지하는 역할을 한다.
func pauseTimer(id: UUID) {
guard let timer = getTimer(id: id) else { return }
timer.isRunning.accept(false)
let remainingTime = timer.remainingTime.value
NotificationManager.shared.cancelNotification(identifier: id.uuidString)
timerSubscription[id]?.dispose()
timerSubscription.removeValue(forKey: id)
}
getTimer(id:)
메서드를 통해 해당 타이머를 가져오고, 타이머의 실행 상태를 false
로 설정한다. (timer.isRunning.accept(false)
)
알림을 취소한다. (NotificationManager.shared.cancelNotification
)
타이머의 구독을 해제하고, 구독 정보를 메모리에서 제거한다.
특정 타이머를 일시정지 상태에서 재개하는 역할을 한다.
func resumeTimer(id: UUID) {
guard let timer = getTimer(id: id) else { return }
timer.remainingTime.accept(remainingTime)
timer.isRunning.accept(true)
let newEndTime = Date().addingTimeInterval(remainingTime)
notification(id: id, endTime: newEndTime)
startTimer(id: timer.id)
}
불러온 남은 시간을 timer.remainingTime.accept(remainingTime)
으로 설정하고, 타이머의 실행 상태를 true
로 변경한다(timer.isRunning.accept(true)
).
새로운 종료 시간을 계산하고(newEndTime
), 알림을 다시 설정한다.
startTimer(id:)
메서드를 호출하여 타이머를 재시작한다.
특정 타이머를 취소하는 역할을 한다.
func cancelTimer(id: UUID) {
guard let timer = getTimer(id: id) else { return }
pauseTimer(id: id)
timer.remainingTime.accept(0)
NotificationManager.shared.cancelNotification(identifier: id.uuidString)
removeTimer(id: id)
}
getTimer(id:)
메서드를 통해 해당 타이머를 가져오고, pauseTimer(id:)
를 호출하여 타이머를 일시정지한다.
타이머의 남은 시간을 0으로 설정한다. (timer.remainingTime.accept(0)
)
알림을 취소하고(NotificationManager.shared.cancelNotification
), 타이머 리스트에서 해당 타이머를 제거한다(removeTimer(id:)
).
타이머가 삭제되거나 종료될 때도 timers
의 값을 업데이트한다. 이 경우 removeTimer(id:)
메서드를 통해 해당 타이머를 리스트에서 제거하고, 변경된 타이머 리스트를 timers
에 전달한다.
func removeTimer(id: UUID) {
timers.accept(timers.value.filter { $0.id != id })
}
timers.accept(timers.value.filter { $0.id != id })
: 특정 타이머를 제거한 새로운 타이머 리스트를 timers
에 전달하여, 구독자에게 최신 타이머 상태를 알린다.