Try it free in the sandbox · first FHIR call in 5 minutesFree · 30 synthetic patients · no card
Apple Health (HealthKit)를 FHIR로
Swift로 HealthKit을 사용하여 Apple Watch 및 Apple Health 데이터를 로컬 일별로 읽고 FHIR R4 레코드에 쓰기: 권한, 백그라운드 동기화, Apple 규칙.
Apple Watch 데이터는 페어링된 iPhone의 Apple Health에 저장되며, 이를 읽는 유일한 방법은 해당 iPhone의 앱에서 사용자의 허가를 받아 HealthKit을 사용하는 것입니다. FHIR 레코드로 저장하려면 HKStatisticsCollectionQuery를 사용하여 일일 통계를 읽고, 해당 값을 백엔드로 전송한 후, 각 측정값을 LOINC 코드 및 UCUM 단위와 함께 Observation으로 작성합니다. 이 페이지에서는 Swift 코드(iOS 17 SDK에 대해 타입 검사됨), Apple이 설정한 권한 및 규칙, 그리고 값이 FHIR R4 Observation이 되는 과정을 보여줍니다.
Apple Health에 포함된 내용
Apple Health는 iPhone 및 Apple Watch에서 측정된 심박수, 안정 시 심박수, 심박 변이도(SDNN), 혈중 산소, 호흡수, 걸음 수, 에너지, 수면 단계, 불규칙한 리듬 알림, ECG 기록과 Garmin Connect, Oura, Withings Health Mate, 스마트 체중계, 혈압계, 혈당계와 같은 다른 앱에서 기록한 데이터를 수집합니다. 따라서 Apple Health를 읽는 것은 Apple Watch 이상의 내용을 포괄합니다.
HealthKit 서버나 웹 API는 없습니다. 데이터는 기기 내에 저장되며(그리고 사용자의 암호화된 iCloud 백업에 저장됨) 앱은 로컬에서 데이터를 읽습니다.
권한
- Xcode에서 HealthKit 기능을 켜고
NSHealthShareUsageDescription을Info.plist에 추가합니다 (쓰기 작업도 하는 경우에만NSHealthUpdateUsageDescription를 추가합니다). - 필요한 유형에 대한 권한을 요청합니다. 사용자는 일부는 허용하고 일부는 거부할 수 있으며, HealthKit은 앱에 읽기 액세스가 거부되었는지 여부를 알려주지 않습니다: 거부된 유형은 단순히 데이터를 반환하지 않습니다. 오류가 아닌 "데이터 없음" 상태를 위한 화면을 디자인하십시오.
- 처음 실행 시가 아니라, 사용자가 연결하는 시점에 왜 필요한지 설명하는 문장과 함께 권한을 요청하십시오.
Swift에서 일별 값 읽기
import Foundation
import HealthKit
/// One day of Apple Health data, ready to send to your backend.
struct DailyValue: Codable {
let metric: String // "heart_rate", "steps"
let day: String // "2026-10-01", the user's local day
let value: Double
let min: Double?
let max: Double?
}
let store = HKHealthStore()
let heartRate = HKQuantityType(.heartRate)
let steps = HKQuantityType(.stepCount)
func requestAccess() async throws {
// Read-only: the app never writes to Apple Health.
try await store.requestAuthorization(toShare: [], read: [heartRate, steps])
}
/// Daily statistics for the last `days` days, one value per local calendar day.
func dailyValues(_ type: HKQuantityType, metric: String, unit: HKUnit,
options: HKStatisticsOptions, days: Int) async throws -> [DailyValue] {
let calendar = Calendar.current
let end = calendar.startOfDay(for: Date()).addingTimeInterval(86_400)
let start = calendar.date(byAdding: .day, value: -days, to: end)!
let query = HKStatisticsCollectionQueryDescriptor(
predicate: HKSamplePredicate.quantitySample(type: type,
predicate: HKQuery.predicateForSamples(withStart: start, end: end)),
options: options,
anchorDate: start,
intervalComponents: DateComponents(day: 1))
let collection = try await query.result(for: store)
let fmt = DateFormatter()
fmt.dateFormat = "yyyy-MM-dd"
var out: [DailyValue] = []
collection.enumerateStatistics(from: start, to: end) { stats, _ in
let day = fmt.string(from: stats.startDate)
if options.contains(.cumulativeSum), let sum = stats.sumQuantity() {
out.append(DailyValue(metric: metric, day: day, value: sum.doubleValue(for: unit), min: nil, max: nil))
} else if let avg = stats.averageQuantity() {
out.append(DailyValue(metric: metric, day: day, value: avg.doubleValue(for: unit),
min: stats.minimumQuantity()?.doubleValue(for: unit),
max: stats.maximumQuantity()?.doubleValue(for: unit)))
}
}
return out
}
func collect() async throws -> [DailyValue] {
try await requestAccess()
let bpm = HKUnit.count().unitDivided(by: .minute())
let hr = try await dailyValues(heartRate, metric: "heart_rate", unit: bpm,
options: [.discreteAverage, .discreteMin, .discreteMax], days: 30)
let st = try await dailyValues(steps, metric: "steps", unit: .count(), options: .cumulativeSum, days: 30)
return hr + st // POST these to your backend, which writes them to the FHIR record
}
샘플을 읽는 대신 통계 쿼리를 사용하는 이유: HKStatisticsCollectionQuery은 Apple Health에서 이미 소스별로 중복 제거된 일별 총계 및 평균을 반환하므로 iPhone과 Watch 모두에서 계산된 걸음 수가 두 번 더해지지 않습니다. 원시 샘플을 읽고 직접 합산하는 것이 걸음 수 증가의 가장 일반적인 원인입니다.
일은 해당 사용자의 로컬 달력일(Calendar.current)입니다. FHIR 레코드까지 이 상태를 유지해야 하며, 여기서 Observation의 effectivePeriod은 시간대 오프셋이 있는 해당 로컬 날짜가 됩니다.
기록 최신 상태 유지하기
- 앱 열기 시: 앱이 포그라운드로 전환될 때마다 마지막 성공적인 동기화 이후 경과된 일수를 읽습니다. 이것이 신뢰할 수 있는 기준선입니다.
- 백그라운드 시:
HKObserverQuery와enableBackgroundDelivery(for:frequency:)는 새 데이터가 도착하면 앱을 깨웁니다. 이를 위해서는 HealthKit 백그라운드 전달 권한이 필요하며, iOS가 실제 타이밍을 결정합니다. - 첫 연결 시: 수년 치를 가져오는 대신 제한된 기록(예: 30일)을 가져온 다음 앞으로 진행합니다.
Swift 값에서 FHIR Observation으로
DailyValue 배열을 백엔드로 전송하고, 사람, 측정 항목, 날짜별로 하나의 Observation을 안정적인 식별자와 함께 작성하며, 같은 날이 다시 오면 업데이트합니다. 매핑 테이블(HealthKit 유형 → LOINC → UCUM), 검증된 JSON 예시, 그리고 이를 수행하는 TypeScript 코드는 Wearable data in FHIR에서 확인할 수 있습니다. 여기서 두 가지 측정 항목에 대한 코드는 다음과 같습니다.
| HealthKit | LOINC | UCUM | 일별 값 |
|---|---|---|---|
heartRate |
8867-4 Heart rate | /min |
평균, 구성 요소로 최소값 및 최대값 포함 |
stepCount |
41950-7 Number of steps in 24 hour | {steps} |
총계 |
Apple의 건강 데이터 관련 규정
- App Review Guideline 5.1.3: 건강 데이터는 광고 또는 데이터 마이닝에 사용될 수 없으며, 사용자의 동의 없이 제3자와 공유될 수 없고, 개인 건강 정보는 iCloud에 저장될 수 없습니다.
- 귀하의 개인정보처리방침에는 어떤 데이터를 읽고 그 이유가 명시되어야 합니다. Apple에서 이를 확인합니다.
- 귀하의 앱이 Apple Health에 기록하는 데이터는 귀하의 앱에 귀속됩니다. 귀하가 읽는 데이터는 원래 출처(
HKSource)를 유지하며, 이를 소스 시스템으로 기록에 포함해야 합니다.
Apple Health Records
피트니스 데이터와는 별개로, Apple Health는 참여 의료 시스템(주로 미국)에서 다운로드한 임상 기록을 저장할 수 있으며, HealthKit은 이를 원래 FHIR 리소스를 담고 있는 HKClinicalRecord 객체로 노출합니다. 이를 읽으려면 별도의 권한 및 엔타이틀먼트가 필요합니다. 이는 웨어러블 데이터와는 다른 통합이며, Anpheros Daily는 현재 이를 읽지 않습니다.
Anpheros의 역할
Anpheros Platform은 개인의 FHIR R4 레코드를 저장합니다. 각 웨어러블 값은 Observation으로 기록되며, 기기 데이터, 다른 앱에 대한 동의, 출처 정보, 접근 로그로 표시됩니다. Anpheros는 Apple Health 자체를 읽지 않습니다. 이는 사용자의 iOS 앱에서 처리됩니다. Anpheros 자체 앱인 Anpheros Daily는 iPhone(Plus 및 Max 플랜)에서 Apple Health를 읽고 위의 코드를 사용하여 일일 요약을 레코드에 기록합니다.
자주 묻는 질문
Apple Watch 데이터를 서버에서 읽을 수 있나요?
아니요. Apple Watch 데이터는 페어링된 iPhone의 Apple Health로 동기화되며, HealthKit은 해당 기기의 앱에서만 사용할 수 있습니다. 앱이 데이터를 읽어 서버로 전송합니다.
Apple Health 웹 API 또는 REST API가 있나요?
아니요. Apple은 Apple Health에 대한 웹 또는 REST API를 제공하지 않습니다. HealthKit은 개인의 iPhone(또는 Apple Watch) 내 앱에서만 실행됩니다. 서버에서 데이터를 사용하려면 앱이 개인의 허가를 받아 데이터를 읽고, 예를 들어 FHIR Observations 형태로 Anpheros로 전송해야 합니다.
Apple Health API에 API 키가 필요하거나 비용을 지불해야 하나요?
키도 필요 없고 수수료도 없습니다. HealthKit은 iOS의 일부입니다. 앱에 HealthKit 기능, 각 데이터 유형에 대한 사용 설명, 그리고 개인의 허가가 필요합니다. App Store 검토는 데이터 사용 방식을 확인합니다.
HealthKit 유형에 대한 접근을 개인이 거부했는지 어떻게 알 수 있나요?
읽기 접근의 경우 알 수 없습니다. HealthKit은 개인 정보 보호를 위해 이를 숨기며, 거부된 유형은 데이터를 반환하지 않습니다. "데이터 없음"을 정상 상태로 처리하고 Health 앱에서 권한을 검토할 수 있는 방법을 제공하세요.
내 걸음 수가 Health 앱보다 많은 이유는 무엇인가요?
아마도 여러 소스(iPhone 및 Watch)의 원시 샘플을 합산하고 있을 것입니다. Apple Health 자체의 중복 제거를 적용하는 .cumulativeSum와 함께 HKStatisticsCollectionQuery을 사용하세요.
Apple Watch 심박 변이도(HRV)에 대한 LOINC 코드는 무엇인가요?
Apple Health는 HRV를 SDNN으로 저장하며, 이는 LOINC 80404-7과 일치합니다. Health Connect는 다른 통계인 RMSSD를 저장하므로 두 가지를 하나의 코드로 혼합하지 마세요.
관련 자료
- FHIR에서의 웨어러블 데이터: 매핑 테이블 및 백엔드 코드
- Health Connect를 FHIR로 변환: Android에서의 동일한 내용
- 개인 건강 기록 앱 구축 방법
- Flutter로 헬스케어 앱 구축 방법
Try it free in the sandbox · first FHIR call in 5 minutesFree · 30 synthetic patients · no card