
[이미지는 chatGPT가. 뭐야. 야 너, 왜 이렇게 좋아졌어]
iOS 개발을 해오면서, GPS 권한 중에 '항상 허용'을 써본 적이 단 한 번도 없다.
생각해보면 그럴 만도 한게, '앱 사용 중 허용'으로도 대부분의 기능이 다 되니까. 굳이 '항상 허용'을 써야 하는 케이스가 별로 없었다.
그리고 내가 '항상 허용'을 싫어했다.
왜 내 위치를 항상 알고 싶은건데?! 난 알려주고 싶지 않다고.
하지만 그것도 월급 앞에선 무력하다. 해보란다.
그래 뭐.
귀찮으니까 클로드 코드한테 시켜봤다.
만들어달라고 한 것
요구사항은 단순하게 정리.
- 점심 먹으러 갈 식당이랑 식사 후 갈 카페 위치를 등록
- 폰 들고 그 장소에 도착하면 알림이 뜨는 앱
- iOS 16, Swift, SwiftUI
- 설정한 위치의 반경 10미터, local notification
- 빌드 테스트는 xcodebuildmcp로 알아서
xcodegen이 깔려있고 xcodebuildmcp를 붙여놓은 덕분에 클로드가 알아서 project.yml 만들고, xcodegen으로 프로젝트 생성하고, 시뮬레이터에 빌드해서 띄워줬다.
세상 참... 좋아졌다.
그런데 '항상 허용'이 안 나오네?
테스트 앱이 떴는데, 권한 요청 버튼을 눌렀더니 '앱 사용 중', '한 번 허용', '허용 안함'만 나오는 거다.
"항상 허용 안 나오는데?"
클로드: "iOS 13부터는 첫 다이얼로그에 '항상 허용'이 안 나옵니다."
이런건 얘가 지 멋대로 말할 때가 있다. 그래, 내가 메시지를 잘못 던졌지.
- 음, 진짜야? 한 번 더 검색해봐.
Tavily MCP 사용해서 한 번 더 체크.
오... 진짜였다.
iOS 13부터 Apple이 개인정보 보호 명목으로 다이얼로그를 두 단계로 쪼개놨다.
1. 먼저 '앱 사용 중 허용'을 받고
2. 나중에 앱이 백그라운드에서 위치를 사용하는 권한요청을 해야 시스템이 두 번째 다이얼로그를 띄워서 '항상 허용으로 변경'을 묻는다.
// 1단계
manager.requestWhenInUseAuthorization()
// 사용자가 '앱 사용 중 허용' 선택 후, 별도 액션으로
// 2단계
manager.requestAlwaysAuthorization()지오펜싱 백그라운드 처리
앱이 종료되거나 백그라운드에 있을 때 처리 때문에 배터리가 많이 달지 않을까 생각했다.
일단,
지오펜싱은 시스템 데몬locationd)이 처리한다.
앱: CLCircularRegion 등록
→ locationd에 region 정보 전달
→ 앱은 끝. 더 이상 GPS 안 씀
locationd: GPS/Wi-Fi/Cell tower 조합으로 위치 추적
→ 등록된 region boundary 교차 감지
→ 앱 프로세스를 wake-up (terminated 상태면 relaunch)
→ didEnterRegion 콜백 호출그러니까 앱은 startMonitoring(for:) 한 번만 호출하고 손 떼면, 나머지는 다 시스템이 알아서 한다. 와, 이게 되네.
배터리는?
연속 GPS 추적startUpdatingLocation()은 배터리 잡아먹는 주범일 줄 알았다.
내가 Google Maps 타임라인 항상허용 켜봐서 아는데... 아니었다.
지오펜싱은 이벤트 드리븐 방식이라 거의 안 먹는다.
GPS를 상시 켜는 게 아니라 Wi-Fi/Cell/GPS를 조합해서 boundary 교차만 감지하는 거니까. 사용자가 boundary를 넘고 20초 정도 그 자리에 있어야 트리거되도록 해서 노이즈도 줄인다.
10m 반경처럼 극소 범위는 시스템이 정밀 GPS를 더 자주 켤 수 있어서 약간 더 먹긴 한다는데, 그래도 연속 추적 대비 무시할 수준이라고.
### 앱이 죽어있어도 알림이 온다고?
여기서 또 궁금증.
"앱이 메모리에서 완전히 해제되어도 알림이 와?"
답은 "온다, 단 조건이 있다."
locationd가 boundary 교차를 감지하면 iOS가 앱을 백그라운드에서 relaunch한다.
application(_:didFinishLaunchingWithOptions:)이 호출되고, launchOptions에 .location 키가 들어온다.
그러면서 위험경고를 하는데, 앱이 빠르게 CLLocationManager delegate를 연결해야 pending region event를 받을 수 있다는 거다.
오... 이런거 처음 받아봤어.
처음에 LocationManager를 ContentView의 @StateObject로 만들었는데, 클로드가 이건 위험하다고 했다.
SwiftUI View body가 평가되는 시점에 따라 delegate 연결이 늦어질 수 있어서.
그래서 App 레벨로 끌어올렸다.
라는걸 추천하길래 그대로 작업 지시.
@main
struct AlwaysGPSTestApp: App {
@StateObject private var locationManager = LocationManager()
var body: some Scene {
WindowGroup {
ContentView(locationManager: locationManager)
}
}
}점심에 순대국 먹으러 가서 알림 확인하고, 카페 들려서 알림 확인하고 왔다.
스크린샷은 찍었는데, 배경화면이 덕밍아웃이라 알림 스크린샷 첨부는 패스.

결론
아직 모바일 앱을 Native, Swift와 SwiftUI로 만드는 건 잘 못한다는 생각을 하고 있었는데,
오늘은 이상하게 잘 만들어줬다는 결론.
잡다한 설명이나 경고도 주고 말이야.
얘도 바이오리듬같은게 있나? 주말에 좀 쉬었니?