HealthKit query hangs and consumes vast amounts of memory on device only.

I have suspected there is something wrong with my personal Health account for a while but while working on a new app that queries HealthKit I'm pretty certain.

I've developed a very simple health app where you can request auth to read steps and then runs a statistic collection query to get a tally of steps you take.

This test app runs on the simulator fine. But when I run it on a device using my Apple ID the query just hangs. I don't get the callback for the initial result or the updated results.

Looking in Xcode, the memory graph just steadily increases until you terminate the app. And it appears there are millions of statistics objects being created.

Can anyone suggest anything to prevent this from happening? Do I need to reset my Health data? I would rather there was another solution.

I've included the code for this dummy app below. You will need to add HealthKit capabilities and the Privacy: Health Sharing info key for it to run.

import SwiftUI
import HealthKit

@main
struct StepCountApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class ViewModel: ObservableObject {
	let healthStore = HKHealthStore()
	var query: HKStatisticsCollectionQuery?
	@Published var isQuerying = false
	@Published var authStatus: HKAuthorizationStatus = .notDetermined
	@Published var stepCount: Int = 0
	
	init() {
		
	}
	
	func updateAuthStatus() {
		authStatus = healthStore.authorizationStatus(for: .quantityType(forIdentifier: .stepCount)!)
	}
	
	func requestAuth() {
		let sharing = Set<HKSampleType>()
		let reading = Set<HKObjectType>([.quantityType(forIdentifier: .stepCount)!])
		healthStore.requestAuthorization(toShare: sharing, read: reading) { [weak self] result, error in
			if result {
				DispatchQueue.main.async {
					self?.updateAuthStatus()
				}
			} else if let error = error {
				print("Auth failed: \(error.localizedDescription)")
			} else {
				fatalError("How did we get here?")
			}
		}
	}
	
	func stopQuery() {
		guard let query = query else { return }
		healthStore.stop(query)
		self.query = nil
		isQuerying = false
	}
	
	func startQuery() {
		let stepsType = HKObjectType.quantityType(forIdentifier: .stepCount)!
		let intervalComponents = DateComponents(day: 1)
		query = HKStatisticsCollectionQuery(
			quantityType: stepsType,
			quantitySamplePredicate: nil,
			options: [.cumulativeSum],
			anchorDate: Date(),
			intervalComponents: intervalComponents
		)
		
		query!.initialResultsHandler = { [weak self] query, results, error in
			print("Initial result")
			if let error = error {
				print("Error: \(error.localizedDescription)")
				return
			}
			guard let statisticsCollection = results else {
				print("Error: No stats collection")
				return
			}
			guard let statistics = statisticsCollection.statistics(for: Date()) else {
				print("Error: No stats for date")
				return
			}
			
			let sum = statistics.sumQuantity()?.doubleValue(for: .count()) ?? 0
			DispatchQueue.main.async {
				self?.stepCount = Int(sum)
			}
		}
		query!.statisticsUpdateHandler = { [weak self] query, statistics, results, error in
			print("Updated result")
			if let error = error {
				print("Error: \(error.localizedDescription)")
				return
			}
			guard let statisticsCollection = results else {
				print("Error: No stats collection")
				return
			}
			guard let statistics = statisticsCollection.statistics(for: Date()) else {
				print("Error: No stats for date")
				return
			}
			guard let sum = statistics.sumQuantity()?.doubleValue(for: .count()) else {
				print("Error: Statistics have no data?")
				return
			}
			
			DispatchQueue.main.async {
				DispatchQueue.main.async {
					self?.stepCount = Int(sum)
				}
			}
		}
		healthStore.execute(query!)
		
		isQuerying = true
	}
	
	var authStatusString: String {
		switch authStatus {
			case .notDetermined: return "Not determined"
			case .sharingAuthorized: return "Sharing authorised"
			case .sharingDenied: return "Sharing denied"
			@unknown default: fatalError()
		}
	}
}

struct ContentView: View {
	@ObservedObject var viewModel = ViewModel()
	
	var body: some View {
		Form {
			Section {
				HStack {
					Text("Auth Status")
					Spacer()
					Text("\(viewModel.authStatusString)")
				}
			}
			Section {
				Button("Request Auth") {
					viewModel.requestAuth()
				}
			}
			Section {
				if viewModel.isQuerying {
					Button("Stop query") {
						viewModel.stopQuery()
					}
				} else {
					Button("Start query") {
						viewModel.startQuery()
					}
				}
			}
			Section {
				HStack {
					Text("Step count")
					Spacer()
					Text("\(viewModel.stepCount)")
				}
			}
		}
	}
}

struct ContentView_Previews: PreviewProvider {
	static var previews: some View {
		ContentView()
	}
}

Accepted Reply

I deleted all my Health data by going to Settings > Health > Data Access & Devices then going through all the devices and apps that had written data to Health. This appears to have solved the issue at the cost of all my recorded data.

Replies

Ok, I've got a spare device and I have reset it and created a brand new Apple ID and now the above code works exactly as I would expect.

So this leads me to the following question. How can I reset my HealthKit for my personal Apple ID, without losing my data as I would like to keep it?

I deleted all my Health data by going to Settings > Health > Data Access & Devices then going through all the devices and apps that had written data to Health. This appears to have solved the issue at the cost of all my recorded data.