@kingstinct/react-native-healthkit 14.1.0 → 16.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -3
- package/ReactNativeHealthkit.podspec +11 -7
- package/app.plugin.js +33 -94
- package/app.plugin.ts +29 -104
- package/ios/CategoryTypeModule.swift +1 -0
- package/ios/CharacteristicTypeModule.swift +7 -6
- package/ios/Constants.swift +9 -10
- package/ios/CoreModule.swift +77 -42
- package/ios/CorrelationTypeModule.swift +3 -2
- package/ios/ElectrocardiogramModule.swift +4 -3
- package/ios/HeartbeatSeriesModule.swift +4 -3
- package/ios/Helpers.swift +11 -216
- package/ios/MedicationModule.swift +9 -8
- package/ios/MemoryEstimation.swift +84 -0
- package/ios/PredicateHelpers.swift +16 -86
- package/ios/QuantityTypeModule.swift +37 -28
- package/ios/Serializers.swift +34 -70
- package/ios/SourceProxy.swift +15 -5
- package/ios/StateOfMindModule.swift +3 -2
- package/ios/WorkoutProxy.swift +31 -13
- package/ios/WorkoutsModule.swift +4 -3
- package/lib/commonjs/generated/healthkit.generated.js +15 -3
- package/lib/commonjs/healthkit.ios.js +6 -2
- package/lib/commonjs/healthkit.js +24 -79
- package/lib/commonjs/types/Auth.js +3 -18
- package/lib/commonjs/types/Background.js +2 -10
- package/lib/commonjs/types/QueryOptions.js +2 -17
- package/lib/module/generated/healthkit.generated.js +14 -2
- package/lib/module/healthkit.ios.js +4 -0
- package/lib/module/healthkit.js +22 -77
- package/lib/module/types/Auth.js +1 -18
- package/lib/module/types/Background.js +1 -10
- package/lib/module/types/QueryOptions.js +1 -17
- package/lib/typescript/generated/healthkit.generated.d.ts +20 -7
- package/lib/typescript/healthkit.d.ts +4 -2
- package/lib/typescript/healthkit.ios.d.ts +13 -9
- package/lib/typescript/specs/CoreModule.nitro.d.ts +22 -4
- package/lib/typescript/specs/SourceProxy.nitro.d.ts +2 -5
- package/lib/typescript/types/Auth.d.ts +1 -16
- package/lib/typescript/types/Background.d.ts +1 -9
- package/lib/typescript/types/Device.d.ts +1 -13
- package/lib/typescript/types/QuantityType.d.ts +3 -3
- package/lib/typescript/types/QueryOptions.d.ts +3 -37
- package/lib/typescript/types/Shared.d.ts +2 -25
- package/lib/typescript/types/Source.d.ts +1 -10
- package/lib/typescript/types/Subscriptions.d.ts +2 -3
- package/lib/typescript/types/Units.d.ts +1 -1
- package/nitrogen/generated/ios/ReactNativeHealthkit-Swift-Cxx-Bridge.cpp +24 -16
- package/nitrogen/generated/ios/ReactNativeHealthkit-Swift-Cxx-Bridge.hpp +91 -12
- package/nitrogen/generated/ios/c++/HybridCategoryTypeModuleSpecSwift.hpp +8 -5
- package/nitrogen/generated/ios/c++/HybridCoreModuleSpecSwift.hpp +8 -0
- package/nitrogen/generated/ios/c++/HybridCorrelationTypeModuleSpecSwift.hpp +8 -5
- package/nitrogen/generated/ios/c++/HybridElectrocardiogramModuleSpecSwift.hpp +8 -5
- package/nitrogen/generated/ios/c++/HybridHeartbeatSeriesModuleSpecSwift.hpp +8 -5
- package/nitrogen/generated/ios/c++/HybridMedicationModuleSpecSwift.hpp +8 -5
- package/nitrogen/generated/ios/c++/HybridQuantityTypeModuleSpecSwift.hpp +8 -5
- package/nitrogen/generated/ios/c++/HybridStateOfMindModuleSpecSwift.hpp +8 -5
- package/nitrogen/generated/ios/c++/HybridWorkoutProxySpecSwift.hpp +3 -4
- package/nitrogen/generated/ios/c++/HybridWorkoutsModuleSpecSwift.hpp +6 -3
- package/nitrogen/generated/ios/swift/CategorySample.swift +8 -8
- package/nitrogen/generated/ios/swift/CategoryTypeIdentifier.swift +8 -0
- package/nitrogen/generated/ios/swift/CategoryTypeIdentifierWriteable.swift +8 -0
- package/nitrogen/generated/ios/swift/CorrelationSample.swift +8 -8
- package/nitrogen/generated/ios/swift/ElectrocardiogramSample.swift +8 -8
- package/nitrogen/generated/ios/swift/Func_void_std__unordered_map_std__string__std__chrono__system_clock__time_point_.swift +54 -0
- package/nitrogen/generated/ios/swift/HeartbeatSeriesSample.swift +8 -8
- package/nitrogen/generated/ios/swift/HybridCoreModuleSpec.swift +1 -0
- package/nitrogen/generated/ios/swift/HybridCoreModuleSpec_cxx.swift +25 -0
- package/nitrogen/generated/ios/swift/MedicationDoseEvent.swift +8 -8
- package/nitrogen/generated/ios/swift/ObjectTypeIdentifier.swift +12 -4
- package/nitrogen/generated/ios/swift/QuantitySample.swift +8 -8
- package/nitrogen/generated/ios/swift/QuantityTypeIdentifier.swift +4 -4
- package/nitrogen/generated/ios/swift/QuantityTypeIdentifierWriteable.swift +4 -4
- package/nitrogen/generated/ios/swift/QueryStatisticsResponse.swift +6 -13
- package/nitrogen/generated/ios/swift/QueryStatisticsResponseFromSingleSource.swift +4 -11
- package/nitrogen/generated/ios/swift/SampleTypeIdentifier.swift +12 -4
- package/nitrogen/generated/ios/swift/SampleTypeIdentifierWriteable.swift +12 -4
- package/nitrogen/generated/ios/swift/SourceRevision.swift +4 -11
- package/nitrogen/generated/ios/swift/StateOfMindSample.swift +8 -8
- package/nitrogen/generated/ios/swift/WorkoutActivityType.swift +8 -0
- package/nitrogen/generated/shared/c++/CategorySample.hpp +7 -7
- package/nitrogen/generated/shared/c++/CategoryTypeIdentifier.hpp +67 -59
- package/nitrogen/generated/shared/c++/CategoryTypeIdentifierWriteable.hpp +67 -59
- package/nitrogen/generated/shared/c++/CorrelationSample.hpp +7 -7
- package/nitrogen/generated/shared/c++/ElectrocardiogramSample.hpp +7 -7
- package/nitrogen/generated/shared/c++/HeartbeatSeriesSample.hpp +7 -7
- package/nitrogen/generated/shared/c++/HybridCoreModuleSpec.cpp +1 -0
- package/nitrogen/generated/shared/c++/HybridCoreModuleSpec.hpp +3 -1
- package/nitrogen/generated/shared/c++/MedicationDoseEvent.hpp +7 -7
- package/nitrogen/generated/shared/c++/ObjectTypeIdentifier.hpp +206 -198
- package/nitrogen/generated/shared/c++/QuantitySample.hpp +7 -7
- package/nitrogen/generated/shared/c++/QuantityTypeIdentifier.hpp +73 -73
- package/nitrogen/generated/shared/c++/QuantityTypeIdentifierWriteable.hpp +73 -73
- package/nitrogen/generated/shared/c++/QueryStatisticsResponse.hpp +8 -9
- package/nitrogen/generated/shared/c++/QueryStatisticsResponseFromSingleSource.hpp +8 -9
- package/nitrogen/generated/shared/c++/SampleTypeIdentifier.hpp +199 -191
- package/nitrogen/generated/shared/c++/SampleTypeIdentifierWriteable.hpp +193 -185
- package/nitrogen/generated/shared/c++/SourceRevision.hpp +8 -9
- package/nitrogen/generated/shared/c++/StateOfMindSample.hpp +7 -7
- package/nitrogen/generated/shared/c++/WorkoutActivityType.hpp +4 -0
- package/package.json +8 -3
- package/src/generated/healthkit-schema.json +53 -8
- package/src/generated/healthkit.generated.ts +22 -6
- package/src/healthkit.ios.ts +6 -0
- package/src/healthkit.ts +32 -84
- package/src/specs/CoreModule.nitro.ts +25 -4
- package/src/specs/SourceProxy.nitro.ts +2 -4
- package/src/test-setup.ts +1 -0
- package/src/type-tests/generated-typing.ts +30 -8
- package/src/types/Auth.ts +4 -17
- package/src/types/Background.ts +1 -9
- package/src/types/Device.ts +1 -13
- package/src/types/QuantityType.ts +3 -3
- package/src/types/QueryOptions.ts +7 -65
- package/src/types/Shared.ts +7 -28
- package/src/types/Source.ts +1 -11
- package/src/types/Subscriptions.ts +2 -3
- package/src/types/Units.ts +1 -1
- package/ios/BackgroundDeliveryManager.swift +0 -277
- package/ios/Bridge.h +0 -10
- package/ios/ExceptionCatcher.h +0 -5
- package/ios/ExceptionCatcher.mm +0 -32
- package/ios/module.modulemap +0 -5
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
//
|
|
2
|
+
// MemoryEstimation.swift
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Rough estimates of the native heap kept alive by HybridObject proxies,
|
|
6
|
+
// reported to Nitro via `memorySize` so the JS garbage collector can account
|
|
7
|
+
// for memory it cannot see. Precision is not the goal; the right order of
|
|
8
|
+
// magnitude per object is.
|
|
9
|
+
//
|
|
10
|
+
|
|
11
|
+
import HealthKit
|
|
12
|
+
import ReactNativeHealthkitCore
|
|
13
|
+
|
|
14
|
+
/// Approximate per-object bridging overhead of a Nitro HybridObject that Nitro
|
|
15
|
+
/// does not account for itself. The generated `*Spec_cxx.swift` adds the Swift
|
|
16
|
+
/// implementation instance via `MemoryHelper.getSizeOf`; this covers the rest:
|
|
17
|
+
/// the Swift `_cxx` bridge class, the C++ `HybridObject` with its per-runtime
|
|
18
|
+
/// cache map, the `shared_ptr` control block and the reference-state records
|
|
19
|
+
/// behind the JS weak reference. Add it once, in each proxy's `memorySize`.
|
|
20
|
+
let nitroHybridObjectOverheadBytes = 512
|
|
21
|
+
|
|
22
|
+
/// Rough size of an `NSString` on the heap, including its object header.
|
|
23
|
+
func estimateStringMemorySize(_ value: String?) -> Int {
|
|
24
|
+
guard let value = value else {
|
|
25
|
+
return 0
|
|
26
|
+
}
|
|
27
|
+
return 32 + value.utf16.count * 2
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// Rough size of a `metadata` dictionary as HealthKit keeps it in memory.
|
|
31
|
+
func estimateMetadataMemorySize(_ metadata: [String: Any]?) -> Int {
|
|
32
|
+
guard let metadata = metadata, !metadata.isEmpty else {
|
|
33
|
+
return 0
|
|
34
|
+
}
|
|
35
|
+
// NSDictionary header plus one bucket per entry.
|
|
36
|
+
var total = 64 + metadata.count * 48
|
|
37
|
+
for (key, value) in metadata {
|
|
38
|
+
total += estimateStringMemorySize(key)
|
|
39
|
+
if let string = value as? String {
|
|
40
|
+
total += estimateStringMemorySize(string)
|
|
41
|
+
} else if value is HKQuantity {
|
|
42
|
+
// HKQuantity: object header, unit object, double.
|
|
43
|
+
total += 128
|
|
44
|
+
} else {
|
|
45
|
+
// NSNumber, NSDate and friends are small tagged or boxed objects.
|
|
46
|
+
total += 32
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return total
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Rough size of one `HKStatistics` entry: the object itself plus the
|
|
53
|
+
/// sum/min/max/average/most-recent quantities and their date interval.
|
|
54
|
+
let estimatedStatisticsEntryMemorySize = 512
|
|
55
|
+
|
|
56
|
+
/// Estimate of the `HKWorkout` object graph only; the proxy adds Nitro's
|
|
57
|
+
/// bridging overhead on top.
|
|
58
|
+
func estimateWorkoutMemorySize(_ workout: HKWorkout) -> Int {
|
|
59
|
+
// HKWorkout object, uuid, dates, HKSourceRevision (+HKSource), HKDevice,
|
|
60
|
+
// and the four total quantities.
|
|
61
|
+
var total = 1_024
|
|
62
|
+
total += estimateMetadataMemorySize(workout.metadata)
|
|
63
|
+
|
|
64
|
+
if let events = workout.workoutEvents {
|
|
65
|
+
for event in events {
|
|
66
|
+
// HKWorkoutEvent: object header, type, NSDateInterval.
|
|
67
|
+
total += 128 + estimateMetadataMemorySize(event.metadata)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if #available(iOS 16.0, *) {
|
|
72
|
+
total += workout.allStatistics.count * estimatedStatisticsEntryMemorySize
|
|
73
|
+
|
|
74
|
+
for activity in workout.workoutActivities {
|
|
75
|
+
// HKWorkoutActivity: object header, uuid, dates, HKWorkoutConfiguration.
|
|
76
|
+
total += 512
|
|
77
|
+
total += estimateMetadataMemorySize(activity.metadata)
|
|
78
|
+
total += activity.workoutEvents.count * 128
|
|
79
|
+
total += activity.allStatistics.count * estimatedStatisticsEntryMemorySize
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return total
|
|
84
|
+
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
//
|
|
7
7
|
|
|
8
8
|
import HealthKit
|
|
9
|
+
import ReactNativeHealthkitCore
|
|
9
10
|
|
|
10
11
|
func createSourcePredicate(_ sources: [HybridSourceProxySpec]?) -> NSPredicate? {
|
|
11
12
|
if let sources = sources {
|
|
@@ -147,103 +148,32 @@ func createPredicateForWorkout(_ filter: FilterForWorkouts?) -> NSPredicate? {
|
|
|
147
148
|
return nil
|
|
148
149
|
}
|
|
149
150
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
let strictEndDate = dateFilter.strictEndDate ?? false
|
|
154
|
-
|
|
155
|
-
let options: HKQueryOptions =
|
|
156
|
-
strictStartDate && strictEndDate
|
|
157
|
-
? [.strictStartDate, .strictEndDate]
|
|
158
|
-
: strictEndDate
|
|
159
|
-
? .strictEndDate
|
|
160
|
-
: strictStartDate
|
|
161
|
-
? .strictStartDate
|
|
162
|
-
: []
|
|
163
|
-
|
|
164
|
-
return HKQuery.predicateForSamples(
|
|
165
|
-
withStart: dateFilter.startDate,
|
|
166
|
-
end: dateFilter.endDate,
|
|
167
|
-
options: options
|
|
168
|
-
)
|
|
169
|
-
}
|
|
170
|
-
return nil
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
func createUUIDsPredicate(uuids: [String]?) -> NSPredicate? {
|
|
174
|
-
if let uuids = uuids {
|
|
175
|
-
let uuids = uuids.compactMap { uuidStr -> UUID? in
|
|
176
|
-
do {
|
|
177
|
-
let uuid = try initializeUUID(uuidStr)
|
|
178
|
-
return uuid
|
|
179
|
-
} catch {
|
|
180
|
-
warnWithPrefix(error.localizedDescription)
|
|
181
|
-
return nil
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
return HKQuery.predicateForObjects(with: Set(uuids))
|
|
185
|
-
}
|
|
186
|
-
return nil
|
|
187
|
-
}
|
|
151
|
+
// The generated filter structs adopt the core protocols so the shared
|
|
152
|
+
// predicate builders in ReactNativeHealthkitCore can read them.
|
|
153
|
+
extension DateFilter: DateFilterConvertible {}
|
|
188
154
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
let uuid = try initializeUUID(uuidStr)
|
|
193
|
-
return HKQuery.predicateForObject(with: uuid)
|
|
194
|
-
} catch {
|
|
195
|
-
warnWithPrefix("createUUIDPredicate: \(error.localizedDescription)")
|
|
155
|
+
extension PredicateWithMetadataKey: MetadataPredicateConvertible {
|
|
156
|
+
public var operatorRawValue: Int? {
|
|
157
|
+
guard let operatorType = operatorType else {
|
|
196
158
|
return nil
|
|
197
159
|
}
|
|
160
|
+
return Int(operatorType.rawValue)
|
|
198
161
|
}
|
|
199
|
-
return nil
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
func getComparisonPredicateOperator(_ op: ComparisonPredicateOperator?) -> NSComparisonPredicate
|
|
203
|
-
.Operator? {
|
|
204
|
-
if let rawValue = op?.rawValue {
|
|
205
|
-
if let op = NSComparisonPredicate.Operator.init(rawValue: UInt(rawValue)) {
|
|
206
|
-
return op
|
|
207
|
-
} else {
|
|
208
|
-
warnWithPrefix(
|
|
209
|
-
"getComparisonPredicateOperator: Unsupported operator in metadata filter: \(rawValue)")
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
return nil
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
func createMetadataPredicate(_ metadata: PredicateWithMetadataKey?) -> NSPredicate? {
|
|
216
|
-
if let metadata = metadata {
|
|
217
|
-
|
|
218
|
-
guard let valueVariant = metadata.value else {
|
|
219
|
-
return HKQuery.predicateForObjects(withMetadataKey: metadata.withMetadataKey)
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
let actualValue: Any
|
|
223
162
|
|
|
224
|
-
|
|
163
|
+
public var metadataValue: Any? {
|
|
164
|
+
switch value {
|
|
225
165
|
case .first(let boolValue):
|
|
226
|
-
|
|
166
|
+
return NSNumber(value: boolValue ? 1 : 0)
|
|
227
167
|
case .second(let stringValue):
|
|
228
|
-
|
|
168
|
+
return stringValue
|
|
229
169
|
case .third(let doubleValue):
|
|
230
|
-
|
|
170
|
+
return NSNumber(value: doubleValue)
|
|
231
171
|
case .fourth(let dateValue):
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if let operatorType = metadata.operatorType != nil
|
|
236
|
-
? getComparisonPredicateOperator(metadata.operatorType)
|
|
237
|
-
: .equalTo {
|
|
238
|
-
return HKQuery.predicateForObjects(
|
|
239
|
-
withMetadataKey: metadata.withMetadataKey,
|
|
240
|
-
operatorType: operatorType,
|
|
241
|
-
value: actualValue
|
|
242
|
-
)
|
|
172
|
+
return dateValue
|
|
173
|
+
case nil:
|
|
174
|
+
return nil
|
|
243
175
|
}
|
|
244
|
-
|
|
245
176
|
}
|
|
246
|
-
return nil
|
|
247
177
|
}
|
|
248
178
|
|
|
249
179
|
func createPredicateForSamplesBase(_ filter: FilterForSamplesBase?) -> NSPredicate? {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import HealthKit
|
|
2
2
|
import NitroModules
|
|
3
|
+
import ReactNativeHealthkitCore
|
|
3
4
|
|
|
4
5
|
func emptyStatisticsResponse(from: Date?, to: Date?) -> QueryStatisticsResponse {
|
|
5
6
|
return QueryStatisticsResponse(
|
|
@@ -47,13 +48,13 @@ func queryStatisticsForQuantityInternal(
|
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
healthStore.execute(query)
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
func serializeStatistics(gottenStats: HKStatistics, unit: HKUnit) -> QueryStatisticsResponse {
|
|
55
56
|
let sources = gottenStats.sources?.map { source in
|
|
56
|
-
return
|
|
57
|
+
return serializeSourceStruct(source)
|
|
57
58
|
} ?? []
|
|
58
59
|
|
|
59
60
|
var averageQuantity: Quantity?
|
|
@@ -178,7 +179,7 @@ func queryStatisticsCollectionForQuantityInternal(
|
|
|
178
179
|
}
|
|
179
180
|
}
|
|
180
181
|
|
|
181
|
-
|
|
182
|
+
healthStore.execute(query)
|
|
182
183
|
}
|
|
183
184
|
}
|
|
184
185
|
|
|
@@ -244,7 +245,7 @@ func serializeStatisticsPerSource(gottenStats: HKStatistics, unit: HKUnit)
|
|
|
244
245
|
}
|
|
245
246
|
|
|
246
247
|
return QueryStatisticsResponseFromSingleSource(
|
|
247
|
-
source:
|
|
248
|
+
source: serializeSourceStruct(source),
|
|
248
249
|
startDate: gottenStats.startDate,
|
|
249
250
|
endDate: gottenStats.endDate,
|
|
250
251
|
duration: duration,
|
|
@@ -313,11 +314,13 @@ class QuantityTypeModule: HybridQuantityTypeModuleSpec {
|
|
|
313
314
|
return Promise.async {
|
|
314
315
|
let quantityType = try initializeQuantityType(identifier.stringValue)
|
|
315
316
|
|
|
316
|
-
if let gottenStats = try await
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
317
|
+
if let gottenStats = try await retryingWhileStoreReopens({
|
|
318
|
+
try await queryStatisticsForQuantityInternal(
|
|
319
|
+
quantityType: quantityType,
|
|
320
|
+
statistics: statistics,
|
|
321
|
+
options: options
|
|
322
|
+
)
|
|
323
|
+
}) {
|
|
321
324
|
let unit = try await getUnitToUse(
|
|
322
325
|
unitOverride: options?.unit,
|
|
323
326
|
quantityType: quantityType
|
|
@@ -336,13 +339,15 @@ class QuantityTypeModule: HybridQuantityTypeModuleSpec {
|
|
|
336
339
|
return Promise.async {
|
|
337
340
|
let quantityType = try initializeQuantityType(identifier.stringValue)
|
|
338
341
|
|
|
339
|
-
if let statistics = try await
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
342
|
+
if let statistics = try await retryingWhileStoreReopens({
|
|
343
|
+
try await queryStatisticsCollectionForQuantityInternal(
|
|
344
|
+
quantityType: quantityType,
|
|
345
|
+
statistics: statistics,
|
|
346
|
+
anchorDate: anchorDate,
|
|
347
|
+
intervalComponents: intervalComponents,
|
|
348
|
+
options: options
|
|
349
|
+
)
|
|
350
|
+
}) {
|
|
346
351
|
|
|
347
352
|
let unit = try await getUnitToUse(
|
|
348
353
|
unitOverride: options?.unit,
|
|
@@ -385,11 +390,13 @@ class QuantityTypeModule: HybridQuantityTypeModuleSpec {
|
|
|
385
390
|
return Promise.async {
|
|
386
391
|
let quantityType = try initializeQuantityType(identifier.stringValue)
|
|
387
392
|
|
|
388
|
-
if let gottenStats = try await
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
+
if let gottenStats = try await retryingWhileStoreReopens({
|
|
394
|
+
try await queryStatisticsForQuantityInternal(
|
|
395
|
+
quantityType: quantityType,
|
|
396
|
+
statistics: statistics,
|
|
397
|
+
options: options
|
|
398
|
+
)
|
|
399
|
+
}) {
|
|
393
400
|
let unit = try await getUnitToUse(
|
|
394
401
|
unitOverride: options?.unit,
|
|
395
402
|
quantityType: quantityType
|
|
@@ -412,13 +419,15 @@ class QuantityTypeModule: HybridQuantityTypeModuleSpec {
|
|
|
412
419
|
return Promise.async {
|
|
413
420
|
let quantityType = try initializeQuantityType(identifier.stringValue)
|
|
414
421
|
|
|
415
|
-
if let statistics = try await
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
+
if let statistics = try await retryingWhileStoreReopens({
|
|
423
|
+
try await queryStatisticsCollectionForQuantityInternal(
|
|
424
|
+
quantityType: quantityType,
|
|
425
|
+
statistics: statistics,
|
|
426
|
+
anchorDate: anchorDate,
|
|
427
|
+
intervalComponents: intervalComponents,
|
|
428
|
+
options: options
|
|
429
|
+
)
|
|
430
|
+
}) {
|
|
422
431
|
let unit = try await getUnitToUse(
|
|
423
432
|
unitOverride: options?.unit,
|
|
424
433
|
quantityType: quantityType
|
package/ios/Serializers.swift
CHANGED
|
@@ -8,12 +8,7 @@
|
|
|
8
8
|
import Foundation
|
|
9
9
|
import HealthKit
|
|
10
10
|
import NitroModules
|
|
11
|
-
|
|
12
|
-
private let metadataDateFormatter: ISO8601DateFormatter = {
|
|
13
|
-
let formatter = ISO8601DateFormatter()
|
|
14
|
-
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
|
15
|
-
return formatter
|
|
16
|
-
}()
|
|
11
|
+
import ReactNativeHealthkitCore
|
|
17
12
|
|
|
18
13
|
func serializeQuantityTyped(unit: HKUnit, quantityNullable: HKQuantity?) -> Quantity? {
|
|
19
14
|
guard let q = quantityNullable else {
|
|
@@ -43,10 +38,10 @@ func serializeQuantitySample(sample: HKQuantitySample, unit: HKUnit) throws -> Q
|
|
|
43
38
|
startDate: sample.startDate,
|
|
44
39
|
endDate: sample.endDate,
|
|
45
40
|
hasUndeterminedDuration: sample.hasUndeterminedDuration,
|
|
46
|
-
metadata: serializeMetadata(sample.metadata),
|
|
47
41
|
uuid: sample.uuid.uuidString,
|
|
48
42
|
sourceRevision: serializeSourceRevision(sample.sourceRevision),
|
|
49
|
-
device: serializeDevice(hkDevice: sample.device)
|
|
43
|
+
device: serializeDevice(hkDevice: sample.device),
|
|
44
|
+
metadata: serializeMetadata(sample.metadata)
|
|
50
45
|
)
|
|
51
46
|
}
|
|
52
47
|
throw runtimeErrorWithPrefix(
|
|
@@ -62,25 +57,38 @@ func serializeDeletedSample(sample: HKDeletedObject) -> DeletedSample {
|
|
|
62
57
|
|
|
63
58
|
func serializeCategorySample(sample: HKCategorySample) -> CategorySample {
|
|
64
59
|
return CategorySample(
|
|
65
|
-
categoryType: CategoryTypeIdentifier(
|
|
60
|
+
categoryType: CategoryTypeIdentifier(
|
|
61
|
+
fromString: categoryTypeIdentifierName(
|
|
62
|
+
fromHealthKitIdentifier: sample.categoryType.identifier))!,
|
|
66
63
|
value: Double(sample.value),
|
|
67
64
|
sampleType: serializeSampleType(sample.sampleType),
|
|
68
65
|
startDate: sample.startDate,
|
|
69
66
|
endDate: sample.endDate,
|
|
70
67
|
hasUndeterminedDuration: sample.hasUndeterminedDuration,
|
|
71
|
-
metadata: serializeMetadata(sample.metadata),
|
|
72
68
|
uuid: sample.uuid.uuidString,
|
|
73
69
|
sourceRevision: serializeSourceRevision(sample.sourceRevision),
|
|
74
|
-
device: serializeDevice(hkDevice: sample.device)
|
|
70
|
+
device: serializeDevice(hkDevice: sample.device),
|
|
71
|
+
metadata: serializeMetadata(sample.metadata)
|
|
75
72
|
)
|
|
76
73
|
}
|
|
77
74
|
|
|
75
|
+
/// Wraps an `HKSource` in a proxy HybridObject. Only use this where the
|
|
76
|
+
/// caller explicitly asks for a source handle (`querySources`,
|
|
77
|
+
/// `currentAppSource`); everything embedded in serialized values should use
|
|
78
|
+
/// `serializeSourceStruct` so that no per-sample native object is allocated.
|
|
78
79
|
func serializeSource(_ source: HKSource) -> SourceProxy {
|
|
79
80
|
return SourceProxy(
|
|
80
81
|
source: source
|
|
81
82
|
)
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
func serializeSourceStruct(_ source: HKSource) -> Source {
|
|
86
|
+
return Source(
|
|
87
|
+
name: source.name,
|
|
88
|
+
bundleIdentifier: source.bundleIdentifier
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
84
92
|
func serializeUnknownQuantityTyped(quantity: HKQuantity?) -> Quantity? {
|
|
85
93
|
guard let quantity = quantity else {
|
|
86
94
|
return nil
|
|
@@ -210,59 +218,19 @@ func serializeUnknownQuantity(quantity: HKQuantity) -> [String: AnyValue]? {
|
|
|
210
218
|
return nil
|
|
211
219
|
}
|
|
212
220
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
serialized.setBoolean(key: item.key, value: number.boolValue)
|
|
220
|
-
} else if isKnownNumericMetadataKey(item.key) {
|
|
221
|
-
serialized.setDouble(key: item.key, value: number.doubleValue)
|
|
222
|
-
} else if CFGetTypeID(number) == CFBooleanGetTypeID() {
|
|
223
|
-
serialized.setBoolean(key: item.key, value: number.boolValue)
|
|
224
|
-
} else {
|
|
225
|
-
serialized.setDouble(key: item.key, value: number.doubleValue)
|
|
226
|
-
}
|
|
227
|
-
continue
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if let bool = item.value as? Bool {
|
|
231
|
-
serialized.setBoolean(key: item.key, value: bool)
|
|
232
|
-
continue
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if let str = item.value as? String {
|
|
236
|
-
serialized.setString(key: item.key, value: str)
|
|
237
|
-
continue
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if let double = item.value as? Double {
|
|
241
|
-
serialized.setDouble(key: item.key, value: double)
|
|
242
|
-
continue
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if let date = item.value as? Date {
|
|
246
|
-
serialized.setString(
|
|
247
|
-
key: item.key,
|
|
248
|
-
value: metadataDateFormatter.string(from: date)
|
|
249
|
-
)
|
|
250
|
-
continue
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if let quantity = item.value as? HKQuantity {
|
|
254
|
-
if let s = serializeUnknownQuantity(quantity: quantity) {
|
|
255
|
-
serialized.setObject(key: item.key, value: s)
|
|
256
|
-
}
|
|
257
|
-
continue
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if let dict = item.value as? [String: AnyValue] {
|
|
261
|
-
serialized.setObject(key: item.key, value: dict)
|
|
262
|
-
}
|
|
221
|
+
private let metadataSerializationOptions = MetadataSerializationOptions(
|
|
222
|
+
isBooleanKey: isKnownBooleanMetadataKey,
|
|
223
|
+
isNumericKey: isKnownNumericMetadataKey,
|
|
224
|
+
serializeQuantity: { quantity in
|
|
225
|
+
if let serialized = serializeUnknownQuantity(quantity: quantity) {
|
|
226
|
+
return .object(serialized)
|
|
263
227
|
}
|
|
228
|
+
return nil
|
|
264
229
|
}
|
|
265
|
-
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
func serializeMetadata(_ metadata: [String: Any]?) -> AnyMap {
|
|
233
|
+
return ReactNativeHealthkitCore.serializeMetadata(metadata, options: metadataSerializationOptions)
|
|
266
234
|
}
|
|
267
235
|
|
|
268
236
|
func serializeDevice(hkDevice: HKDevice?) -> Device? {
|
|
@@ -282,15 +250,9 @@ func serializeDevice(hkDevice: HKDevice?) -> Device? {
|
|
|
282
250
|
)
|
|
283
251
|
}
|
|
284
252
|
|
|
285
|
-
func serializeOperatingSystemVersion(_ version: OperatingSystemVersion) -> String {
|
|
286
|
-
let versionString = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)"
|
|
287
|
-
|
|
288
|
-
return versionString
|
|
289
|
-
}
|
|
290
|
-
|
|
291
253
|
func serializeSourceRevision(_ hkSourceRevision: HKSourceRevision) -> SourceRevision {
|
|
292
254
|
return SourceRevision(
|
|
293
|
-
source:
|
|
255
|
+
source: serializeSourceStruct(hkSourceRevision.source),
|
|
294
256
|
version: hkSourceRevision.version,
|
|
295
257
|
operatingSystemVersion: serializeOperatingSystemVersion(
|
|
296
258
|
hkSourceRevision.operatingSystemVersion),
|
|
@@ -300,7 +262,9 @@ func serializeSourceRevision(_ hkSourceRevision: HKSourceRevision) -> SourceRevi
|
|
|
300
262
|
|
|
301
263
|
func serializeSampleType(_ sampleType: HKSampleType) -> SampleType {
|
|
302
264
|
return SampleType(
|
|
303
|
-
identifier: sampleType
|
|
265
|
+
identifier: sampleType is HKCategoryType
|
|
266
|
+
? categoryTypeIdentifierName(fromHealthKitIdentifier: sampleType.identifier)
|
|
267
|
+
: sampleType.identifier,
|
|
304
268
|
allowsRecalibrationForEstimates: sampleType.allowsRecalibrationForEstimates,
|
|
305
269
|
isMinimumDurationRestricted: sampleType.isMinimumDurationRestricted,
|
|
306
270
|
isMaximumDurationRestricted: sampleType.isMaximumDurationRestricted
|
package/ios/SourceProxy.swift
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import HealthKit
|
|
9
9
|
import NitroModules
|
|
10
|
+
import ReactNativeHealthkitCore
|
|
10
11
|
|
|
11
12
|
class SourceProxy: HybridSourceProxySpec {
|
|
12
13
|
func toJSON(key: String?) throws -> Source {
|
|
@@ -14,11 +15,7 @@ class SourceProxy: HybridSourceProxySpec {
|
|
|
14
15
|
warnWithPrefix("SourceProxy does not support toJSON with key: \(key!)")
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
return
|
|
18
|
-
name: self.name,
|
|
19
|
-
bundleIdentifier: self.bundleIdentifier
|
|
20
|
-
)
|
|
21
|
-
|
|
18
|
+
return serializeSourceStruct(source)
|
|
22
19
|
}
|
|
23
20
|
|
|
24
21
|
let source: HKSource
|
|
@@ -31,5 +28,18 @@ class SourceProxy: HybridSourceProxySpec {
|
|
|
31
28
|
self.source = source
|
|
32
29
|
self.name = source.name
|
|
33
30
|
self.bundleIdentifier = source.bundleIdentifier
|
|
31
|
+
// HKSource object with its own name and bundle identifier strings, plus
|
|
32
|
+
// the Swift copies of those strings. Computed once; Nitro reads it on
|
|
33
|
+
// every conversion to JS.
|
|
34
|
+
self.memorySize =
|
|
35
|
+
128
|
|
36
|
+
+ 2 * estimateStringMemorySize(source.name)
|
|
37
|
+
+ 2 * estimateStringMemorySize(source.bundleIdentifier)
|
|
38
|
+
+ nitroHybridObjectOverheadBytes
|
|
34
39
|
}
|
|
40
|
+
|
|
41
|
+
/// Estimated heap footprint of the wrapped `HKSource`, reported to Nitro so
|
|
42
|
+
/// the JS garbage collector accounts for the native memory held by each
|
|
43
|
+
/// proxy. Nitro adds the Swift instance's own size on top.
|
|
44
|
+
let memorySize: Int
|
|
35
45
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import HealthKit
|
|
2
2
|
import NitroModules
|
|
3
|
+
import ReactNativeHealthkitCore
|
|
3
4
|
|
|
4
5
|
#if compiler(>=6)
|
|
5
6
|
|
|
@@ -40,10 +41,10 @@ import NitroModules
|
|
|
40
41
|
startDate: sample.startDate,
|
|
41
42
|
endDate: sample.endDate,
|
|
42
43
|
hasUndeterminedDuration: sample.hasUndeterminedDuration,
|
|
43
|
-
metadata: serializeMetadata(sample.metadata),
|
|
44
44
|
uuid: sample.uuid.uuidString,
|
|
45
45
|
sourceRevision: serializeSourceRevision(sample.sourceRevision),
|
|
46
|
-
device: serializeDevice(hkDevice: sample.device)
|
|
46
|
+
device: serializeDevice(hkDevice: sample.device),
|
|
47
|
+
metadata: serializeMetadata(sample.metadata)
|
|
47
48
|
)
|
|
48
49
|
}
|
|
49
50
|
|
package/ios/WorkoutProxy.swift
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import CoreLocation
|
|
9
9
|
import HealthKit
|
|
10
10
|
import NitroModules
|
|
11
|
+
import ReactNativeHealthkitCore
|
|
11
12
|
|
|
12
13
|
@available(iOS 17.0.0, *)
|
|
13
14
|
func getWorkoutPlanInternal(workout: HKWorkout) async throws -> WorkoutPlan? {
|
|
@@ -49,38 +50,43 @@ func getWorkoutRoutesInternal(
|
|
|
49
50
|
|
|
50
51
|
func getRouteLocations(
|
|
51
52
|
route: HKWorkoutRoute
|
|
52
|
-
) async -> [CLLocation] {
|
|
53
|
-
|
|
53
|
+
) async throws -> [CLLocation] {
|
|
54
|
+
return try await withCheckedThrowingContinuation {
|
|
54
55
|
(continuation: CheckedContinuation<[CLLocation], Error>) in
|
|
55
56
|
var allLocations: [CLLocation] = []
|
|
57
|
+
// HealthKit streams locations in batches through the same handler; make
|
|
58
|
+
// sure the continuation is resumed exactly once whatever the sequence.
|
|
59
|
+
var hasFinished = false
|
|
60
|
+
let finish = { (result: Result<[CLLocation], Error>) in
|
|
61
|
+
guard !hasFinished else {
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
hasFinished = true
|
|
65
|
+
continuation.resume(with: result)
|
|
66
|
+
}
|
|
56
67
|
|
|
57
68
|
let query = HKWorkoutRouteQuery(route: route) {
|
|
58
69
|
(_, locationsOrNil, done, errorOrNil) in
|
|
59
70
|
|
|
60
71
|
DispatchQueue.main.async {
|
|
61
72
|
if let error = errorOrNil {
|
|
62
|
-
|
|
63
|
-
return
|
|
73
|
+
return finish(.failure(error))
|
|
64
74
|
}
|
|
65
75
|
|
|
66
76
|
guard let currentLocationBatch = locationsOrNil else {
|
|
67
|
-
return
|
|
68
|
-
throwing: runtimeErrorWithPrefix("Unexpected empty response")
|
|
69
|
-
)
|
|
77
|
+
return finish(.failure(runtimeErrorWithPrefix("Unexpected empty response")))
|
|
70
78
|
}
|
|
71
79
|
|
|
72
80
|
allLocations.append(contentsOf: currentLocationBatch)
|
|
73
81
|
|
|
74
82
|
if done {
|
|
75
|
-
|
|
83
|
+
finish(.success(allLocations))
|
|
76
84
|
}
|
|
77
85
|
}
|
|
78
86
|
}
|
|
79
87
|
|
|
80
|
-
|
|
88
|
+
healthStore.execute(query)
|
|
81
89
|
}
|
|
82
|
-
|
|
83
|
-
return locations
|
|
84
90
|
}
|
|
85
91
|
|
|
86
92
|
func serializeLocation(location: CLLocation, previousLocation: CLLocation?)
|
|
@@ -122,7 +128,7 @@ func getSerializedWorkoutLocations(
|
|
|
122
128
|
route.metadata
|
|
123
129
|
)
|
|
124
130
|
|
|
125
|
-
let routeCLLocations = await getRouteLocations(
|
|
131
|
+
let routeCLLocations = try await getRouteLocations(
|
|
126
132
|
route: route
|
|
127
133
|
)
|
|
128
134
|
|
|
@@ -161,7 +167,7 @@ func saveWorkoutRouteInternal(
|
|
|
161
167
|
// create CLLocations and return if locations are empty
|
|
162
168
|
let clLocations = mapLocations(from: locations)
|
|
163
169
|
let routeBuilder = HKWorkoutRouteBuilder(
|
|
164
|
-
healthStore:
|
|
170
|
+
healthStore: healthStore,
|
|
165
171
|
device: nil
|
|
166
172
|
)
|
|
167
173
|
try await routeBuilder.insertRouteData(clLocations)
|
|
@@ -397,8 +403,20 @@ class WorkoutProxy: HybridWorkoutProxySpec {
|
|
|
397
403
|
|
|
398
404
|
init(workout: HKWorkout) {
|
|
399
405
|
self.workout = workout
|
|
406
|
+
// Computed here rather than lazily: proxies are created inside
|
|
407
|
+
// `Promise.async` on a background executor, while Nitro reads `memorySize`
|
|
408
|
+
// on the JS thread every time the proxy is converted to a JS object.
|
|
409
|
+
self.memorySize = estimateWorkoutMemorySize(workout) + nitroHybridObjectOverheadBytes
|
|
400
410
|
}
|
|
401
411
|
|
|
412
|
+
/// Estimated heap footprint of the wrapped `HKWorkout` graph (events,
|
|
413
|
+
/// activities, statistics, metadata), reported to Nitro so the JS garbage
|
|
414
|
+
/// collector sees the native memory kept alive by each proxy. Nitro adds
|
|
415
|
+
/// the Swift instance's own size on top. Rough by design: the goal is the
|
|
416
|
+
/// right order of magnitude per workout, so hundreds of proxies from a query
|
|
417
|
+
/// do not look like hundreds of empty objects.
|
|
418
|
+
let memorySize: Int
|
|
419
|
+
|
|
402
420
|
func getWorkoutPlan() throws -> Promise<WorkoutPlan?> {
|
|
403
421
|
return Promise.async {
|
|
404
422
|
if #available(iOS 17.0.0, *) {
|