Skip to content
AF
HealthKit Queries

HKSampleQuery: a one-time snapshot of matching samples

Last verified October 3, 2026 · 5 min read

HKSampleQuery is HealthKit's general-purpose read, which Apple describes as "a general query that returns a snapshot of all the matching samples currently saved in the HealthKit store." You give it a sample type, an optional predicate, a limit and optional sort descriptors, run it with HKHealthStore's execute(_:), and receive one array of samples on a background queue. It does not keep watching the store, cannot be changed once created, and does not compute totals: use HKAnchoredObjectQuery to follow changes and HKStatisticsQuery for sums or averages.

Covered here:HealthKit

HKSampleQuery is usually the first query class anyone writes, and the one most often kept for jobs it was never designed for. This page covers how it behaves, according to Apple's own documentation, and the places it quietly gives you the wrong answer. Setup, entitlements and authorization are covered in the HealthKit integration guide.

What Apple says it is#

Apple's class page summarises it in one sentence: "A general query that returns a snapshot of all the matching samples currently saved in the HealthKit store." The overview adds three facts worth keeping in mind:

  • It searches "any concrete subclasses of the HKSample class, including HKCategorySample, HKCorrelation, HKQuantitySample, and HKWorkout objects."
  • "Sample queries are immutable: The query's properties are set when the query is first created, and they can't change."
  • "As with many HealthKit classes, don't subclass the HKSampleQuery class."

Availability. Apple's class page lists iOS, iPadOS, Mac Catalyst, macOS, visionOS and watchOS. Its documentation data gives no introduction version for the class itself. The multi-type initializer init(queryDescriptors:limit:resultsHandler:) is listed from iOS 15.0, iPadOS 15.0, Mac Catalyst 15.0, macOS 13.0, visionOS 1.0 and watchOS 8.0. The type identifiers that iOS 15 added are listed on the HealthKit types introduced in iOS 15.

The initializer, parameter by parameter#

The classic form is init(sampleType:predicate:limit:sortDescriptors:resultsHandler:). Apple documents each argument:

ParameterWhat Apple says
sampleType"This object can be an instance of the HKCategoryType, HKCorrelationType, HKQuantityType, or HKWorkoutType class."
predicateLimits the results. "Pass nil to receive all the samples of the specified type."
limit"The maximum number of samples returned by the query. If you want to return all matching samples, use HKObjectQueryNoLimit."
sortDescriptorsThe order of the results. "Pass nil if you don't need the results in a specific order."
resultsHandlerCalled once with the query, an array of samples ("or nil if an error occurs") and an error.

The predicate is where most of the real decisions are made, including whether a sample that started yesterday counts as today's. That has its own page: HealthKit query predicates.

A minimal read#

This sketch follows Apple's "Executing Sample Queries" article: one type, a date window, results sorted by start date.

import HealthKit

func readEnergyConsumed(store: HKHealthStore, start: Date, end: Date) {
    guard let energyType = HKSampleType.quantityType(forIdentifier: .dietaryEnergyConsumed) else { return }

    let window = HKQuery.predicateForSamples(withStart: start, end: end, options: [])
    let byStartDate = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: true)

    let query = HKSampleQuery(sampleType: energyType,
                              predicate: window,
                              limit: HKObjectQueryNoLimit,
                              sortDescriptors: [byStartDate]) { _, results, error in
        guard let samples = results as? [HKQuantitySample] else {
            // results is nil only when error is set
            return
        }
        DispatchQueue.main.async {
            // update the UI with samples
        }
    }
    store.execute(query)
}

The identifier used here is dietaryEnergyConsumed; converting its values means choosing an HKUnit, which the HealthKit units reference lists per type.

Which thread the handler runs on#

Apple is explicit: "Queries run on an anonymous background queue. As soon as the query is complete, the results handler is executed on the background queue. You typically dispatch these results to the main queue to update the user interface." The article version adds that the handler runs on the same background queue "but not necessarily the same thread". Touching UI state directly inside the handler is the classic crash; hop to the main queue first.

Reading several types in one query#

