自我量化——使用HealthKit導出運動數據

最近開始跑步了,天天看到「健身紀錄」的圓圈,挺有感觸的。html

IMG_0248

天天的「圈」裏能看到當天的活動量、鍛鍊時長、站立時間。ios

打開詳情後,還能看到跑步步數、跑步距離、以及從第三方同步過來的數據等。git

IMG_0254

今天咱們的目標是拿到第一個量化數據:「健身記錄」的圓圈數據,即 iPhone 提供的 HealthKit data 數據導出,放入第三方數據庫中,以供後續統計和分析。github

數據來源

簡單看看個人「健康」APP 的數據: 數據庫

IMG_0259

有了健康 app,你能夠將各類健康和健身信息都保存在一個地方,讓它們盡在你的掌控。保存哪些信息,以及哪些 app 能夠經過健康 app 訪問你的數據都由你決定。當你使用密碼、觸控 ID 或面容 ID 鎖定手機時,健康 app 中全部的健康和運動數據都將被加密,只有醫療急救卡中的信息除外。你能夠經過 iCloud,讓健康數據自動在你的各類設備上保持更新,包括傳輸和存儲過程都會被加密保護。此外,訪問 HealthKit 的 app 都必須備有隱私政策,所以在受權這些 app 訪問你的健康和健身數據以前,請務必仔細查看這些政策。swift

咱們再來看看平時都有哪些第三方 app 受權訪問咱們的健康數據:api

IMG_0255

數據導出

數據來源有了,接下來就是考慮如何將健康數據導出。經過對 HealthKit 的瞭解,咱們須要本身動手開發一個 iOS app,獲取健康數據,並上傳到咱們的服務器上,達到數據導出的目標。數組

IMG_0250

這張「圈」圖是我 11 月 9 日的數據,今天的目標就是經過 HealthKit 獲取這個圈的數據,主要包括:bash

  1. 活動數據:1048 大卡
  2. 鍛鍊時間:92 分鐘
  3. 站立時間:11 小時

開發準備

在建立 iOS 應用 id 時,須要擁有「HealthKit」能力:服務器

Info.plist 配置中增長如下兩項內容:

MBHealthTracker

參考 HealthKit 開發文檔,要獲取「圈」運動統計數據,須要利用 Activity summary query 即,HKActivitySummaryQuery 查詢。

結果字段類型爲:HKActivitySummary

HKActivitySummaryQuery

A query for read activity summary objects from the HealthKit store.

參考:developer.apple.com/documentati…

HKActivitySummary

An object that contains the move, exercise, and stand data for a given day.

參考:developer.apple.com/documentati…

瞭解了 HKActivitySummaryQueryHKActivitySummary,咱們就能夠進入開發了。

這裏我主要藉助「MBHealthTracker」,MBHealthTracker 封裝好了和 HealthKit 交互的受權,獲取數據等,只要直接調用便可。

MBHealthTracker Github: github.com/matybrennan…

當我發現 MBHealthTracker 沒有對應的功能獲取 HKActivitySummary 功能,因此須要咱們在 MBHealthTracker 基礎上增長杜對應的獲取方法。

ActivitySummaryService

先在 Presentation 下增長 ActivitySummary 模型,主要是建立數組。

import Foundation
import HealthKit

public struct ActivitySummary {
    public let items: [HKActivitySummary]
}
複製代碼

接着在 Business Logic 邏輯層,建立協議和實現方法:

// ActivitySummaryServiceProtocol.swift
import Foundation
import HealthKit

public protocol ActivitySummaryServiceProtocol {
    
    // 根據起止時間獲取 ActivitySummary
    func getActivitySummary(startDate: Date, endDate: Date, completionHandler: @escaping (MBAsyncCallResult<ActivitySummary>) -> Void) throws
}

// ActivitySummaryService.swift
import Foundation
import HealthKit

class ActivitySummaryService {
    public init() { }
}

extension ActivitySummaryService: ActivitySummaryServiceProtocol {
    
    func getActivitySummary(startDate: Date, endDate: Date, completionHandler: @escaping (MBAsyncCallResult<ActivitySummary>) -> Void) throws {
        
        
        try isDataStoreAvailable()
        
        // Create the date components for the predicate
        guard let calendar = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian) else {
            fatalError("*** This should never fail. ***")
        }
        
        let units: NSCalendar.Unit = [.day, .month, .year, .era]
        
        var startDateComponents = calendar.components(units, from: startDate)
        startDateComponents.calendar = calendar as Calendar
        
        var endDateComponents = calendar.components(units, from: endDate)
        endDateComponents.calendar = calendar as Calendar
        
        // Create the predicate for the query
        let summariesWithinRange = HKQuery.predicate(forActivitySummariesBetweenStart: startDateComponents, end: endDateComponents)
         
        // Build the query
        let query = HKActivitySummaryQuery(predicate: summariesWithinRange) { (query, summaries, error) -> Void in
            self.configure(query: query, summaries: summaries, error: error, completionHandler: completionHandler)
        }
        
