Build a research and care app, part 2: Schedule tasks
Health & Fitness 进阶 26m

构建研究与护理应用(二):调度研究任务

Build a research and care app, part 2: Schedule tasks

2021年6月10日

在 Apple 官方观看视频

一句话判断

CareKit 的任务调度系统比想象中灵活得多 —— 支持条件触发、自定义间隔、甚至基于 HealthKit 数据的事件驱动,但这套 API 的学习曲线不低,文档也不够完善。

这场 Session 讲了什么

作为系列的第二部分,这集聚焦于如何在 CareKit 中创建和管理研究任务的调度。Session 深入讲解了 OCKSchedule 的各种配置方式:固定时间触发、条件触发、自定义间隔,以及如何把这些调度和 ResearchKit 的任务(Survey、Active Task)结合。

演示了一个心血管研究 app 中常见的任务类型:每日心率检测(Active Task)、每周健康问卷(Survey)、以及根据 HealthKit 数据触发的异常提醒。Session 强调了调度灵活性和用户负担之间的平衡 —— 太频繁的任务会导致参与者退出研究。

还介绍了 OCKOutcome 的概念:任务完成后的数据记录。每条 Outcome 绑定到具体的 Schedule Event,可以包含多个值(如心率数值、问卷答案等)。

值得深挖的点

Schedule Event 和 Outcome 的对应关系。CareKit 的调度不是简单的”每天几点”,而是生成一系列离散的 OCKScheduleEvent,每个事件有唯一 ID。参与者完成任务后,OCKOutcome 会精确绑定到触发它的那个 Event。这意味着你可以在用户补做漏掉的任务时,准确追溯到原始调度时间点,这对研究数据的完整性至关重要。

条件任务的实现。CareKit 支持基于 HealthKit 数据的触发条件,比如”当步数低于 1000 时提醒用户做活动”。这需要把 OCKHealthKitCondition 附加到 Schedule 上,系统会在后台监控 HealthKit 数据变化。但要注意,条件检查依赖于后台刷新(Background Fetch),不是实时的,延迟可能在 15 分钟到数小时之间。

代码片段

创建自定义间隔的调度计划

import CareKit

func createWeeklySchedule() -> OCKSchedule {
    // 每周一次,周三上午 9 点触发,持续 12 周
    let startDate = Calendar.current.date(
        byAdding: .day, value: 1, to: Date()
    )!
    let endDate = Calendar.current.date(
        byAdding: .month, value: 3, to: startDate
    )!

    let elements: [OCKScheduleElement] = [
        OCKScheduleElement(
            start: startDate,
            end: endDate,
            interval: DateComponents(weekOfMonth: 1),
            text: "本周健康问卷已发布",
            targetValues: [],
            duration: .hours(24)  // 24 小时内完成有效
        )
    ]
    return OCKSchedule(composing: elements)
}

记录任务结果

import CareKit

func recordOutcome(taskEvent: OCKTaskEvent, heartRate: Double) {
    let store = OCKStore(name: "CardioStudy")
    // 创建结果,绑定到具体的调度事件
    let outcome = OCKOutcome(
        taskUUID: taskEvent.task.uuid,
        taskOccurrenceIndex: taskEvent.scheduleEvent.occurrence,
        values: [
            OCKOutcomeValue(Double(heartRate), units: "bpm")
        ]
    )
    store.addOutcome(outcome) { result in
        switch result {
        case .success:
            print("心率数据记录成功: \(heartRate) bpm")
        case .failure(let error):
            print("记录失败: \(error)")
        }
    }
}

创建带条件的任务

import CareKit
import HealthKit

func createConditionalTask() {
    // 当用户当天步数低于目标时,提醒做简短运动
    let stepCondition = OCKHealthKitCondition(
        quantityType: HKQuantityType.quantityType(forIdentifier: .stepCount)!,
        comparator: .lessThan,
        value: HKQuantity(unit: .count(), doubleValue: 5000)
    )
    var task = OCKTask(
        id: "activity_reminder",
        title: "该活动一下了",
        carePlanUUID: nil,
        schedule: OCKSchedule.dailyAtTime(
            hours: 18, minutes: 0,
            start: Date(), end: nil,
            text: "今天步数不足,做一组拉伸吧"
        )
    )
    // 附加 HealthKit 条件
    task.impactsAdherence = true
    // 条件满足时才在任务列表中显示
}

最佳实践

  1. 任务的 duration 设置要给用户留足余地。如果任务是早上 8 点触发、duration 设 1 小时,上班族大概率错过。建议至少设 12 小时窗口,甚至全天有效。
  2. OCKSchedule.composing 组合多个调度元素,实现”前两周每天一次,之后每周一次”这种递减式研究设计,避免参与者疲劳。
  3. Outcome 数据尽早同步到服务端。CareKit 本地存储在 app 被删除后就没了,对于研究数据这是不可接受的。建议每次记录后立即推送到你的后端。

还有什么值得关注

  • CareKit 的任务完成度会自动计算 adherence(遵从率),可以直接展示给研究者和参与者。
  • 系列第三集(Session 10081 之外的 part 3)覆盖了数据收集和分析流程。
  • OCKContact 可以用来在 app 内展示研究团队联系方式,增加参与者信任度。
WWDC 2021