Location
Configuring your app to use location services
https://developer.apple.com/documentation/corelocation/configuring-your-app-to-use-location-services
거의 모든 Apple 기기에서 사용 가능한 location data 는 앱에서 유용하게 사용될 수 있음.
- 가능한 Use case 들
- 지도에 사람의 위치를 보여준다
- 식당 위치 데이터를 사용해서 근처에 없는 것들은 목록에서 제외시킨다
- 특정 기기/지역 근처에 있을 경우 알림을 발송한다 등
- Core Location 을 사용하기로 했을 때 고려사항
- location data 가 없는 경우
- 권한
Location data 는 민감 정보이기 때문에 수집한 정보를 안전하게 관리하는 것이 중요함. Disk 에 저장 / 네트워크 통신에 정보를 포함해서 보낼 때 location data 는 encrypt 해야 함. 또한 어떻게 location data 를 사용하는지 명확히 보여줘야 함
Availability Check
Location service 를 사용하기 전에 사용 가능 여부를 항상 검증하고 사용할 수 없는 경우 미리 location service 를 사용하는 기능을 비활성화하는 것이 서비스를 사용하고 에러를 handling 하는 것보다 좋음
- Location service 를 사용할 수 없을 수도 있는 상황
- 기기가 Airplane 모드일 때
- 기기가 필요한 하드웨어가 없는 경우
- 기기가 특정 서비스를 지원하지 않는 경우
- 앱이 서비스를 사용할 권한이 없는 경우
코드를 통해 서비스 사용 전 사용가능 여부를 확인하기
// 각 service 에 맞는 적절한 method 호출
// Check if heading data is available.
if CLLocationManager.headingAvailable() {
locationManager.startUpdatingHeading()
} else {
// Disable compass features.
}
Receive location changes & auth status changes
코드를 통해 location update 를 요청할 수 있는데 권한을 요청하지 않았던 경우 비동기 이벤트들을 받아서 코드가 실행되는 시점에 권한을 요청함. Location data 는 민감 정보이기 때문에 사용자가 권한을 제어하고, 어느 때든 설정 앱에서 조정 가능.
공식 문서에서는 위치 정보를 사용하기 시작하는 시점에 권한을 요청하는 것이 사용자가 왜 권한이 필요한지 더 잘 알게하기 때문에 앱 실행 맨 처음에 요청하는 것보다 좋다고 함
Location updates & Auth status changes : 비동기 이벤트
// Obtain an asynchronous stream of updates.
let stream = CLLocationUpdate.liveUpdates()
// return / break / throw 가 명시적으로 되지 않는 이상 중단되지 않음
// Iterate over the stream and handle incoming updates.
for try await update in stream {
// location update 여부와 권한을 모두 확인
if update.location != nil {
// Process the location.
} else if update.authorizationDenied {
// Process the authorization denied state change.
} else {
// Process other state changes.
}
}
앱이 항상 권한이 필요한 경우 CLServiceSession 에서 제공된 세션을 생성해서 가지고 있어야 함. CLServiceSession 을 사용해서 권한을 “While using” 에서 “Always” 로 업그레이드 할 수 있음
Device capability 정의
- Core Location 의 업데이트
- location : Wi-Fi, cellular, GPS 하드웨어 사용. 항상 모든 하드웨어를 사용하지 않기 때문에 정확도 레벨을 설정하면 그에 맞게 power 를 덜 쓰도록 필요한 하드웨어를 사용함
- compass : 자성 하드웨어 사용
하드웨어
특정 하드웨어가 꼭 필요한 경우 Info.plist 에 UIRequiredDeviceCapabilities 를 추가하면 App Store 가 특정 하드웨어가 없는 기기에 앱이 설치되는 것을 막아줄 수 있음
e.g. value : [“location-services”, “gps”, “magnetometer”]
gps: 가장 높은 정확도를 요구하는 경우 추가. (e.g. 내비 앱)magnetometer: heading 정보가 필요한 경우
Supporting live updates in SwiftUI and Mac Catalyst apps
iOS 17 이후 Core Location 은 async/await 를 지원함.
SwiftUI 에 lifecycle 이벤트 추가
https://developer.apple.com/documentation/corelocation/supporting-live-updates-in-swiftui-and-mac-catalyst-apps
- 필요한 것
CLLocationManager,CLBackgroundActivitySession인스턴스를 갖고 있는ObservableObject의 shared state 사용application(_:didFinishLaunchingWithOptions:)메서드를 제공하는AppDelegate객체. Background / 앱 재실행 시 백그라운드 작업을 재개하는 메서드.SwiftUI내AppDelegate객체 / Mac Catalyst 앱의@main구조
- Singleton 패턴의
ObservableObject
import SwiftUI
// Shared state that manages the `CLLocationManager` and `CLBackgroundActivitySession`.
@MainActor class LocationsHandler: ObservableObject {
static let shared = LocationsHandler() // Create a single, shared instance of the object.
private let manager: CLLocationManager
private var background: CLBackgroundActivitySession?
@Published var lastLocation = CLLocation()
@Published var isStationary = false
@Published var count = 0
@Published
var updatesStarted: Bool = UserDefaults.standard.bool(forKey: "liveUpdatesStarted") {
didSet { UserDefaults.standard.set(updatesStarted, forKey: "liveUpdatesStarted") }
}
@Published
var backgroundActivity: Bool = UserDefaults.standard.bool(forKey: "BGActivitySessionStarted") {
didSet {
backgroundActivity ? self.background = CLBackgroundActivitySession() : self.background?.invalidate()
UserDefaults.standard.set(backgroundActivity, forKey: "BGActivitySessionStarted")
}
}
private init() {
self.manager = CLLocationManager() // Creating a location manager instance is safe to call here in `MainActor`.
}
func startLocationUpdates() {
if self.manager.authorizationStatus == .notDetermined {
self.manager.requestWhenInUseAuthorization()
}
self.logger.info("Starting location updates")
// Background 일 때도 정상 동작
Task() {
do {
self.updatesStarted = true
let updates = CLLocationUpdate.liveUpdates()
for try await update in updates {
if !self.updatesStarted { break } // End location updates by breaking out of the loop.
if let loc = update.location {
self.lastLocation = loc
self.isStationary = update.isStationary
self.count += 1
print("Location \(self.count): \(self.lastLocation)")
}
}
} catch {
print("Could not start location updates")
}
return
}
}
func stopLocationUpdates() {
print("Stopping location updates")
self.updatesStarted = false
}
}
AppDelegate인스턴스 생성
import Foundation
import UIKit
class AppDelegate: NSObject, UIApplicationDelegate, ObservableObject {
func application(_ application: UIApplication, didFinishLaunchingWithOptions
launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
let locationsHandler = LocationsHandler.shared
// If location updates were previously active, restart them after the background launch.
if locationsHandler.updatesStarted {
locationsHandler.startLocationUpdates()
}
// If a background activity session was previously active, reinstantiate it after the background launch.
if locationsHandler.backgroundActivity {
locationsHandler.backgroundActivity = true
}
return true
}
}
@main구조에 AppDelegate 주입
@main
struct MyApp: App {
@UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Getting the current location of a device
https://developer.apple.com/documentation/corelocation/getting-the-current-location-of-a-device
Core Location 은 location 관련 데이터를 위한 많은 서비스를 제공함.
- 현 위치 데이터를 사용한 use case
- 내비게이션
- 근처 POI 찾기
- 지도 위치 표시
- 친구와 공유
- 사진 location 태깅 등
현 위치는 Wi-Fi radio, celluar radio, GPS radio(무선 통신 전파 송수신 하드웨어) 들 사용해서 파악 가능. 전부 다 사용하는 것은 아니고 가능한 가장 power-효율적인 방법으로 선택적으로 사용함.
CLLocationManager 를 어떻게 사용하느냐에 따라 앱의 power 소비량에 영향을 주는 radio 를 선택함
필요한 location data 를 제공하는 service 사용하기
앱의 요구사항을 충족할 수 있는 가장 power-efficient 한 location service 를 사용
- Core Location 이 제공하는 service
- Visits : location data 를 얻는 가장 power-efficient 한 방법. 시스템은 사람이 방문한 장소와 그 장소에서 보낸 시간을 모니터링하고 나중에 그 데이터를 전달함.
startMonitoringVisits()를 통해 시작 - Significant-change : location update 를 받기 위한 저전력 service. Cellular, Wi-Fi (GPS X) 를 사용해서 차이가 날 만큼 큰 거리를 이동한 경우에만 location 변화를 알림.
startMonitoringSignificantLocationChanges()를 통해 시작 - Standard : 가장 정확하고 일반적인 location data 를 제공, 다른 서비스에 비해 더 많은 전력 사용. 내비나 정확도가 필요한 경우 사용.
startUpdatingLocation()를 통해 시작, 하나의 location event 를 받으려면requestLocation()사용.
- Visits : location data 를 얻는 가장 power-efficient 한 방법. 시스템은 사람이 방문한 장소와 그 장소에서 보낸 시간을 모니터링하고 나중에 그 데이터를 전달함.
가능한 한 location service 의 시작 시점을 늦추기. e.g. 앱을 사용하기 시작할 때 / location data 가 정말 필요한 시점
location data 가 더 이상 필요 없다면 서비스를 중단해서 배터리 사용량을 낮추기.
서비스를 구현하면 관련 delegate 를 메서드를 구현함. ‘Standard’, ‘Significant-change’ service 는 같은 delegate 메서드를 사용하지만 ‘Visits’ 는 다른 메서드를 사용함.
Power-saving features
전력 사용량을 줄이기 위한 최고의 최적화는 location data 가 필요하지 않은 경우 location service 를 끄는 것. 다른 최적화는 location manager 객체를 설정하는 것
- location manager 객체 configuration
distanceFilter: 가능한 큰 값 설정. 클수록 시스템이 radio 하드웨어를 더 자주 끌 수 있게 해줌desiredAccuracy: 가능한 낮은 값 설정. 낮을수록 시스템이 더 전력을 적게 사용하는 하드웨어를 사용할 수 있게 해주며 시스템이 하드웨어 자체를 금방 끌 수 있게 함activityType: 적절한 값을 설정하고pausesLocationUpdatesAutomatically를 true 를 설정함. Core Location 이 이 값을 사용해서 조건에 맞으면 하드웨어를 자동으로 끌 수 있게 해줌.allowsBackgroundLocationUpdates: background location update 가 필요하지 않은 경우 false 로 설정
- Info.plist
NSLocationDefaultAccuracyReduced: true 로 설정. 낮은 정확도로도 충분한 경우 사용.
Handling location updates in the background
https://developer.apple.com/documentation/corelocation/handling-location-updates-in-the-background
일부 애플 기기에서 os 는 백그라운드 앱을 suspend 시켜서 배터리를 절약함. iOS, iPadOS, watch OS 에서 시스템은 대부분의 앱이 background 에 가면 실행을 중지시킴.
Suspended 상태에서 앱은 실행되지 않고 시스템에서 location update 를 받지 않음. 대신 시스템은 location udpate 를 queue 에 담아두고 앱이 다시 실행되는 시점 (foreground / background) 에 전달함.
실시간성이 중요하다면 location service 가 active 할 때 시스템에 앱을 suspend 하지 않게 할 수 있음.
- 실시간 background 업데이트가 필요한 use case
- hike / 운동 중 정확한 경로를 추적
- 실시간 내비
- 시간에 민감한 알림 / 업데이트 생성
- 특정 지역에 진입 / 진출할 때 즉각적으로 해야하는 작업이 있는 경우
Background location update 가 필요한 경우 프로젝트 업데이트가 필요. macOS 는 앱을 background 에서 suspend 시키지 않기 때문에 필요 없음.
Background mode capability
Signing & Capabilities 에서 Location updates option 선택.
https://docs-assets.developer.apple.com/published/c0f8aa3e82bfe5772ba30b60f500dfe6/media-4061646%402x.png
Background 에서 location update 받기
CLBackgroundActivitySession인스턴스 생성 : background activity session 을 시작하고 location update 를 받을 수 있게 함CLServiceSession생성 : whenInUse / always 권한 얻고 앱이 foreground 에 있을 때 세션 생성. 앱이 terminate 되면 background 에서 즉시 다시 만들어야 함
Core Location 은 CLMonitor, CLLocationUpdate, CLBackgroundActivitySession 에서 이벤트를 받을 경우 내부적으로 When in Use 권한을 설정함.
Always 권한을 사용할 때는 사용자에게 명시적으로 background 에서 location update 가 일어남을 알려야 함
앱 실행 후 location updates
시스템은 메모리 확보를 위해 어느 때든 앱을 종료할 수 있기 때문에 앱이 종료되면, 앱이 재실행 되고 나서 update 를 받기 위해 API 를 다시 실행해야 함.
Monitoring the user’s proximity to geographic regions
https://developer.apple.com/documentation/corelocation/monitoring-the-user-s-proximity-to-geographic-regions
Condition monitoring (geofencing) 은 특정 지역에 사용자가 진입 / 진출할 경우 앱이 알 수 있게 하는 방법
iOS 에서 시스템은 지역을 모니터링해서 조건이 satisfied <-> unsatisfied 상태 사이에서 변할 때 앱을 깨움.
Geographic condition 정의 및 모니터링
- 좌표 기준 원형 지역 정의 :
CLCircularGeographicCondition사용- 반지름을 통해 경계를 정의
- 지역 모니터링 조건 설정 및 등록 :
CLLocationManager의startMonitoring(for:)메서드 호출
시스템은 명시적으로 조건을 중지하거나 기기가 재부팅하지 않는 이상 조건을 모니터링 함
Task {
// Create a custom monitor.
let monitor = await CLMonitor("my_custom_monitor")
// Register the condition for 200 meters.
let center = myFirstLocation;
let condition = CLCircularGeographicCondition(center: center1, radius: 200)
// Add the condition to the monitor.
monitor.add(condition, identifier: "stay_within_200_meters")
// Start monitoring.
for try await event in monitor.events {
// Respond to events.
if event.state == .satisfied {
// Process the 200 meter condition.
}
}
}
Condition 은 특정 하드웨어 capability 에 의존하는 공유 resource 임. 모든 앱이 condition monitoring 을 할 수 있게 보장하기 위해 Core Location 은 한 앱에서 최대 20개의 condition 을 동시에 모니터링 할 수 있게 함.
Condition 이 statisfied 상태가 됐을 때 iOS 앱이 실행중이지 않은 상태면 시스템이 실행하려고 함. 앱이 재실행하면 같은 identifier 로 monitor 를 생성해야 함. monitoring 은 기기 reboot 후 사용자가 잠금해제 한 이후에만 가능함.
Respond to events
Condition 의 상태가 변할때마다 monitor 의 AsyncSequence 를 통해 이벤트를 제공함.
Conidition 이 만족됐을 때 iOS 앱이 실행중이지 않으면 시스템이 실행하려고 함. 앱이 재실행하면 같은 identifier 로 monitor 를 생성해야 함.
Creating a location push service extension
- 고찰
위치 권한
Location 관련 정보를 받기 전에 권한이 필수.
위치 권한을 얻기 위해서는 CLLocationManager 의 권한 관련 메서드를 실행해야 함
CLLocationManager의 권한 요청 메서드requestWhenInUseAuthorization(): 앱이 “in use” 인 경우 위치 서비스를 사용할 권한 요청requestAlwaysAuthorization(): 앱의 상태와 무관하게 위치 서비스를 사용할 권한 요청
Info.plist 에 추가 없이 메서드만 호출할 경우 아무런 프롬프트도 뜨지 않음
Info.plist 에 추가까지 해야 위 메서드를 호출하는 시점에 권한 요청
| In Use | Always | |
|---|---|---|
| Info.plist key | NSLocationWhenInUseUsageDescription |
NSLocationWhenInUseUsageDescription, NSLocationAlwaysAndWhenInUseUsageDescription 둘 다 필요 |
| 권한 획득 방법 | requestWhenInUseAuthorization() 메서드 호출 |
In Use 영구 권한 먼저 획득 후 requestAlwaysAuthorization() 메서드 호출 |
In Use
권한 요청 시 사용자가 선택할 수 있는 옵션
| Option | Authorization |
|---|---|
| Allow While Using App | In Use auth 가 만료되지 않음 |
| Allow Once | 앱이 더 이상 in use 가 아닐 때 In Use 권한이 만료됨 -> 앱 terminated 되고 재실행 될 경우 다시 요청해야 함 |
| Don’t Allow | 권한 획득 거절, 앞으로의 요청은 허용 안됨 |
권한이 .notDetermined 상태일 때만 권한을 요청함.
만약 temporal 권한을 부여한다면 그 권한이 만료될 경우 권한은 .notDetermined 상태로 돌아가게 됨
만약 foregorund 일 때 location service 를 시작했다면, 앱이 background location updates (프로젝트의 Capability tab 에서 확인 가능) 가 활성화된 경우에 background 에서 계속 실행됨. 시스템은 앱이 background 로 전환됐을 때 상태 바에 location services indicator 를 띄움.
Always
권한이 .notDetermined 이거나 .authorizedWhenInUse 상태일 때만 권한을 요청함.
Core Location 은 requestAlwaysAuthroization() 메서드 호출 횟수를 제한해서 한 번 호출한 경우 이후 호출은 효력이 없음.
In Use 권한 획득 이후 요청하는 경우
Prompt 에는 NSLocationAlwaysUsageDescription 에 설정했던 문구를 보여줌
사용자가 선택할 수 있는 옵션
| Option | Authorization |
|---|---|
| Keep Only While Using | In Use 권한 그대로 놔둠 |
| Change to Always Allow | Always 권한을 보장함 |
바로 Always 권한을 요청하는 경우
권한이 .notDetermined 상태인 경우
두 개의 prompt 를 보여줌.
- 첫 번째 prompt
NSLocationWhenInUseUsageDescription 에 설정했던 문구를 보여줌
사용자가 선택할 수 있는 옵션
| Option | Authorization |
|---|---|
| Allow While Using App | Always 권한 부여 |
| Allow Once | 임시 When In Use 권한 부여 |
| Don’t Allow | 권한 거절 |
- 두 번째 prompt
만약 앱이 임시 always 권한을 부여받은 경우 필요한 이벤트를 전달하려고 할 때 노출됨
사용자가 선택할 수 있는 옵션
| Option | Authorization |
|---|---|
| Keep Only While Using | 권한을 In Use 권한으로 바꿈 |
| Change to Always Allow | 임시 권한을 영구 Always 권한으로 바꿈 |
Choosing the Location Services Authorization to Request
https://developer.apple.com/documentation/BundleResources/choosing-the-location-services-authorization-to-request#Request-Authorization-for-Apps-Running-in-iOS-12-and-Earlier
Location 이벤트를 받을 때 앱의 authorization 상태가 결정됨
- 권한 종류
- When In Use : 앱이 in use 상태 일 때 모든 location service 를 사용하고 이벤트를 받을 수 있음.
- Always : 앱이 실행되지 않을때도 모든 location service 를 사용하고 이벤트를 받을 수 있음. 앱이 실행중이지 않은 상태면 시스템이 앱을 실행하고 이벤트를 전달함
In use 상태 : iOS 앱은 foreground 에 있을 때 / background location usage indicator 가 켜져 있는 상태에서 background에서 실행될 때 in use 로 간주됨
When In Use Authorization 권한 우선시하기
가능한 한 When In Use 권한을 요청.
- When In Use 권한으로 가능한 기능들
- 앱이 사용중일 때 모든 location service 를 사용할 수 있음. 사용자가 앱 사용을 중지하면 다시 앱을 사용할 때까지 진행중인 request 는 중지됨
- Background location updates 를 활성화해서 앱이 background 에 진입했을 때도 location update 를 받을 수 있음
- Location notification trigger
UNLocationNotificationTrigger를 사용해서 사용자가 특정 지역에 진입했을 때 알릴 수 있음. 알림을 선택하면 시스템이 앱을 실행해서 location 이벤트를 받을 수 있게 함.
- 사용중인 상태 (In use) 로 간주되는 케이스
- 앱이 foreground 에서 실행될 때
- 앱이 foreground 에서 벗어난 후 몇 초 짧은 시간 동안 사용자가 실행한 현재 location 작업을 마무리함
- 앱이 background location usage indicator 를 보여줄 때 (
showsBackgroundLocationIndicator)
Alwyas 권한
사용자가 location 정보가 필요한 순간마다 앱을 사용할 수 없거나 사용하고 싶지 않은 상황에서 Always 권한을 요청할 수 있음.
- Always 권한을 요청할 수 있는 케이스
- Prompt 를 노출하는게 불편하고 원하지 않는 상황에서 앱이 자동화된 작업을 수행하는 경우. e.g. 사용자가 특정 지역에 진입했을 때 자동으로 특정 작업 (불을 키거나 하는 등) 을 수행함
- 하루에 방문한 장소들을 기록함. Always 권한을 통해 앱이 사용중이지 않은 경우에도 장소를 기록할 수 있고, 사용자에게 prompt 를 보여주지 않을 수 있음 e.g. 일기앱
공유용
해외 지역 기반 알림
- 검토 기능 : 특정 시간대에 알림이 노출되지 않도록 하는 기능
⚠️ AS-IS : 지역에 진입한 경우 발동되는 UNLocationNotificationTrigger 를 사용해 알림 등록
⚠️ Notification Service / Notification Content extension 을 사용해서 이미 등록된 local notification 을 intercept 할 수 있는 방법은 없음
- UNNotificationServiceExtension : 오직 push (remote) notification 에만 적용됨
- UNNotificationContentExtension : 사용자가 알림을 확장 한 이후의 UI 를 커스터마이징 할 수 있는 옵션
⚠️ AS-IS : 지역에 진입한 경우 발동되는 UNLocationNotificationTrigger 를 사용해 알림 등록
✅ TO-BE : Geofencing 사용. 특정 지역에 진입했을 때를 감지해 즉시 알림 발송
- 기획과 확인해야 하는 포인트
- Always 권한 추가가 가능한지
- 최대 20개까지의 지역까지 모니터링 가능한 제약 사항
- 전력 사용량 - 정확도를 낮춰도 되는지
Geofencing (Condition monitoring)
사용자가 특정 지역에 진입 / 진출할 때 사용자가 알 수 있도록 하는 방법
- 요구 사항
- 하드웨어 : 위치 정보 관련 radio 탑재. 대부분의 iphone 은 조건 충족
- 소프트웨어 (현재 T 프로젝트 기준)
- iOS 10 + : ✅
- Always 권한 : ❌
- Background Modes : Location updates capability : ✅
- 제약 사항
- 한 앱당 20 개의 지역까지 모니터링 할 수 있음
- 앱 전원이 꺼지면 모니터링 등록한 내용 날라감
- 사용자가 위치 권한 거절하면 location 서비스 자체 이용 불가
권한
- 종류
- When In Use (하위) : 앱이 in use 상태 일 때 모든 location service 를 사용하고 이벤트를 받을 수 있음.
- Always (상위) : 앱이 in use 상태가 아닐때도 모든 location service 를 사용하고 이벤트를 받을 수 있음. 앱이 실행중이지 않은 상태면 시스템이 앱을 실행하고 이벤트를 전달함
In use 상태?
- foreground 상태
- background location usage indicator 가 켜져 있는 상태에서 background에서 실행될 때
배터리 사용량
정확한 데이터를 얻기 위해 더 배터리 소모량이 많은 하드웨어를 사용하게 됨.
Geofencing 자체는 power-efficient.
startMonitoring()은 GPS radio 를 항상 켜 놓고 있지 않음.- ‘Wake up’ 전략 : 시스템은 모니터링 하는 특정 지역 근처에 가까워 질 때 앱을 키므로 대부분의 상황에서는 배터리를 사용하지 않음
배터리 사용량을 줄이기 위한 최적화
높은 정확도가 필요하지 않은 경우 아래의 옵션들을 사용해 배터리 사용량을 최대한 적게 유지하도록 설정 가능. 괌 여행과 같은 큰 단위 지역을 가정하면 높은 정확도는 필요 없을 것으로 예상
CLLocationManager 객체 configuration 설정 및 info.plist 설정.
- location manager 객체 configuration
distanceFilter: 가능한 큰 값 설정. 클수록 시스템이 radio 하드웨어를 더 자주 끌 수 있게 해줌desiredAccuracy: 가능한 낮은 값 설정. 낮을수록 시스템이 더 전력을 적게 사용하는 하드웨어를 사용할 수 있게 해주며 시스템이 하드웨어 자체를 금방 끌 수 있게 함activityType: 적절한 값을 설정하고pausesLocationUpdatesAutomatically를 true 를 설정함. Core Location 이 이 값을 사용해서 조건에 맞으면 하드웨어를 자동으로 끌 수 있게 해줌.
- Info.plist
NSLocationDefaultAccuracyReduced: true 로 설정. 낮은 정확도로도 충분한 경우 사용.
로직 복잡도
사용자가 전원 끄고 나서도 유지되는지
Meet Core Location Monitor
Monitor overview
- CLMonitor : top level Swift actor, monitoring 의 gateway 로 동작함
- actor : thread, task synchronization 의 부담을 덜어줌. CLMonitor 의 컨텐츠 접근, condition 추가/삭제는 await 되어야 함
Monitor 생성
// 해당 이름의 monitor 가 없는 경우 새로운 monitor 생성됨
// 해당 이름의 monitor 가 있는 경우 기존 monitor instance 가 리턴됨
let self.monitor = await CLMonitor("Greeter")
- string 함께 initializer 호출
- 특정 순간에 주어진 string 으로 오직 하나의 monitor 만 생성될 수 있음
Condition
관찰되는 entity : condition
- condition 추가 : identifier 와 함께 add 메서드 호출
- record 객체는 제거될 때까지 identifier 로 record 객체와 content 에 접근 가능함
- Record 객체 제거 : identifier 와 함께 remove 메서드 호출
- condition 을 제거하면 일치하는 record도 삭제됨
Supported Conidtions
CircularGeographicCondition
center, radius 로 정의된 원형 지역 조건
- satisfied : radius 안에 있는 경우
- unsatisfied : radius 밖에 있는 경우
// create monitor
let monitor = await CLMonitor("Greeter")
// Circular geographic condition
let center = CLLocationCoordinate2D(latitude: 37.33, longitude: -122)
let appleParkCondition = CLMonitor.CLCircularGeographicConidtion(center: center, radius: 100.0)
BeaconIdentityCondition
만약 회사가 여러 지역에 여러 지점이 있다면 beacon을 배포해서 사용자가 특정 지점에 있는지, 특정 지점의 특정 구역에 있는지 확인할 수 있음.
Beacon
-
전통적 의미의 beacon : 어떤 신호를 알리기 위해 주기적으로 신호를 전송하는 기기*
-
Beacon 의 구성 요소
- UUID
- Major
- Minor
BeaconIdentityCondition 을 사용해서 위 세가지 요소로 특정 beacon 을 모니터링 할 수 있음. Wildcard 를 사용해서도 매칭 가능
e.g. Apple park 의 여러 cafeteria site 가 존재. 모든 cafeteria site 에 같은 UUID 를 갖는 beacon 을 배포할 수 있음
사용자가 beacon 중 하나 근처에 있다면 조건이 충족됨
// UUID 는 cafeteria 에 배포된 beacon 들이 같은 UUID 를 사용하고 있을 것
// Monitor for all cafeterias
let allCafeteriasConidtion = CLMonitor.BeaconIdentityCondition(uuid: uuid)
// 특정 site 근처에 있는지 판별 가능
// 한 site 내 beacon 들은 major 넘버를 공유함
// major number 로 site 판별
let appleParkCafeteria = CLMonitor.BeaconIdentityCondition(uuid: uuid, major: 6)
// 특정 site 내 특정 section 을 판별 가능
let appleParkDesserts = CLMonitor.BeaconIdentityCondition(uuid: uuid, major: 6, minor: 5)
let appleParkIndian = CLMonitor.BeaconIdentityCondition(uuid: uuid, major: 6, minor: 27)
Condition Monitoring
let appleParkCondition = CLMonitor.CircularGeographicCondition(center: center, radius: 100.0)
// Add conidtion for monitoring
await self.monitor.add(appleParkCondition, identifier: "appleParkCondition")
// 초기 상태를 미리 알고 있는 경우
await self.monitor.add(appleParkCondition, identifier: "appleParkCondition", assuming: .unsatisfied)
// Remove Condition for monitoring
await self.monitor.remove("appleParkCondition")
- condition 추가
- string idneitifer 와 함께 add 메서드를 호출해서 condition monitoring
- 이미 주어진 identifier 로 monitoring 되는 조건이 있다면 새로운 condition 으로 대체됨
- condition 을 추가하면 초기 상태는 Core Location 에 의해 결정되기 전까지 unknown 상태임
- 추가하기 전에 condition 의 상태를 미리 알고 있는 경우, assuming 을 통해 초기 상태를 지정할 수도 있음. 가정이 틀릴 경우 Core Location 이 올바른 상태로 바꿔놓음
- condition 삭제
- condition 을 추가할 때 사용한 identifier 로 remove 메서드 호출
Inspecting Records
- Monitoring 할 condition 을 추가하면 Core Location 은 record 를 생성하고 그 record 에 condition 을 추가함
-
condition 과 함께 record 는 event 라는 객체를 포함함
- event 구성 요소
- state : 현재 관찰된 condiition 의 상태. (satisfied, unsatisfied, unknown)
- date : condition 이 상태에 진입한 날짜와 시간
- condition : refinement
refinement? : 시스템이 내가 요청했던 구체적인 정보를 주는 것.
e.g. UUID 로만 제약사항을 추가함. UUID ‘A’ 인 beacon 은 다 탐지해줘. 실제로 탐지된 beacon 은 UUID ‘A’, Major 10, Minor 5 의 정보를 갖고 있음. 내가 물어본 건 UUID ‘A’ 이지만 refinement 에서는 Major, Minor 정보까지 다 주는 것
condition 이 추가됐을 때 사용된 identifier 로 고유하게 구분되는 여러 record instance 들이 생성될 수 있음. 모든 record 들은 앱에 저장돼서 특정 condition 과 해당하는 state 를 질의할 수 있음
// Get record
let monitoredRecord = await self.monitor.record(for: "appleParkCondition")
// Monitored condition
let condition = monitoredRecord?.condition
// Get last event
let lastEvent = monitoredRecord?.lastEvent
// Get last state
let lastState = lastEvent?.state
Monitoring 되는 identifier 들 목록을 갖고 있기 때문에 각 record 와 컨텐츠를 불러오기 쉬움
// Get all monitored identifiers
for identifier in await self.monitor.identifiers {
// get record
if let monitoredRecord = await self.monitor.record(for: identifier) {
// do something with the record
}
}
Handling Events
변화가 생겼을 때 이벤트 다루기
// Receiving events
Task {
for try await event in self.monitor.events {
switch(event.state) {
case .satisfied:
print("\(event.identifier) is satisfied")
case .unsatisfied:
print("\(event.identifier) is unsatisfied")
case .unknown:
print("\(event.identifier) is unknown")
}
}
}
Leave a comment