HealthKitアプリSwiftコードの紹介(3) – HealthKitデータの監視

Apple

HealthKitの心電図データと数量データの監視

次のstartObservingDataChangesメソッドは、健康データの変更を監視し、データをサーバーにアップロードするための非同期処理を開始するためのものです。このメソッドは、指定されたクロージャーの完了時に呼び出される非同期タスクを開始します。

シーケンス図

以下に、本投稿で説明する部分のシーケンス図を示します。

まず、self.queue.asyncを使用して、データ処理を非同期で実行するためにディスパッチキューに渡します。これにより、メソッドの呼び出し元のスレッドがブロックされることなく、バックグラウンドで処理が行われます。

その後、HealthKitStore.healthKitActivityTypeの各サンプルタイプに対して、処理を開始します。サンプルタイプごとに、最新の健康データサンプルを取得し、データを適切な形式に変換して処理します。例えば、心電図の場合はECGUploadDataModelに変換し、その他の健康データの場合はHealthKitQualityUploadDataModelに変換します。これらの変換されたデータは、データベースに保存され、サーバーにアップロードされます。

すべてのサンプルタイプに対する処理が完了すると、指定されたクロージャーが呼び出され、処理の完了を通知します。

    public func startObservingDataChanges(completion: @escaping () -> Void) {
        self.queue.async { [weak self] in
            guard let `self` = self else { return }
            self.group = DispatchGroup()
            self.blockIdentifier = [:]
            self.sendingIdentifier = [:]
            self.uploadDatas = []
            //Query activity type
            HealthKitStore.healthKitActivityType.forEach { sampleType in
                self.group.enter()
                self.getMostRecentHealthKitSample(for: sampleType) { [weak self] samples, endDate, error in
                    if let error = error {
                        print("error \((error.localizedDescription))")
                        self?.group.leave()
                        return
                    }
                    if sampleType == HKSampleType.electrocardiogramType() {
                        var newDatas = [ECGUploadDataModel]()
                        samples.forEach { sample in
                            if let sample = sample as? HKElectrocardiogram {
                                let uuid = UUID().uuidString
                                let newData = ECGUploadDataModel(uuid: uuid, averageHeartRate: sample.averageHeartRate?.doubleValue(for: HKUnit(from: "count/min")), classification: sample.classification.rawValue, symptomsStatus: sample.symptomsStatus.rawValue, numberOfVoltageMeasurements: sample.numberOfVoltageMeasurements, samplingFrequency: sample.samplingFrequency?.doubleValue(for: HKUnit.hertz()), startDate: sample.startDate, endDate: sample.endDate)
                                newDatas.append(newData)
                                self?.queryECGSample(ecgSample: sample, ecgId: uuid)
                            }
                        }
                        self?.storeDataToDatabase(type: sampleType.identifier, models: newDatas)
                        self?.updateEndTime(identifier: sampleType.identifier, endDate: endDate)
                        self?.group.leave()
                        return
                    }
                    if let samples = samples as? [HKQuantitySample], let unit = HealthKitStore.mapHealthKitActivityUnit[sampleType], samples.count > 0 {
                        var newDatas = [HealthKitQualityUploadDataModel]()
                        samples.forEach { sample in
                            var count = sample.quantity.doubleValue(for: unit)
                            if unit == HKUnit.percent() {
                                count *= 100
                            }
                            let newData = HealthKitQualityUploadDataModel(quantity: count, unit: unit.unitString, startDate: sample.startDate, endDate: sample.endDate)
                            newDatas.append(newData)
                        }
                        self?.storeDataToDatabase(type: sampleType.identifier, models: newDatas)
                        self?.updateEndTime(identifier: sampleType.identifier, endDate: endDate)
                    }
                    self?.group.leave()
                }
            }

            //省略
    }

このコードは、HealthKitのデータ変更を監視し、変更があった場合に対応する非同期処理を実行しています。

  1. self.queue.async { ... }: バックグラウンドで非同期処理を実行するためのGCDのキューに処理を追加します。
  2. self.group = DispatchGroup(): 新しいDispatchGroupを作成します。このグループは、複数の非同期タスクが完了したかを監視するために使用されます。
  3. HealthKitStore.healthKitActivityType.forEach { sampleType in ... }: HealthKitで監視するデータの種類をループで処理します。これにより、各データタイプに対して適切な処理を実行します。
  4. self.group.enter(): 新しい非同期タスクが開始されたことをDispatchGroupに通知します。
  5. self.getMostRecentHealthKitSample(for: sampleType) { ... }: 指定されたサンプルタイプの最新のHealthKitデータを取得する非同期処理を実行します。
  6. if let error = error { ... }: エラーが発生した場合は、エラーメッセージを出力し、DispatchGroupから退出します。
  7. if sampleType == HKSampleType.electrocardiogramType() { ... }: サンプルタイプが心電図データである場合、心電図データを処理します。
  8. if let samples = samples as? [HKQuantitySample], let unit = HealthKitStore.mapHealthKitActivityUnit[sampleType], samples.count > 0 { ... }: データが数量データである場合、それを処理します。
  9. self?.group.leave(): 非同期処理が完了したことをDispatchGroupに通知し、グループから退出します。

このコードは、HealthKitからのデータ変更を非同期で監視し、データが変更された場合に適切な処理を実行するメカニズムを提供します。

HealthKitのイベント型データの監視

    public func startObservingDataChanges(completion: @escaping () -> Void) {
        self.queue.async { [weak self] in

         //省略

            //Query event type
            HealthKitStore.healthKitEventType.forEach { sampleType in
                self.group.enter()
                self.getMostRecentHealthKitSample(for: sampleType) { [weak self] samples, endDate, error in
                    if let error = error {
                        print("error \((error.localizedDescription))")
                        self?.group.leave()
                        return
                    }
                    if let samples = samples as? [HKCategorySample], samples.count > 0 {
                        var newDatas = [HealthKitQualityUploadDataModel]()
                        samples.forEach { sample in
                            let newData = HealthKitQualityUploadDataModel(quantity: Double(sample.value), unit: "", startDate: sample.startDate, endDate: sample.endDate)
                            newDatas.append(newData)
                        }
                        self?.storeDataToDatabase(type: sampleType.identifier, models: newDatas)
                        self?.updateEndTime(identifier: sampleType.identifier, endDate: endDate)
                    }
                    self?.group.leave()
                }
            }

         //省略

        }
    }

この部分のコードは、HealthKitのイベント型データを監視し、最新のデータを取得して処理するための非同期処理です。

  1. HealthKitStore.healthKitEventType.forEach { sampleType in ... }: HealthKitのイベント型データをループで処理します。これにより、各イベントタイプに対して適切な処理を実行します。
  2. self.group.enter(): 新しい非同期タスクが開始されたことをDispatchGroupに通知します。
  3. self.getMostRecentHealthKitSample(for: sampleType) { ... }: 指定されたイベントタイプの最新のHealthKitデータを取得する非同期処理を実行します。
  4. if let error = error { ... }: エラーが発生した場合は、エラーメッセージを出力し、DispatchGroupから退出します。
  5. if let samples = samples as? [HKCategorySample], samples.count > 0 { ... }: データがカテゴリ型のデータである場合、それを処理します。
  6. self?.group.leave(): 非同期処理が完了したことをDispatchGroupに通知し、グループから退出します。

このコードは、HealthKitからのイベント型データの変更を監視し、データが変更された場合に適切な処理を実行します。

HealthKitのカテゴリ型データを監視

    public func startObservingDataChanges(completion: @escaping () -> Void) {
        self.queue.async { [weak self] in

         //省略

            //Query category type
            HealthKitStore.healthKitCategoryType.forEach { sampleType in
                self.group.enter()
                self.getMostRecentHealthKitSample(for: sampleType) { [weak self] samples, endDate, error in
                    if let error = error {
                        print("error \((error.localizedDescription))")
                        self?.group.leave()
                        return
                    }
                    if let samples = samples as? [HKCategorySample], samples.count > 0 {
                        var newDatas = [HealthKitQualityUploadDataModel]()
                        samples.forEach { sample in
                            let newData = HealthKitQualityUploadDataModel(quantity: Double(sample.value), unit: "", startDate: sample.startDate, endDate: sample.endDate)
                            newDatas.append(newData)
                        }
                        self?.storeDataToDatabase(type: sampleType.identifier, models: newDatas)
                        self?.updateEndTime(identifier: sampleType.identifier, endDate: endDate)
                    }
                    self?.group.leave()
                }
            }

         //省略

        }
    }

この部分のコードは、HealthKitのカテゴリ型データを監視し、最新のデータを取得して処理するための非同期処理です。

  1. HealthKitStore.healthKitCategoryType.forEach { sampleType in ... }: HealthKitのカテゴリ型データをループで処理します。これにより、各カテゴリ型データに対して適切な処理を実行します。
  2. self.group.enter(): 新しい非同期タスクが開始されたことをDispatchGroupに通知します。
  3. self.getMostRecentHealthKitSample(for: sampleType) { ... }: 指定されたカテゴリ型データの最新のHealthKitデータを取得する非同期処理を実行します。
  4. if let error = error { ... }: エラーが発生した場合は、エラーメッセージを出力し、DispatchGroupから退出します。
  5. if let samples = samples as? [HKCategorySample], samples.count > 0 { ... }: データがカテゴリ型のデータである場合、それを処理します。
  6. self?.group.leave(): 非同期処理が完了したことをDispatchGroupに通知し、グループから退出します。

このコードは、HealthKitからのカテゴリ型データの変更を監視し、データが変更された場合に適切な処理を実行します。

非同期処理が完了した際の処理

    public func startObservingDataChanges(completion: @escaping () -> Void) {
        self.queue.async { [weak self] in

         //省略

            self.group.notify(queue: self.queue) { [weak self] in
                guard let self else { return }
                self.updateCharacteristicIdentifiers()
                if self.uploadDatas.count != 0 {
                    let deviceUUID = self.userDefault.stringValue(key: Constaints.kDeviceUUID) ?? ""
                    self.dataSyncAPI.requestBGUploadData(data: self.uploadDatas, deviceUUID: deviceUUID, sendingIdentifiers: self.sendingIdentifier)
                }
                completion()
            }

         //省略

        }
    }

この部分のコードは、DispatchGroup内のすべての非同期処理が完了した際に実行される処理を定義しています。

  1. self.group.notify(queue: self.queue) { ... }: DispatchGroup内のすべてのタスクが完了したときに、指定されたキューでクロージャが非同期に実行されます。これにより、DispatchGroup内の全ての非同期処理の完了を待ちます。
  2. guard let self else { return }: クロージャ内でselfが解放されていないことを確認し、それ以外の場合は処理を中断します。
  3. self.updateCharacteristicIdentifiers(): HealthKitの特性識別子を更新するためのメソッドを呼び出します。これにより、特定の特性識別子が変更されたかどうかを検出し、必要に応じてアップロードデータを更新します。
  4. if self.uploadDatas.count != 0 { ... }: アップロードデータがある場合、それをサーバーに送信します。
  5. completion(): 完了ハンドラーを呼び出し、処理が終了したことを通知します。通常、このハンドラーは外部から渡され、処理の完了を監視するために使用されます。

このコードは、DispatchGroup内の全ての非同期処理が完了した後に特定の処理を実行し、その完了を外部に通知するために使用されます。

関連記事

カテゴリー

アーカイブ

Lang »