Since iOS 15.0 you can pass an array of HKQueryDescriptor objects instead of one type, using init(queryDescriptors:limit:resultsHandler:) or the variant that also takes sort descriptors. Apple: "Each descriptor can specify a different data type." Its own example reads step count and push count samples together. The results still come back as [HKSample], so you sort them out by type yourself.

When HKSampleQuery is the wrong class#

You needUse instead
A sum, average, minimum or maximum over a windowHKStatisticsQuery
One value per day or hour for a chartHKStatisticsCollectionQuery
Only what changed since the last read, including deletionsHKAnchoredObjectQuery
A signal that something changedHKObserverQuery
async/await instead of a callbackHKSampleQueryDescriptor
The GPS points of a workoutHKWorkoutRouteQuery
Activity ring valuesHKActivitySummaryQuery

Apple makes one comparison explicitly, on the anchored query's page: "Often, it's more efficient to set up and run a single anchored object query than to run separate sample and observer queries."

Pitfalls#

  • Sort identifiers only work inside a query. Apple's note on sortDescriptors: "Use the sort descriptors you create with these identifiers only in queries. You cannot use them to perform an in-memory sort of an array of samples." To re-sort an array you already have, sort on the sample's own properties.
  • A snapshot never shows a deletion. Running HKSampleQuery again and comparing the results is a poor way to sync, because a deleted sample is simply missing. Apple's page for HKDeletedObject says to "use HKAnchoredObjectQuery queries to generate a list of recently deleted objects." The reasoning is laid out in incremental sync.
  • Adding up samples is not the same as asking for a total. Apple documents that statistics queries "automatically merge the data from all of your data sources before performing the calculations". A loop over a sample array does no such merge. Read totals with HKStatisticsQuery, and see deduplicating health data for what overlapping sources do to a naive sum.
  • The limit is not optional. In this API limit is a plain Int, so you choose a cap or pass HKObjectQueryNoLimit. With a date sort descriptor and a small limit you get the first N samples in that order, which is how "the newest ten" is written; without a sort descriptor, Apple does not document which N you get.
  • An empty array is not an error. A query that comes back with no samples and no error can mean there is no data, the predicate matched nothing, or reading was not permitted. HealthKit returns no data covers how to tell those apart, and authorization denied covers the permission side.

Frequently asked questions

How do I make HKSampleQuery return every matching sample instead of a capped number?
Pass HKObjectQueryNoLimit as the limit. Apple documents it as a value indicating that the query returns all the matching samples in the HealthKit store, and the limit parameter's own description says to use it when you want every match. Any positive integer instead caps the result count, which combined with a sort descriptor on the start or end date is how you ask for only the newest or oldest few samples.
Can I sort an array of HealthKit samples with HKSampleSortIdentifierStartDate?
No. Apple's note on the sortDescriptors parameter says sort descriptors built from HealthKit's sort identifiers, such as HKSampleSortIdentifierStartDate or HKWorkoutSortIdentifierDuration, can be used only in queries and cannot be used to sort an array of samples in memory. Pass them to the query so the store returns the samples in order, or sort an existing array on the samples' own date properties.
Which thread does the HKSampleQuery results handler run on?
A background one. Apple documents that queries run on an anonymous background queue and that the results handler executes on that background queue once the query completes, though not necessarily on the same thread. Apple's guidance is to dispatch the results to the main queue before updating the user interface, so any UI work belongs inside a main-queue dispatch.
Can a single HKSampleQuery read more than one data type?
Yes, from iOS 15.0 and watchOS 8.0. The initializer init(queryDescriptors:limit:resultsHandler:) takes an array of HKQueryDescriptor objects, and Apple says each descriptor can specify a different data type. Apple's example reads step count and push count samples in one query. The results arrive as one array of HKSample objects, so your code separates them by type afterwards.

All 40 questions in HealthKit Queries →

Keep reading

Elsewhere on the site

Pages that share this one’s concepts and sources, from other sections.

Next steps

Was this page useful?

Independent comparison, last reviewed October 3, 2026. Pricing, rate limits, and feature availability change often — confirm current details in each provider’s official documentation before you commit. Product and company names are trademarks of their respective owners; AIFitnessAPI is not affiliated with, endorsed by, or sponsored by any product listed here.

← All healthkit queries · by AIFitnessAPI