        healthStore.execute(query)
    }
}

private extension ActivitySummaryService {
    
    func configure(query: HKActivitySummaryQuery, summaries: [HKActivitySummary]?, error: Error?, completionHandler: @escaping (MBAsyncCallResult<ActivitySummary>) -> Void) {
        
        guard error == nil else {
            completionHandler(.failed(error!))
            return
        }
        let activitySummary = ActivitySummary(items: summaries!)
        completionHandler(.success(activitySummary))
    }
}
複製代碼

參考官網的 demo 和 MBHealthTracker 的方法,依葫蘆畫瓢寫好實現類,代碼簡單,就不詳細說明。

剩下的就是在 MBHealthTrackerProtocolMBHealthTracker 載入方法便可。

// MBHealthTrackerProtocol.swift
var activitySummary: ActivitySummaryServiceProtocol { get }

// MBHealthTracker.swift
private lazy var privateActivitySummaryService: ActivitySummaryServiceProtocol = {
    return ActivitySummaryService()
}()

...

public var activitySummary: ActivitySummaryServiceProtocol {
    return privateActivitySummaryService
}
複製代碼

獲取數據的部分暫時告一段落,下一步就是要經過配置,拿到受權能夠讀取 ActivitySummary 數據。

/// Just has read capabilities
public enum MBReadType: ReadableType {
    
    // Characteristics
    case dob
    case gender
    case activitySummary
    
    public var readable: HKObjectType {
        switch self {
        case .dob:
            return HKCharacteristicType.characteristicType(forIdentifier: .dateOfBirth)!
        case .gender:
            return HKCharacteristicType.characteristicType(forIdentifier: .biologicalSex)!
        case .activitySummary: return HKActivitySummaryType.activitySummaryType()
        }
    }
}
複製代碼

萬事俱備,咱們測試下看看運行結果。

import Foundation
import HealthKit

protocol ViewInteractorProtocol {
    func configurePermissions()
    func runTest()
}

class ViewInteractor {
    
    private let healthTracker: MBHealthTrackerProtocol
    
    init(healthTracker: MBHealthTrackerProtocol) {
        self.healthTracker = healthTracker
    }
}

// MARK: - ViewInteractorProtocol
extension ViewInteractor: ViewInteractorProtocol {
    
    // 配置寫入和讀取受權的類目
    func configurePermissions() {
        healthTracker.configuration.requestAuthorization(toShare: []
        
        ,toRead: [MBReadType.activitySummary]) { _ in }
    }
    
    // 測試,看看11-9號到11-3號得數據
    func runTest() {
        do {
            print("-----------------get summary begin----------------")
            let date = Date()
            let start = date.parse("2019-11-09")
            let end = date.parse("2019-11-13")
            try healthTracker.activitySummary.getActivitySummary(startDate: start, endDate: end, completionHandler: { (result) in
                print(result)
            })
            print("-----------------get summary end----------------")
        } catch {
            print("Unable to get: \(error.localizedDescription)")
        }
    }
}
複製代碼

運行打印出來的結果:

success(Lianghua.ActivitySummary(items: [<<HKActivitySummary: 0x2832480c0>: Date=(Year: 2019, Month: 11, Day: 9) Active Energy Burned=(1048.476259360438/310) Apple Exercise Minutes=(92/30) Apple Stand Hours=(11/12)>, <<HKActivitySummary: 0x283248180>: Date=(Year: 2019, Month: 11, Day: 10) Active Energy Burned=(474.8101270220084/310) Apple Exercise Minutes=(33/30) Apple Stand Hours=(6/12)>, <<HKActivitySummary: 0x283240f00>: Date=(Year: 2019, Month: 11, Day: 11) Active Energy Burned=(357.55/310) Apple Exercise Minutes=(22/30) Apple Stand Hours=(16/12)>, <<HKActivitySummary: 0x283241200>: Date=(Year: 2019, Month: 11, Day: 12) Active Energy Burned=(344.8089999999997/310) Apple Exercise Minutes=(17/30) Apple Stand Hours=(16/12)>, <<HKActivitySummary: 0x2832412c0>: Date=(Year: 2019, Month: 11, Day: 13) Active Energy Burned=(181.595/310) Apple Exercise Minutes=(21/30) Apple Stand Hours=(4/12)>]))
複製代碼

這裏咱們對照開篇的「圈」圖,和這裏的數據徹底一致。

總結

咱們導出健康數據後,就要考慮統一存放到雲平臺或者第三方存儲平臺上,以供後續統計分析。具體選擇什麼平臺來存儲數據呢,咱們下期再聊!

推薦

  1. 自我量化開篇:ziwolianghua.coding01.cn/1.1.html

未完待續

參考:

  1. developer.apple.com/documentati…
  2. github.com/openmhealth…
  3. www.openmhealth.org/
  4. www.apple.com/cn/ios/heal…
  5. github.com/mseemann/he…
  6. github.com/matybrennan…
  7. www.openmhealth.org/features/ca…
  8. developer.apple.com/documentati…
相關文章
相關標籤/搜索