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

  • 필요한 것
    1. CLLocationManager, CLBackgroundActivitySession 인스턴스를 갖고 있는 ObservableObject 의 shared state 사용
    2. application(_:didFinishLaunchingWithOptions:) 메서드를 제공하는 AppDelegate 객체. Background / 앱 재실행 시 백그라운드 작업을 재개하는 메서드.
    3. SwiftUIAppDelegate 객체 / Mac Catalyst 앱의 @main 구조
  1. 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
    } 
}
  1. 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
    }
}
  1. @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() 사용.

가능한 한 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 사용
    • 반지름을 통해 경계를 정의
  • 지역 모니터링 조건 설정 및 등록 : CLLocationManagerstartMonitoring(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 를 보여줌.

  1. 첫 번째 prompt

NSLocationWhenInUseUsageDescription 에 설정했던 문구를 보여줌

사용자가 선택할 수 있는 옵션

Option Authorization
Allow While Using App Always 권한 부여
Allow Once 임시 When In Use 권한 부여
Don’t Allow 권한 거절
  1. 두 번째 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

Image

관찰되는 entity : condition

  • condition 추가 : identifier 와 함께 add 메서드 호출
    • record 객체는 제거될 때까지 identifier 로 record 객체와 content 에 접근 가능함
  • Record 객체 제거 : identifier 와 함께 remove 메서드 호출
    • condition 을 제거하면 일치하는 record도 삭제됨

Supported Conidtions

CircularGeographicCondition

Image

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

Image

만약 회사가 여러 지역에 여러 지점이 있다면 beacon을 배포해서 사용자가 특정 지점에 있는지, 특정 지점의 특정 구역에 있는지 확인할 수 있음.

Beacon

  • 전통적 의미의 beacon : 어떤 신호를 알리기 위해 주기적으로 신호를 전송하는 기기*

  • Beacon 의 구성 요소

    • UUID
    • Major
    • Minor

BeaconIdentityCondition 을 사용해서 위 세가지 요소로 특정 beacon 을 모니터링 할 수 있음. Wildcard 를 사용해서도 매칭 가능

e.g. Apple park 의 여러 cafeteria site 가 존재. 모든 cafeteria site 에 같은 UUID 를 갖는 beacon 을 배포할 수 있음

Image

사용자가 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

Image

  • 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 정보까지 다 주는 것

Image

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")
    }
  }
}

Best practices

Categories:

Updated:

Leave a comment