⏰ 퀵라벨타이머 (QuickLabelTimer)
플랫폼·기술: iOS 앱, SwiftUI
앱 컨셉: 시간을 빠르게 설정할 수 있는 퀵 타이머에, ‘왜 맞췄는지’를 기록할 수 있는 라벨 기능을 결합한 타이머 앱
개발 목적: AI-Assisted Programming을 실제 서비스 개발에 적용하여 장단점을 살펴보고, 서비스 완성 및 운영 경험을 통해 설계 능력과 문제 해결 능력을 강화하고자 함
GitHub: https://github.com/data-sy/quick-label-timer
![]() | ![]() |
---|
이번 주는 삽질의 주였다.😭
앱의 핵심 기능인 알람 기능 구현에 도전했고, 지난주에 경험한 SRP 원칙을 적용해 보고자 클린 아키텍처 리팩토링을 진행했다.
[ 주요 작업 요약 ]
알람 기능을 구현할 때 가장 중요한 건 무엇일까?
(간격 주는 용도의 짤. 넣을 예정)
바로 애플워치를 벗는 것이다!!
단위 테스트도 잘 통과하고 시뮬레이터에서도 문제없었는데, 정작 실기기에서 소리가 안 났다.
혹시 iOS 정책 때문인가 싶어 헤맸는데…원인은 단순했다. 내가 워치를 차고 있었던 것 😱
(워치를 착용하면 iPhone 알림은 Apple Watch로 라우팅 된다)
iOS에서 알람 기능을 만드신다면, local notification
을 테스트할 때 아래 항목은 꼭 체크해 보시길…
이제 실제 구현 과정을 회고하자면, 그야말로 삽질의 연속이었다.
처음엔 정책을 이렇게 정리했다.
화면에는 로컬 알림이 배너로 뜨고, 소리와 진동은 오디오 세션을 통해 사용자가 확실히 인지할 때까지 울리도록 설계했다.
(이미지 위치 조절 예정)
단위 테스트도 잘 통과하고, 간단한 실기기 테스트에서도 정상적으로 동작했다.
“됐다!” 싶었던 순간…
타이머 시간을 조금 길게 설정하자, 소리·진동이 울리지 않았다.
원인은 바로 iOS의 Suspended 상태였다.
iOS는 앱이 사용되지 않으면 자동으로 Suspended 상태로 전환하고 개발자는 임의로 이 진입을 막을 수 없다. 애플이 허용한 일부 백그라운드 모드에서만 제한적으로 실행을 이어갈 수 있는데, 알람 어플은 해당 사항이 없다. 즉, 앱이 한 번 Suspended로 전환되면 실행이 완전히 멈추기 때문에, 오디오 세션을 실행시키는 함수가 호출 자체가 안 된 것이다.
어쩐지 구글링을 해도, 스택 오버플로우를 디깅해도 정보가 없더라니… “시중 앱에서 가능하니까 방법이 있겠지” 생각하고 넘긴 게 안일한 판단이었다.
눈물이 앞을 가리니 더 이상의 자세한 설명은 생략한다... 👉 삽질 기록은 여기... (추가 예정)
최종적으로, 사용자 기기 설정(무음 모드, 소리 볼륨 등)에 영향을 받지 않는 알람 앱을 만들기 위해
피할 수 없는 정책적/기술적 관문은 다음과 같다.
방법 | 원리 | 장점 | 단점 | 대안 |
---|---|---|---|---|
로컬 알림 | iOS가 예약·발송 | Suspended 상태에서도 알림 가능 | 기기 설정에 종속 | 무음 모드 시 울리지 않는다고 안내 |
오디오 세션 | 앱이 직접 제어 | 기기 설정과 상관없이 소리/진동 울림 | Suspended 상태에서 동작 안 함 | (iOS 정책 우회) 무음 사운드를 틀어 백그라운드 유지 |
이 제약을 고려해 떠올린 시나리오는 이랬다:
- 외부 푸시 알림으로 타이머 종료 시간 5분 전 앱을 깨우기
- 5분간 무음 사운드를 재생해 앱이 Suspended 상태가 되지 않도록 유지
- 타이머 종료 시점에 오디오 세션으로 알람 울리기
하지만 이 방식은 구현 과정에서 실패할 가능성이 있고, iOS 정책을 우회하는 방법이라 리젝 리스크가 있어 고민이 되었다. 이때, 지난주 회고에서 정리했던 말이 떠올랐다. 좋은 UX는 머릿속 상상이 아니라 실제 피드백으로 검증된다.
기능 구현도 마찬가지일 것이다. 이번에는 ‘완벽 대신 완성’을 모토로, 우선 로컬 알림 기반 MVP를 내기로 결정했다.
지난주에 SRP의 중요성을 크게 느낀 뒤, 이번 주에는 아키텍처 개선을 시도했다.
TimerManager
의 책임을 Repository와 Service로 분리완벽하게 원칙을 따르려면 더 작은 단위(엔진 등)까지 쪼갤 수 있었지만, 지난 2주 동안 경험했듯이 과도하게 깊이 파고들면 우선순위가 밀린다는 걸 알았다.
그래서 이번에는 Service/Repository 수준까지만 분리하고 마무리했다.
(이전 아키텍처 다이어그램 → 새로운 아키텍처 다이어그램 첨부 예정)
이번 개선 과정에서 클린 아키텍처를 공부하며 도움이 된 블로그 링크도 함께 남겨본다. [iOS] Clean Architecture + MVVM + Observable 사용해서 날씨앱 리팩토링하기