Background Data Fetch 하기

SensorKit 데이터를 수집할 때 background 환경에서도 데이터를 수집할 수 있도록 하기 위해 앱 내 백그라운드 작업이 가능하도록 하는 로직을 구현하였다.!

오늘은 백그라운드를 통해 작업을 수행할 수 있도록 하는 방식을 기록으로 남기려고 한다!

 

먼저 백그라운드 작업을 하기 위해 Signing & Capabilties에 백그라운드 Capability를 추가한 다음 사진에 보이는 두가지를 체크해야한다.

그 다음 infoPlist 내 백그라운드 작업이 진행 될 identifier를 넣어주어야 하는데

<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
    <string>com.yourapp.sensordatafetch</string>
</array>

이와 같이 본인 앱의 번들아이디를 넣어준 뒤 코드 내 기재해 둔 identifier를 넣어주면 된다.

이렇게 하면 프로젝트 내 세팅은 마무리하고 코드를 넣어주도록 하자!

 

먼저 이 프로젝트는 모듈화하여 각각 센서들이 다른 곳에 사용될 수 있도록 해야하기 때문에 동일한 로직들도 같이 구현하지 않고 각각의 Manager를 구현 하는 방식으로 진행되었기 때문에 이를 하나로 통합해줄 수 있는 SensorKitManager를 만들어주었다.

import Foundation
import BackgroundTasks
import SensorKit

class SensorKitManager {
    static let shared = SensorKitManager()
    
    func resumeAllSensors() {
        print("SensorKit 데이터 수집 재개")
        AmbientManager.shared.startRecording()
        KeyboardMetricsManager.shared.startRecording()
        CallLogManager.shared.startRecording()
        DeviceUsageManager.shared.startRecording()
        SpeechMetricsManager.shared.startRecording()
    }
    
    func startBackgroundFetch() {
        scheduleBackgroundTask()
    }
    
    func scheduleBackgroundTask() {
        let request = BGAppRefreshTaskRequest(identifier: "com.turingbio.vitaltracker.sensordatafetch")
        request.earliestBeginDate = Date(timeIntervalSinceNow: 60 * 15) // 15분 후 작업 예약
        
        do {
            try BGTaskScheduler.shared.submit(request)
            print("백그라운드 작업 예약 성공: \\(request.identifier)")
        } catch {
            print("백그라운드 작업 예약 실패: \\(error.localizedDescription)")
        }
    }
    
    func handleBackgroundTask(task: BGAppRefreshTask) {
        print("백그라운드에서 SensorKit 데이터 수집 시작...")
        
        task.expirationHandler = {
            print("백그라운드 작업 만료")
        }
        
        AmbientManager.shared.fetchAmbientLightData()
        KeyboardMetricsManager.shared.fetchKeyboardMetricsData()
        CallLogManager.shared.fetchCallLogData()
        SpeechMetricsManager.shared.fetchTelephonySpeechMetricsData()
        DeviceUsageManager.shared.fetchDeviceUsageData()
        
        // 백그라운드 작업이 끝나면 재등록
        scheduleBackgroundTask()
        
        print("백그라운드에서 SensorKit 데이터 수집 완료")
        task.setTaskCompleted(success: true)
    }
}

먼저 import를 통해 BackgroundTasks를 넣어준 뒤 백그라운드 작업을 예약할 수 있는 로직을 구현하였다.

func scheduleBackgroundTask() {
    let request = BGAppRefreshTaskRequest(identifier: "com.xxx.xxx.sensordatafetch")
    request.earliestBeginDate = Date(timeIntervalSinceNow: 60 * 15) // 15분 후 작업 예약
    
    do {
        try BGTaskScheduler.shared.submit(request)
        print("백그라운드 작업 예약 성공: \\(request.identifier)")
    } catch {
        print("백그라운드 작업 예약 실패: \\(error.localizedDescription)")
    }
}

BGAppRefreshTaskRequest 를 사용해 백그라운드 작업을 예약한다. 시스템에게 특정 시점 이후에 작업을 수행할 것을 요청할 수 있어 각 사용에 맞게 해당 값을 넣어주면 된다.

 

그 다음 earliestBeginDate를 설정해 언제 작업이 실행될 지 설정한다. 지금은 백그라운드로 이동 후 15분 뒤 작업이 실행되도록 구현하였다.

 

BGTaskScheduler.shared.submit(request) 이걸 활용해 작업 요청을 시스템에 제출하여 실제 백그라운드 작업이 예약되도록 구현하면 된다!

func handleBackgroundTask(task: BGAppRefreshTask) {
    print("백그라운드에서 SensorKit 데이터 수집 시작...")
    
    task.expirationHandler = {
        print("백그라운드 작업 만료")
    }
    
    // 각 SensorKit 데이터 매니저에서 데이터를 수집
    AmbientManager.shared.fetchAmbientLightData()
    KeyboardMetricsManager.shared.fetchKeyboardMetricsData()
    CallLogManager.shared.fetchCallLogData()
    SpeechMetricsManager.shared.fetchTelephonySpeechMetricsData()
    DeviceUsageManager.shared.fetchDeviceUsageData()
    
    // 작업 완료 후 다음 백그라운드 작업 예약
    scheduleBackgroundTask()
    
    print("백그라운드에서 SensorKit 데이터 수집 완료")
    task.setTaskCompleted(success: true)
}

그 다음 백그라운드에서 실행 될 작업을 넣어주는데 각 매니저 내 데이터를 페치하는 로직을 넣어주어 15분 단위로 해당 데이터가 페치될 수 있도록 구현하였다.

 

데이터가 페치된 뒤 scheduleBackgroundTask() 를 호출해 다시 15분 뒤에 데이터를 호출 할 수 있도록 예약 하여 주기적으로 데이터가 들어올 수 있도록 했다.

 

그리고 SensorKit의 특성 상 StartRecording 이후의 값을 수집할 수 있기 떄문에 데이터를 가져오는 로직만 구현해두면 가져올 데이터가 없어 정상적으로 실행된다고 하여도 아무런 데이터를 받을 수 없기 때문에 앱이 포그라운드에 처음 진입할 때 App 단에서 레코딩이 시작될 수 있도록 구현했다.

.onChange(of: scenePhase) { phase in
    switch phase {
    case .active:
        print("앱 포그라운드 들어옴")
        SensorKitManager.shared.resumeAllSensors()

이렇게 구현이 완료되면 백그라운드 이동 시 위에서 스케줄에 넣었던 로그가 원하는 시간이 지나면 이렇게 데이터 페치가 완료되게 된다!

백그라운드 작업에 대해서 조금 이해할 수 있는 과정이었던 것 같아 아주 뿌듯하다..

 

하지만 애플에서는 모든 작업에서 백그라운드 작업을 허용하는 것은 아니니 백그라운드 작업이 필요한 것들은 공식문서를 통해 그 내용을 잘 찾아보도록 하자!!

 

오늘은 여기까지 🙂