sensorkit
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSensorKit
SensorKit
Collect research-grade sensor data from iOS and watchOS devices for approved
research studies. SensorKit provides access to ambient light, motion, device
usage, keyboard metrics, visits, phone/messaging usage, speech metrics, face
metrics, wrist temperature, heart rate, ECG, and PPG data. Targets
Swift 6.3 / iOS 26+.
SensorKit is restricted to Apple-approved research studies. Apps must submit
a research proposal to Apple and receive the
entitlement before any sensor data is accessible. This is not a general-purpose
sensor API -- use CoreMotion for standard accelerometer/gyroscope needs.
com.apple.developer.sensorkit.reader.allow为获批的研究项目从iOS和watchOS设备收集研究级传感器数据。SensorKit支持访问环境光照、运动、设备使用、键盘指标、到访地点、电话/信息使用、语音指标、面部指标、腕部温度、心率、ECG和PPG数据。适配Swift 6.3 / iOS 26+。
SensorKit仅可用于苹果获批的研究项目。 应用必须先向苹果提交研究提案,获得 entitlement之后才能访问任何传感器数据。这不是通用的传感器API——如果需要使用标准的加速度计/陀螺仪功能,请使用CoreMotion。
com.apple.developer.sensorkit.reader.allowContents
目录
Overview and Requirements
概述与要求
SensorKit enables research apps to record and fetch sensor data across iPhone
and Apple Watch. The framework requires:
- Apple-approved research study -- submit a proposal at researchandcare.org.
- SensorKit entitlement -- Apple grants only for approved studies.
com.apple.developer.sensorkit.reader.allow - Manual provisioning profile -- Xcode requires an explicit App ID with the SensorKit capability enabled.
- User authorization -- the system presents a Research Sensor & Usage Data sheet that users approve per-sensor.
- 24-hour data hold -- newly recorded data is inaccessible for 24 hours, giving users time to delete data they do not want to share.
An app can access up to 7 days of prior recorded data for an active sensor.
SensorKit支持研究类应用在iPhone和Apple Watch上录制和拉取传感器数据。该框架要求:
- 苹果获批的研究项目——在researchandcare.org提交申请。
- SensorKit entitlement——苹果仅会为获批的研究项目授予权限。
com.apple.developer.sensorkit.reader.allow - 手动配置描述文件——Xcode需要启用了SensorKit能力的显式App ID。
- 用户授权——系统会展示研究传感器与使用数据弹窗,用户需要为每个传感器单独授权。
- 24小时数据保留期——新录制的数据在24小时内无法访问,为用户提供时间删除不愿意共享的数据。
对于处于活跃状态的传感器,应用最多可以访问过去7天的录制数据。
Entitlements
Entitlements
Add the SensorKit reader entitlement to a file. List only the
sensors your study uses:
.entitlementsxml
<key>com.apple.developer.sensorkit.reader.allow</key>
<array>
<string>ambient-light-sensor</string>
<string>motion-accelerometer</string>
<string>motion-rotation-rate</string>
<string>device-usage</string>
<string>keyboard-metrics</string>
<string>messages-usage</string>
<string>phone-usage</string>
<string>visits</string>
<string>pedometer</string>
<string>on-wrist</string>
</array>Xcode build settings for manual signing:
| Setting | Value |
|---|---|
| Code Signing Entitlements | |
| Code Signing Identity | |
| Code Signing Style | |
| Provisioning Profile | Explicit profile with SensorKit capability |
将SensorKit读取权限添加到文件中,仅列出你的研究项目用到的传感器:
.entitlementsxml
<key>com.apple.developer.sensorkit.reader.allow</key>
<array>
<string>ambient-light-sensor</string>
<string>motion-accelerometer</string>
<string>motion-rotation-rate</string>
<string>device-usage</string>
<string>keyboard-metrics</string>
<string>messages-usage</string>
<string>phone-usage</string>
<string>visits</string>
<string>pedometer</string>
<string>on-wrist</string>
</array>手动签名的Xcode构建设置:
| 设置项 | 值 |
|---|---|
| Code Signing Entitlements | |
| Code Signing Identity | |
| Code Signing Style | |
| Provisioning Profile | 具备SensorKit能力的显式描述文件 |
Info.plist Configuration
Info.plist 配置
Three keys are required:
xml
<!-- Study purpose shown in the authorization sheet -->
<key>NSSensorKitUsageDescription</key>
<string>This study monitors activity patterns for sleep research.</string>
<!-- Link to your study's privacy policy -->
<key>NSSensorKitPrivacyPolicyURL</key>
<string>https://example.com/privacy-policy</string>
<!-- Per-sensor usage explanations -->
<key>NSSensorKitUsageDetail</key>
<dict>
<key>SRSensorUsageMotion</key>
<dict>
<key>Description</key>
<string>Measures physical activity levels during the study.</string>
<key>Required</key>
<true/>
</dict>
<key>SRSensorUsageAmbientLightSensor</key>
<dict>
<key>Description</key>
<string>Records ambient light to assess sleep environment.</string>
</dict>
</dict>If is and the user denies that sensor, the system warns them
that the study needs it and offers a chance to reconsider.
Requiredtrue需要配置三个键:
xml
<!-- 授权弹窗中展示的研究用途说明 -->
<key>NSSensorKitUsageDescription</key>
<string>本研究为睡眠研究监测活动模式。</string>
<!-- 研究项目隐私政策链接 -->
<key>NSSensorKitPrivacyPolicyURL</key>
<string>https://example.com/privacy-policy</string>
<!-- 每个传感器的用途说明 -->
<key>NSSensorKitUsageDetail</key>
<dict>
<key>SRSensorUsageMotion</key>
<dict>
<key>Description</key>
<string>测量研究期间的身体活动水平。</string>
<key>Required</key>
<true/>
</dict>
<key>SRSensorUsageAmbientLightSensor</key>
<dict>
<key>Description</key>
<string>记录环境光照以评估睡眠环境。</string>
</dict>
</dict>如果设置为且用户拒绝了该传感器的授权,系统会提醒用户该研究需要此权限,并提供重新考虑的机会。
RequiredtrueAuthorization
授权
Request authorization for the sensors your study needs. The system shows the
Research Sensor & Usage Data sheet on first request.
swift
import SensorKit
let reader = SRSensorReader(sensor: .ambientLightSensor)
// Request authorization for multiple sensors at once
SRSensorReader.requestAuthorization(
sensors: [.ambientLightSensor, .accelerometer, .keyboardMetrics]
) { error in
if let error {
print("Authorization request failed: \(error)")
}
}Check a reader's current status before recording:
swift
switch reader.authorizationStatus {
case .authorized:
reader.startRecording()
case .denied:
// User declined -- direct to Settings > Privacy > Research Sensor & Usage Data
break
case .notDetermined:
// Request authorization first
break
@unknown default:
break
}Monitor status changes through the delegate:
swift
func sensorReader(_ reader: SRSensorReader, didChange authorizationStatus: SRAuthorizationStatus) {
switch authorizationStatus {
case .authorized:
reader.startRecording()
case .denied:
reader.stopRecording()
default:
break
}
}为你的研究项目所需的传感器申请授权。首次申请时系统会展示研究传感器与使用数据弹窗。
swift
import SensorKit
let reader = SRSensorReader(sensor: .ambientLightSensor)
// 一次性为多个传感器申请授权
SRSensorReader.requestAuthorization(
sensors: [.ambientLightSensor, .accelerometer, .keyboardMetrics]
) { error in
if let error {
print("授权申请失败: \(error)")
}
}在开始录制前检查读取器的当前状态:
swift
switch reader.authorizationStatus {
case .authorized:
reader.startRecording()
case .denied:
// 用户拒绝授权——引导用户前往设置 > 隐私 > 研究传感器与使用数据
break
case .notDetermined:
// 先申请授权
break
@unknown default:
break
}通过代理监听状态变化:
swift
func sensorReader(_ reader: SRSensorReader, didChange authorizationStatus: SRAuthorizationStatus) {
switch authorizationStatus {
case .authorized:
reader.startRecording()
case .denied:
reader.stopRecording()
default:
break
}
}Available Sensors
可用传感器
Device Sensors
设备传感器
| Sensor | Type | Sample Type |
|---|---|---|
| Device usage | |
| Keyboard activity | |
| Watch wrist state | |
| 传感器 | 类型 | 样本类型 |
|---|---|---|
| 设备使用 | |
| 键盘活动 | |
| 手表佩戴状态 | |
App Activity Sensors
应用活动传感器
| Sensor | Type | Sample Type |
|---|---|---|
| Messages app usage | |
| Phone call usage | |
| 传感器 | 类型 | 样本类型 |
|---|---|---|
| 信息应用使用 | |
| 通话使用 | |
User Activity Sensors
用户活动传感器
| Sensor | Type | Sample Type |
|---|---|---|
| Acceleration data | |
| Rotation rate | |
| Step/distance data | |
| Visited locations | |
| Media interactions | |
| Face expressions | |
| Heart rate | Heart rate data |
| Speed/slope | Odometer data |
| Siri speech | |
| Phone speech | |
| Wrist temp (sleep) | |
| PPG stream | |
| ECG stream | |
| 传感器 | 类型 | 样本类型 |
|---|---|---|
| 加速度数据 | |
| 旋转速率 | |
| 步数/距离数据 | |
| 到访地点 | |
| 媒体交互 | |
| 面部表情 | |
| 心率 | 心率数据 |
| 速度/坡度 | 里程计数据 |
| Siri语音 | |
| 通话语音 | |
| 腕部温度(睡眠) | |
| PPG流 | |
| ECG流 | |
Environment Sensors
环境传感器
| Sensor | Type | Sample Type |
|---|---|---|
| Ambient light | |
| Pressure/temp | Pressure data |
| 传感器 | 类型 | 样本类型 |
|---|---|---|
| 环境光照 | |
| 气压/温度 | 气压数据 |
SRSensorReader
SRSensorReader
SRSensorReaderswift
import SensorKit
// Create a reader for one sensor
let lightReader = SRSensorReader(sensor: .ambientLightSensor)
let keyboardReader = SRSensorReader(sensor: .keyboardMetrics)
// Assign delegate to receive callbacks
lightReader.delegate = self
keyboardReader.delegate = selfThe reader communicates entirely through :
SRSensorReaderDelegate| Delegate Method | Purpose |
|---|---|
| Authorization status changed |
| Recording is about to start |
| Recording failed to start |
| Recording stopped |
| Devices fetched |
| Sample received |
| Fetch completed |
| Fetch failed |
SRSensorReaderswift
import SensorKit
// 为单个传感器创建读取器
let lightReader = SRSensorReader(sensor: .ambientLightSensor)
let keyboardReader = SRSensorReader(sensor: .keyboardMetrics)
// 指派代理接收回调
lightReader.delegate = self
keyboardReader.delegate = self读取器完全通过进行通信:
SRSensorReaderDelegate| 代理方法 | 用途 |
|---|---|
| 授权状态发生变化 |
| 即将开始录制 |
| 录制启动失败 |
| 录制已停止 |
| 已拉取到设备列表 |
| 收到样本数据 |
| 拉取完成 |
| 拉取失败 |
Recording and Fetching Data
数据录制与拉取
Start and Stop Recording
启动与停止录制
swift
// Begin recording -- sensor stays active as long as any app has a stake
reader.startRecording()
// Stop recording -- framework deactivates the sensor when
// no app or system process is using it
reader.stopRecording()swift
// 开始录制——只要有任意应用在使用该传感器,它就会保持活跃状态
reader.startRecording()
// 停止录制——当没有任何应用或系统进程使用该传感器时,框架会停用该传感器
reader.stopRecording()Fetch Data
拉取数据
Build an with a time range and target device, then pass it to
the reader:
SRFetchRequestswift
let request = SRFetchRequest()
request.device = SRDevice.current
request.from = SRAbsoluteTime(CFAbsoluteTimeGetCurrent() - 86400 * 2) // 2 days ago
request.to = SRAbsoluteTime.current()
reader.fetch(request)Receive results through the delegate:
swift
func sensorReader(
_ reader: SRSensorReader,
fetching request: SRFetchRequest,
didFetchResult result: SRFetchResult<AnyObject>
) -> Bool {
let timestamp = result.timestamp
switch reader.sensor {
case .ambientLightSensor:
if let sample = result.sample as? SRAmbientLightSample {
let lux = sample.lux
let chromaticity = sample.chromaticity
let placement = sample.placement
processSample(lux: lux, chromaticity: chromaticity, at: timestamp)
}
case .keyboardMetrics:
if let sample = result.sample as? SRKeyboardMetrics {
let words = sample.totalWords
let speed = sample.typingSpeed
processKeyboard(words: words, speed: speed, at: timestamp)
}
case .deviceUsageReport:
if let sample = result.sample as? SRDeviceUsageReport {
let wakes = sample.totalScreenWakes
let unlocks = sample.totalUnlocks
processUsage(wakes: wakes, unlocks: unlocks, at: timestamp)
}
default:
break
}
return true // Return true to continue receiving results
}
func sensorReader(_ reader: SRSensorReader, didCompleteFetch request: SRFetchRequest) {
print("Fetch complete for \(reader.sensor)")
}
func sensorReader(
_ reader: SRSensorReader,
fetching request: SRFetchRequest,
failedWithError error: any Error
) {
print("Fetch failed: \(error)")
}创建包含时间范围和目标设备的,然后将其传递给读取器:
SRFetchRequestswift
let request = SRFetchRequest()
request.device = SRDevice.current
request.from = SRAbsoluteTime(CFAbsoluteTimeGetCurrent() - 86400 * 2) // 2天前
request.to = SRAbsoluteTime.current()
reader.fetch(request)通过代理接收结果:
swift
func sensorReader(
_ reader: SRSensorReader,
fetching request: SRFetchRequest,
didFetchResult result: SRFetchResult<AnyObject>
) -> Bool {
let timestamp = result.timestamp
switch reader.sensor {
case .ambientLightSensor:
if let sample = result.sample as? SRAmbientLightSample {
let lux = sample.lux
let chromaticity = sample.chromaticity
let placement = sample.placement
processSample(lux: lux, chromaticity: chromaticity, at: timestamp)
}
case .keyboardMetrics:
if let sample = result.sample as? SRKeyboardMetrics {
let words = sample.totalWords
let speed = sample.typingSpeed
processKeyboard(words: words, speed: speed, at: timestamp)
}
case .deviceUsageReport:
if let sample = result.sample as? SRDeviceUsageReport {
let wakes = sample.totalScreenWakes
let unlocks = sample.totalUnlocks
processUsage(wakes: wakes, unlocks: unlocks, at: timestamp)
}
default:
break
}
return true // 返回true继续接收结果
}
func sensorReader(_ reader: SRSensorReader, didCompleteFetch request: SRFetchRequest) {
print("\(reader.sensor)的数据拉取完成")
}
func sensorReader(
_ reader: SRSensorReader,
fetching request: SRFetchRequest,
failedWithError error: any Error
) {
print("拉取失败: \(error)")
}Data Holding Period
数据保留期
SensorKit imposes a 24-hour holding period on newly recorded data. Fetch
requests whose time range overlaps this period return no results. Design data
collection workflows around this delay.
SensorKit对新录制的数据强制执行24小时保留期。时间范围覆盖该时段的拉取请求不会返回任何结果。请围绕该延迟设计数据收集工作流。
SRDevice
SRDevice
SRDeviceswift
// Get the current device
let currentDevice = SRDevice.current
print("Model: \(currentDevice.model)")
print("System: \(currentDevice.systemName) \(currentDevice.systemVersion)")
// Fetch all available devices for a sensor
reader.fetchDevices()Handle fetched devices through the delegate:
swift
func sensorReader(_ reader: SRSensorReader, didFetch devices: [SRDevice]) {
for device in devices {
let request = SRFetchRequest()
request.device = device
request.from = SRAbsoluteTime(CFAbsoluteTimeGetCurrent() - 86400)
request.to = SRAbsoluteTime.current()
reader.fetch(request)
}
}
func sensorReader(_ reader: SRSensorReader, fetchDevicesDidFailWithError error: any Error) {
print("Failed to fetch devices: \(error)")
}SRDeviceswift
// 获取当前设备
let currentDevice = SRDevice.current
print("型号: \(currentDevice.model)")
print("系统: \(currentDevice.systemName) \(currentDevice.systemVersion)")
// 拉取某个传感器的所有可用设备
reader.fetchDevices()通过代理处理拉取到的设备:
swift
func sensorReader(_ reader: SRSensorReader, didFetch devices: [SRDevice]) {
for device in devices {
let request = SRFetchRequest()
request.device = device
request.from = SRAbsoluteTime(CFAbsoluteTimeGetCurrent() - 86400)
request.to = SRAbsoluteTime.current()
reader.fetch(request)
}
}
func sensorReader(_ reader: SRSensorReader, fetchDevicesDidFailWithError error: any Error) {
print("拉取设备失败: \(error)")
}SRDevice Properties
SRDevice 属性
| Property | Type | Description |
|---|---|---|
| | User-defined device name |
| | Framework-defined device name |
| | OS name (iOS, watchOS) |
| | OS version |
| | Hardware identifier |
| | Class property for the running device |
| 属性 | 类型 | 描述 |
|---|---|---|
| | 用户自定义的设备名称 |
| | 框架定义的设备名称 |
| | 操作系统名称(iOS、watchOS) |
| | 操作系统版本 |
| | 硬件标识符 |
| | 指向当前运行设备的类属性 |
Common Mistakes
常见错误
DON'T: Attempt to use SensorKit without the entitlement
不要:未获取entitlement就尝试使用SensorKit
swift
// WRONG -- fails at runtime with SRError.invalidEntitlement
let reader = SRSensorReader(sensor: .ambientLightSensor)
reader.startRecording()
// CORRECT -- obtain entitlement from Apple first, configure manual
// provisioning profile, then use SensorKitswift
// 错误——运行时会抛出SRError.invalidEntitlement错误
let reader = SRSensorReader(sensor: .ambientLightSensor)
reader.startRecording()
// 正确——先从苹果获取entitlement,配置手动描述文件,再使用SensorKitDON'T: Expect immediate data access
不要:期望可以立即访问数据
swift
// WRONG -- fetching data recorded moments ago returns nothing
reader.startRecording()
// ... record for a few minutes ...
let request = SRFetchRequest()
request.from = SRAbsoluteTime(CFAbsoluteTimeGetCurrent() - 300)
request.to = SRAbsoluteTime.current()
reader.fetch(request) // Empty results due to 24-hour hold
// CORRECT -- fetch data that is at least 24 hours old
request.from = SRAbsoluteTime(CFAbsoluteTimeGetCurrent() - 86400 * 3)
request.to = SRAbsoluteTime(CFAbsoluteTimeGetCurrent() - 86400)
reader.fetch(request)swift
// 错误——拉取刚刚录制的数据不会返回任何结果
reader.startRecording()
// ... 录制几分钟 ...
let request = SRFetchRequest()
request.from = SRAbsoluteTime(CFAbsoluteTimeGetCurrent() - 300)
request.to = SRAbsoluteTime.current()
reader.fetch(request) // 由于24小时保留期,结果为空
// 正确——拉取至少24小时之前的数据
request.from = SRAbsoluteTime(CFAbsoluteTimeGetCurrent() - 86400 * 3)
request.to = SRAbsoluteTime(CFAbsoluteTimeGetCurrent() - 86400)
reader.fetch(request)DON'T: Forget to set the delegate before fetching
不要:拉取前忘记设置代理
swift
// WRONG -- no delegate means no callbacks, results are silently lost
let reader = SRSensorReader(sensor: .accelerometer)
reader.startRecording()
reader.fetch(request)
// CORRECT -- assign delegate first
reader.delegate = self
reader.startRecording()
reader.fetch(request)swift
// 错误——没有代理就没有回调,结果会被静默丢失
let reader = SRSensorReader(sensor: .accelerometer)
reader.startRecording()
reader.fetch(request)
// 正确——先指派代理
reader.delegate = self
reader.startRecording()
reader.fetch(request)DON'T: Skip per-sensor Info.plist usage detail
不要:缺少Info.plist中每个传感器的用途说明
swift
// WRONG -- missing NSSensorKitUsageDetail for the sensor
// Authorization sheet shows no explanation, user is less likely to approve
// CORRECT -- add usage detail for every sensor you request
// See Info.plist Configuration section aboveswift
// 错误——传感器缺少对应的NSSensorKitUsageDetail配置
// 授权弹窗不会展示用途说明,用户授权的可能性更低
// 正确——为你申请的每个传感器都添加用途说明
// 参考上文Info.plist配置章节DON'T: Ignore SRError codes
不要:忽略SRError错误码
swift
// WRONG -- generic error handling
func sensorReader(_ reader: SRSensorReader, fetching: SRFetchRequest, failedWithError error: any Error) {
print("Error")
}
// CORRECT -- handle specific error codes
func sensorReader(_ reader: SRSensorReader, fetching: SRFetchRequest, failedWithError error: any Error) {
if let srError = error as? SRError {
switch srError.code {
case .invalidEntitlement:
// Entitlement missing or sensor not in entitlement array
break
case .noAuthorization:
// User has not authorized this sensor
break
case .dataInaccessible:
// Data in 24-hour holding period or otherwise unavailable
break
case .fetchRequestInvalid:
// Invalid time range or device
break
case .promptDeclined:
// User declined the authorization prompt
break
@unknown default:
break
}
}
}swift
// 错误——通用错误处理
func sensorReader(_ reader: SRSensorReader, fetching: SRFetchRequest, failedWithError error: any Error) {
print("错误")
}
// 正确——处理特定的错误码
func sensorReader(_ reader: SRSensorReader, fetching: SRFetchRequest, failedWithError error: any Error) {
if let srError = error as? SRError {
switch srError.code {
case .invalidEntitlement:
// 缺少entitlement或者传感器不在entitlement数组中
break
case .noAuthorization:
// 用户尚未授权该传感器
break
case .dataInaccessible:
// 数据处于24小时保留期或其他原因不可用
break
case .fetchRequestInvalid:
// 时间范围或设备无效
break
case .promptDeclined:
// 用户拒绝了授权弹窗
break
@unknown default:
break
}
}
}Review Checklist
审核检查清单
- Apple-approved research study in place before development
- entitlement lists only needed sensors
com.apple.developer.sensorkit.reader.allow - Manual provisioning profile with explicit App ID and SensorKit capability
- in Info.plist with clear study purpose
NSSensorKitUsageDescription - in Info.plist with valid privacy policy URL
NSSensorKitPrivacyPolicyURL - entries for every requested sensor
NSSensorKitUsageDetail - key set appropriately for essential vs. optional sensors
Required - Authorization requested before recording, status checked before fetching
- Delegate assigned before calling or
startRecording()fetch(_:) - Fetch request time ranges account for 24-hour data holding period
- codes handled in all failure delegate methods
SRError - used to discover available devices before fetching
fetchDevices() - called when data collection is complete
stopRecording() - returns
sensorReader(_:fetching:didFetchResult:)to continue ortrueto stopfalse
- 开发前已获得苹果批准的研究项目
- entitlement仅列出所需的传感器
com.apple.developer.sensorkit.reader.allow - 已配置带有显式App ID和SensorKit能力的手动描述文件
- Info.plist中已添加,清晰说明研究用途
NSSensorKitUsageDescription - Info.plist中已添加,指向有效的隐私政策链接
NSSensorKitPrivacyPolicyURL - 为每个申请的传感器都配置了条目
NSSensorKitUsageDetail - 已为必要和可选传感器正确设置键
Required - 录制前已申请授权,拉取前已检查授权状态
- 调用或
startRecording()之前已指派代理fetch(_:) - 拉取请求的时间范围已考虑24小时数据保留期
- 所有失败代理方法中都已处理错误码
SRError - 拉取数据前已使用发现可用设备
fetchDevices() - 数据收集完成后已调用
stopRecording() - 返回
sensorReader(_:fetching:didFetchResult:)继续接收结果或true停止接收false
References
参考资料
- Extended patterns (delegate wiring, multi-sensor manager, sample type details): references/sensorkit-patterns.md
- SensorKit framework
- SRSensorReader
- SRSensor
- SRDevice
- SRFetchRequest
- Configuring your project for sensor reading
- com.apple.developer.sensorkit.reader.allow
- 扩展模式(代理绑定、多传感器管理器、样本类型详情):references/sensorkit-patterns.md
- SensorKit 框架
- SRSensorReader
- SRSensor
- SRDevice
- SRFetchRequest
- 为传感器读取配置你的项目
- com.apple.developer.sensorkit.reader.allow