본문 바로가기

iOS

Background Timer

개인 프로젝트를 진행하면서 발생했던 Background Timer 구현 과정입니다.

타이머 기능을 구현하기 위해 애플리케이션의 생명주기를 관리하고,

사용자와의 상호작용과 같은 다양한 이벤트를 처리하는 Run Loop 에 대한 개념을 익힌 뒤

메인 스레드의 RunLoop에 타이머 추가하고 삭제하는 scheduledTimer, invalidate 메서드를 이용하여 타이머를 사용했습니다.

 

기본적인 타이머의 시작, 중단, 리셋 기능은 쉽게 구현할 수 있었지만,
사용자가 E-Book 을 읽거나, 다른 기능을 이용하거나, 앱이 백그라운드로 이동하는 등
타이머를 중단하지 않는 한 타이머의 시간이 흐르도록 다양한 상황을 고려하고 대응해야 했습니다.

 

저는 보통 처음 기능을 구현할 때 많은 정보를 찾아보고 이해한 뒤, 종합해서 코드에 녹이는 형식으로 진행하는 편입니다.

 

백그라운드 타이머 기능을 구현할 때 가장 참고를 많이했던 영상이기에 혹시나 이 글을 읽고 필요한 기능이라면 참고하면 좋겠습니다.
https://www.youtube.com/watch?v=GzwLobVuXXI&t=2s


백그라운드 타이머 핵심 기능 및 고려한 지점

1. 현재 시간 - 시작 시간의 차를 이용해 타이머의 시간을 계산

 

2. 백그라운드 타이머를 구현하기 위해 UserDefaluts 에 시작시간, 중지시간, 타이머 Checker 를 싱글 톤 패턴으로 구성하여 저장했고, 저장해둔 UserDefaluts 정보를 읽어와서 타이머 상태에 맞게 1번에서 사용했던 계산방법을 이용해 시간을 표기했습니다.

 

3. 또한 백그라운드에서 화면에서 다시 앱을 사용했을 때 사용자에게 어떤 책의 타이머가 실행 됐는지 알리는 기능을 구현하기 위해 다양한 개념을 학습했습니다.

SceneDelegate 의 실행 순서는 willConnectTo -> sceneWillEnterForeground -> sceneDidBecomeActive 입니다.


저는 Background -> Foreground 상태로 왔을 때,

sceneWillEnterForeground 메서드를 통해 토스트 메세지를 띄워주려고 했으나,
토스트 메세지는 온전히 앱이 이벤트를 받을 준비가 되었을 때 정상적으로 나타나기 때문에

원하는 시점에 토스트 메세지를 띄울 수 없는 문제가 있었습니다.

최종적으로, 스위치 상태에서 화면에 들어올 경우
화면이 모두 구성되고, 온전히 이벤트를 받을 준비가 되는 sceneDidBecomeActive 메서드를 이용했습니다.

 

하지만, 이제 VC 에 토스트 메세지를 보낼 수 있었지만,

'사용자가 다시 화면에 돌아왔을 때 어떤 화면을 이용할지 모른다'는 문제가 발생했습니다.

"어떻게 해야 모든 화면에서 감지하고 토스트 메세지를 뿌려줄 수 있을까?"

고민한 끝에 찾은 방법은 window 는 반드시 RootVC 를 갖고 있어야하고, 다른 VC 는 RootVC 를 바라보고 있다고 합니다.

RootVC 는 최상위 뷰컨트롤러로서 현재 프로젝트에 Tabbar 로 이루어져있는데,
탭바가 갖고있는 자식 VC 를 순회하는 아이디어를 사용했습니다.

RootVC.children 프로퍼티로 자식 VC 를 알 수 있었고,
window.rootVC 를 통해 루트VC 부터 ~ 자식 VC 까지 모두 순회하며 메세지를 보내게끔 구현했습니다.

 

swift 의 강력한 기능인 Protocol + Extension 을 사용해 BaseVC 에서 토스트를 사용하게끔 채택하고 준수했습니다.

 
이후  window.rootVC 정보를 이용해 자식 VC 를 순회하며,
RunningTimerBookMessageProtocol 을 채택한 VC 에 토스트 메세지를 모두 뿌려주며 해결하였습니다.


하지만, 이 코드는 어느 화면에서나 토스트 메세지를 받을 수 있다는 장점이 있지만,

타이머가 실행되고 있는 경우, 앱을 종료하고 다시 앱을 실행했을 때 모든 VC 를 순회하기에

화면에 들어오자마자 모든 화면의 ViewDidLoad 가 진행 되는 단점이 있기에 추후 업데이트 사항으로 해결할 예정입니다.