SensorKit Data Fetch 하는 법

 

드디어 SensorKit에 기록된 데이터를 페치할 수 있게 되면서 쌓여있던 응어리가 내려가는 기분이 들었다.. 너무 개운하고 재밌었다.. 이 맛에 계속 개발을 하는 건가 싶을 정도로 데이터가 들어오는 순간에 얼마나 기쁘던지..

 

SensorKit을 구현하면서 레퍼런스 할 자료가 많이 부족했기 때문에 맨땅에 헤딩하면서 많이 배운 것 같다! 공식문서를 읽는 법도 좀 배웠고 헤맨 만큼 내땅이라는 말이 뭔지 조금 알게 되는 순간이었다.

 

오늘은 어떤 로직을 구성해서 데이터를 가져오는지 기록으로 남겨두려고 한다.

 

다양한 데이터를 가져와서 페치하고 있지만 예시를 활용하기 위해 디바이스 정보를 가져오는 로직을 구현해보도록 하자

 

https://developer.apple.com/documentation/sensorkit/srsensor/3377674-deviceusagereport

먼저 디바이스의 데이터를 가져오기 위해선 sensor 중 deviceUsageReport를 사용해야한다.

private let deviceUsageReader = SRSensorReader(sensor: .deviceUsageReport)

deviceUsageReader의 인스턴스를 생생해 디바이스 정보를 가져올 수 있도록 기능을 부여해 준다.

 

그 다음 deviceUsageReport에는 앱 사용 정보, 디바이스 사용량, 앱 알럿 정보 등 다양한 정보를 각각 가져올 수 있기 때문에 해당 하는 데이터 모델을 만들어 준다

@Published var appUsageData: [AppUsageDataPoint] = []
@Published var deviceUsageSummary: [DeviceUsageSummary] = []
@Published var notificationUsageData: [NotificationUsageDataPoint] = []

그 다음 어떤 디바이스에서 데이터를 가져와야할 지 설정해주고 그 디바이스에서 데이터를 수집해야하기 때문에 SRDevice로 현재 연결된 디바이스를 파악해야한다.

var availableDevices: [SRDevice] = []

그 다음은 간단하면서 가장 중요한 권한 설정을 할 수 있도록 올바르게 설정해 줘야한다. SensorKit은 연구목적으로 사용 가능하며 애플에 별도 권한을 부여받아야만 사용할 수 있기때문에 해당 권한을 가지고 있는 entitlement를 올바르게 넣고 infoplist에 권한 설정에 대한 내용을 잘 넣어주어야 한다.

 

이 부분은 넘어가고 권한 설정에 대해서 알아보자

private func checkAuthorizationStatus() {
    switch deviceUsageReader.authorizationStatus {
    case .authorized:
        print("디바이스 사용 리포트 접근 허용")
        startRecording()
    case .notDetermined:
        print("권한 미결정")
        requestAuthorization()
    case .denied:
        print("권한 거부")
    @unknown default:
        print("알 수 없음")
    }
}

권한 설정에 대한 내용을 넣어주고 권한이 있다면 수집을 시작, 없다면 권한을 요청하는 메서드를 실행할 수 있도록 한다.

private func requestAuthorization() {
    SRSensorReader.requestAuthorization(sensors: [.deviceUsageReport]) { error in
        if let error = error {
            print("디바이스 사용 리포트 권한 요청 실패: \\(error.localizedDescription)")
        } else {
            print("권한 설정 완료")
        }
    }
}

이렇게 특정 센서의 권한을 요청하도록 넣어주면 이용자에게 권한을 승인할 것 인지에 대한 안내를 할 수 있다.

private func startRecording() {
    print("Device Usage Recording")
    deviceUsageReader.startRecording()
    fetchAvailableDevices()
}

private func fetchAvailableDevices() {
    deviceUsageReader.fetchDevices()
}

func fetchDeviceUsageData() {
    guard !availableDevices.isEmpty else {
        print("디바이스 없음")
        return
    }
    
    let request = SRFetchRequest()
    
    let now = CFAbsoluteTimeGetCurrent()
    let fromTime = now - (7 * 24 * 60 * 60)
    let toTime = now
    
    request.from = SRAbsoluteTime.fromCFAbsoluteTime(_cf: fromTime)
    request.to = SRAbsoluteTime.fromCFAbsoluteTime(_cf: toTime)
    
    for device in availableDevices {
        request.device = device
        print("\\(device.name)에서 디바이스 사용 데이터를 가져옵니다.")
        deviceUsageReader.fetch(request)
    }
}

그리고 권한 설정이 완료된다면 해당 데이터를 startRecording() 할 수 있도록 넣어주고 현재 연결되어있는 디바이스의 값도 가져올 수 있도록 설정한다.

 

