HealthStoreUnitCache.swift 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. //
  2. // HealthStoreUnitCache.swift
  3. // LoopKit
  4. //
  5. // Copyright © 2018 LoopKit Authors. All rights reserved.
  6. //
  7. import HealthKit
  8. import os.log
  9. public class HealthStoreUnitCache {
  10. private static var cacheCache = NSMapTable<HKHealthStore, HealthStoreUnitCache>.weakToStrongObjects()
  11. public let healthStore: HKHealthStore
  12. private static let fixedUnits: [HKQuantityTypeIdentifier: HKUnit] = [
  13. .dietaryCarbohydrates: .gram(),
  14. .insulinDelivery: .internationalUnit()
  15. ]
  16. private var unitCache = Locked([HKQuantityTypeIdentifier: HKUnit]())
  17. private var userPreferencesChangeObserver: Any?
  18. private init(healthStore: HKHealthStore) {
  19. self.healthStore = healthStore
  20. userPreferencesChangeObserver = NotificationCenter.default.addObserver(forName: .HKUserPreferencesDidChange, object: healthStore, queue: nil, using: { [weak self] (_) in
  21. _ = self?.unitCache.mutate({ (cache) in
  22. cache.removeAll()
  23. })
  24. })
  25. }
  26. public class func unitCache(for healthStore: HKHealthStore) -> HealthStoreUnitCache {
  27. if let cache = cacheCache.object(forKey: healthStore) {
  28. return cache
  29. }
  30. let cache = HealthStoreUnitCache(healthStore: healthStore)
  31. cacheCache.setObject(cache, forKey: healthStore)
  32. return cache
  33. }
  34. public func preferredUnit(for quantityTypeIdentifier: HKQuantityTypeIdentifier) -> HKUnit? {
  35. if let unit = HealthStoreUnitCache.fixedUnits[quantityTypeIdentifier] {
  36. return unit
  37. }
  38. if let unit = unitCache.value[quantityTypeIdentifier] {
  39. return unit
  40. }
  41. guard let quantityType = HKQuantityType.quantityType(forIdentifier: quantityTypeIdentifier) else {
  42. return nil
  43. }
  44. var unit: HKUnit?
  45. let semaphore = DispatchSemaphore(value: 0)
  46. healthStore.preferredUnits(for: [quantityType]) { (results, error) in
  47. if let error = error {
  48. // This is a common/expected case when protected data is unavailable
  49. OSLog(category: "HealthStoreUnitCache").info("Error fetching unit for %{public}@: %{public}@", quantityTypeIdentifier.rawValue, String(describing: error))
  50. }
  51. unit = results[quantityType]
  52. _ = self.unitCache.mutate({ (cache) in
  53. cache[quantityTypeIdentifier] = unit
  54. })
  55. semaphore.signal()
  56. }
  57. _ = semaphore.wait(timeout: .now() + .seconds(3))
  58. return unit
  59. }
  60. deinit {
  61. if let observer = userPreferencesChangeObserver {
  62. NotificationCenter.default.removeObserver(observer)
  63. }
  64. }
  65. }