SensorKit의 데이터는 수집이 시작된지 24시간이 지나고 최대 7일까지의 데이터만 확인할 수 있으므로 최대 7일의 데이터만 가져올 수 있도록 구현한다.

private func processAppUsageData(sample: SRDeviceUsageReport, timestamp: Date) {
    for (category, apps) in sample.applicationUsageByCategory {
        for app in apps {
            let appName = app.bundleIdentifier ?? "Unknown App"
            let usageDuration = app.usageTime
            let notificationCount = sample.notificationUsageByCategory[category]?.count ?? 0
            let webUsageDuration = sample.webUsageByCategory[category]?.reduce(0) { $0 + $1.totalUsageTime } ?? 0
            
            let dataPoint = AppUsageDataPoint(
                timestamp: timestamp,
                appName: appName,
                usageDuration: usageDuration,
                category: category.rawValue,
                notificationCount: notificationCount,
                webUsageDuration: webUsageDuration
            )
            
            DispatchQueue.main.async {
                self.appUsageData.append(dataPoint)
                print("앱 사용 데이터 추가됨: \\(dataPoint)")
            }
        }
    }
}

그 다음 앱의 정보를 가져올 수 있도록 구현한다. 카테고리의 경우 딕셔너리 타입으로 앱의 카테고리와 앱을 각각 가져와야 데이터가 올바르게 페치될 수 있으므로 공식문서를 잘 확인해 타입을 맞춰 가져오도록 하자.

 

여기까지 작성한다면 데이터 페치하는 것에 대한 로직은 완성되지만 빠진게 하나 있다.

 

실질적으로 데이터를 가져와서 처리하는 delegate 메서드 구현이 남아있기 때문!

 

여기서 중요하게 사용되는 델리게이트 메서드는 3가지 정도가 있다.

첫번째로

func sensorReader(_ reader: SRSensorReader, didFetch devices: [SRDevice]) {
    self.availableDevices = devices
    for device in devices {
        print("사용 가능한 디바이스: \\(device.name)")
    }
    
    if !devices.isEmpty {
        fetchDeviceUsageData()
    } else {
        print("사용 가능한 디바이스가 없습니다.")
    }
}

현재 어떤 디바이스가 연결되어 있는지 확인해주는 didFetch 메서드

 

두번째로

func sensorReader(_ reader: SRSensorReader, fetching fetchRequest: SRFetchRequest, didFetchResult result: SRFetchResult<AnyObject>) -> Bool {
    let absoluteTime = result.timestamp.toCFAbsoluteTime()
    let timestamp = Date(timeIntervalSinceReferenceDate: absoluteTime)
    
    if let deviceUsageSample = result.sample as? SRDeviceUsageReport {
        processDeviceUsageSummary(sample: deviceUsageSample, timestamp: timestamp)
        processAppUsageData(sample: deviceUsageSample, timestamp: timestamp)
        processNotificationUsageData(sample: deviceUsageSample, timestamp: timestamp)
    }
    
    return true
}

수집된 데이터가 있다면 해당 데이터를 가져와 데이터 모델에 넣어줄 수 있도록 구현되는 didFetchResult

 

세번째로

func sensorReader(_ reader: SRSensorReader, didCompleteFetch fetchRequest: SRFetchRequest) {
    print("디바이스 사용 데이터 페치 완료")
}

데이터 페치가 완료됨을 확인할 수 있는 didCompleteFetch 메서드 이렇게 세개의 메서드를 상기와 같이 구현하게 된다면 권한 설정이 잘 되어있고 startRecording을 한지 24시간이 지났다면 정상적으로 데이터를 가져올 수 있게 된다.

 

각각 델리게이트 메서드는 콜백 형식으로 앞서 말한 메서드들이 정상적으로 작동했다면 자동으로 실행되는 형식이다. 예를 들어 deviceUsageReader.fetch(request)가 제대로 구현되었다면 자동으로 didCompleteFetch가 실행되고, deviceUsageReader.fetchDevices() 가 제대로 구현되었다면 didFetch 메서드가 실행된다.

 

이 델리게이트 메서드가 제대로 실행되는지 확인한다면 현재 내가 어디에 문제가 있는지 혹은 잘 구현했는지 확인할 수 있다.

 

오늘은 일단 SensorKit을 활용해 데이터를 페치하는 과정에 대해서 기록했다 🙂

데이터를 가져오는데 막혀있느라 마땅히 구현을 잘 하지 못했는데 이제 진도를 좀 쑥쑥 빼려고한다!!

 

오늘은 여기까지!!