大多数浏览器和
Developer App 均支持流媒体播放。
-
位置授权的新动向
位置授权开启 2.0 时代。了解用于获得所需授权的全新建议和技巧,以及可在无法达成授权目标时向你发出通知的全新诊断机制。
章节
- 0:00 - Introduction
- 1:52 - Authorization goals
- 9:25 - Session lifecycle
- 13:29 - Diagnostic properties
资源
-
下载
大家好 我叫 Adam Driscoll 是 Core Location 团队的工程师 今天我将向大家介绍 位置授权的新动向 如果你在看这个视频 那么你可能知道 Core Location 可以让“地图”之类的 App 找到你所在的位置 你的 App 也能做到这一点 通过使用 Core Location API 你的 App 可以向用户展示 附近发生的趣事 或引导用户参观展览 去年 我的团队成员 Siraj 和 Nivash 推出了两个新的 API 对象 CLLocationUpdate 和 CLMonitor 每个对象都提供一个 AsyncSequence 接口 可以在每次设备移动时 轻松获取 纬度和经度更新数据 或者每当你描述的空间条件 变为满足或不满足时 获取状态变化事件
要使用这些 API 你需要获得每个 使用你 App 的用户给出的许可 要实现这一点 需要使用 Core Location Authorization API 但在 Swift 中 相关代码非常复杂 今天 我将帮助你大幅简化 这堆代码 简化到这个程度 并且让代码在进程中 变得更加稳定可靠 实现这一切的关键是 CLServiceSession 以及一个新的 诊断属性系统 今天我将 分成三个主题进行介绍 首先 CLServiceSession 是一种新的声明式方式 可告知 Core Location 你的授权目标是什么 也就是在运行你的 App 的 每项功能时 你需要获得什么授权
然后 由于它是一个会话对象 我将深入介绍 应该何时创建这个会话 以及当你的 App 暂停或终止时 会话会发生什么情况
最后 我将介绍 全新的诊断属性系统 这些属性能够为你的 App 提供情境信息 让 App 全面了解当前授权 以及任何其他 Core Location API 对象的运行情况
首先来看目标 目标的设定从过程模型 转变为声明式模型 以便与位置授权进行交互 这里显示了“设置”中的 “LocationMonitorSample” App 授权界面 对于“精确位置”开关和 上面的选项之间的授权过程 如果制作一个示意图 并稍微简化一下 它看起来可能是这样的
每个 App 一开始都处于 NotDetermined 状态 如果用户选择授权 App 进入右边的某个状态 可以选择授予全精度权限 或仅提供大致位置的访问权限
要启动这个流程 以前需要 使用 CLLocationManager 以及诸如 requestWhenInUseAuthorization 之类的方法来请求授权 这样可将状态从 NotDetermined 变为 WhenInUse 或 Denial 虽然这种询问方式很明确 但也许用户在使用你的 App 时 通常只需提供大致位置 所以用户只授权访问大致的位置 然后 有一天 用户要求进行逐向导航 或使用一些显然只能在 获取全精度位置信息后 才能正常发挥作用的其他功能 这时你需要调用 requestTemporaryFullAccuracy(withPurposeKey:) 让用户能够临时提升授权等级 但临时授权会失效 如果用户一开始 确实选择了“允许一次” 从而只给予你临时 WhenInUse 授权 然后突然切换到其他 App 和朋友聊一会儿天 该怎么办? 你需要再次调用 requestWhenInUseAuthorization 也许还要再次调用 requestTemporaryFullAccuracy(withPurposeKey:) 在这个示例中 为了提供逐向导航 你实际上需要获得全精度 WhenInUse 授权 我们把这个需求称为授权目标 到目前为止 示意图中的 每一步操作都是这个目标驱动的 今年新推出的 CLServiceSession 可以让你直接表达授权目标 并将这些目标与推动 实现目标的任务相关联 在我们的示例中 我们承认 有些用户通常可能会 仅授予大致位置的访问权限 一般来说这是没问题的 因此你可以设定一个 遵守这一选择的目标 只需像这样实例化并保持 CLServiceSession 并使用 .whenInUse 作为参数 Core Location 将根据需要 向每个用户提出请求 以尝试让你的 App 进入这些状态之一 如果有一天 用户 确实要使用导航功能 你可以创建一个包含 fullAccuracyPurposeKey 的 额外 CLServiceSession 来缩小目标范围 这就像叠加层次一样: App 仍然保持它的常规用途 但现在它在执行一项临时需要 全精度授权的额外操作 所以只需保持原有的会话 同时添加这个新会话 不管怎样 系统会在 你用于位置授权的 本地化资源中查找“Nav”键 如果 Core Location 需要请求 临时全精度授权 来实现这个目标 那么你已经提供了一个 描述导航目的的字符串 可以确保每个用户了解为什么 现在需要提供全精度授权
我刚才提到了一些值得重申的事项 首先 CLServiceSession 对象 改变了以往的做法 它们不会告知 Core Location 要做什么 而是说明你的需求是什么 这意味着你不必担心 各种各样难以测试的问题 例如你的 App 在本应请求授权时 却被置于后台运行 你的 App 会继续请求授权吗 会的 因为你保持了 CLServiceSession Core Location 知道你的目标 并会在下次可能的情况下 采取行动以帮助你实现目标
与之相关的是 你应该 主动使用 CLServiceSession 例如 即使你的 App 实际上已经获得全精度授权 也应该保持一个请求全精度 授权的会话 以便在用户使用某项 需要特别请求 相应授权的功能时使用 如何确定某项功能何时需要 以这种明确的方式 请求全精度授权? 思考一下 用户在使用你的 App 时 为什么可能愿意提供他们的大致位置 毕竟 他们本可以完全拒绝 共享位置 但他们没有这样做 有哪些个别的特殊功能 会让用户感觉在使用时 有必要破例一下 提供他们的精确位置呢 仅在使用这些功能的情况下 保持全精度服务会话 最后 CLServiceSession 实际上是可发送的不可变对象 但一般来说 当需求发生变化时 应考虑叠加层次 而不是替换会话
如果你的 App 具有不同的组件 这些组件驱动着 具有不同相关授权目标的不同功能 而这些功能有时采用 分层或嵌套方式构建 在这种情况下 不应该 让这些组件进行协调 来决定要创建哪种服务会话 而是应该让它们在需要时 单独创建各自所需的服务会话 让每个分层功能 都能在服务会话中自行运行 Core Location 会负责完成其余工作
说到分层 我一直在谈论全新的 CLServiceSession 结构 但我们去年推出的 每个 API 还定义了 在迭代 liveUpdates 或事件序列时运行的某种会话 例如 在很多情况下 如果你的 App 在迭代 liveUpdates 那么它的目标是 接收这些实时更新 对吧 Core Location 能够将 迭代 liveUpdates 或 Monitor.events 的行为 视为隐式保持 CLServiceSession 这个功能很棒 因为这意味着 你可能只需要删除代码 就能将 App 更新为使用服务会话 来处理 Core Location 授权 实际上 在更新去年推出的 适用于 CLLocationUpdate 的 示例 App 时 这就是所需的全部工作 系统会默认启用这个行为 因此你可以利用隐式服务会话 来定义授权目标 每个提供用于迭代的异步序列的 现代 Core Location API 都会在迭代时提供 .whenInUse 目标授权状态 因此 我在几分钟前 展示的用于处理授权的 简短代码片段 现在变得更加简短了 希望大家喜欢这些内容 但我知道 在有些情况下 尤其是在大型项目中 有时可能不太适合 向你的 App 用户请求授权 在这种情况下 完全阻止代码迭代更新 可能会带来不便 因此 如果你在使用 API 时 不希望在你的 App 中 隐式激发授权请求 可以完全停用这些隐式会话的效果 方法是在 App 的 Info.plist 中设置 NSLocationRequireExplicitServiceSession 键 在这之后 只需再次 使用显式会话即可 有三个主要原因可以解释 为什么可能要使用实际的 CLServiceSession 对象 而不是只依赖于隐式会话 首先 隐式服务会话 不要求提供全精度授权 因为在大多数情况下 如果用户设置了精确度限制 你的 App 就应该遵守相应限制 仅在确实需要全精度授权时 使用显式会话 同样 Core Location 不会 假定你的 App 需要“始终”授权 如果你确实需要这个授权 就需要采用显式 CLServiceSession 并将目标设置为 .always 以便在需要时使用 它授予的额外访问权限 请注意 从今年开始 只有在你保持某个会话时 “始终”授权才会生效 并且只有当你的 App 位于前台时 你才能开始保持会话 我稍后将在讨论会话生命周期时 详细说明这一点 最后 结合使用 info.plist 键 你可以在特定时间 创建显式服务会话 或者使用 .none 作为授权目标 以便更好地控制 Core Location 何时代表你的 App 向用户请求授权 如果 App 在内部 多次使用 Core Location 或者希望让用户自主决定 App 是否应该使用位置信息 那么这种方法可能是最简单的 你可以使用 CLServiceSession 也可以利用 通过迭代 liveUpdates 和事件获得的隐式会话 由于这些会话 与面向用户的功能相契合 意味着它们将在 App 的 生命周期中作为整体与事件交互 我们来了解一下
我们来直观地呈现 用户运行 App 的场景 标出他们使用的依赖于 Core Location 的功能 有些功能的使用时间可能很短 比如为照片添加地理位置标签 有些功能的使用时间可能比较长 比如在 MapKit 视图中 浏览一段时间 如果他们在浏览时 查询了估算的行程时间 功能甚至可能会重叠 围绕这些功能会话创建并保持 CLServiceSession 对象 比较简单 只需在代码中 将这些对象与实现 相应功能的组件相关联 并确保根据需要叠加层次 当然 默认情况下 隐式会话 甚至会为你完成这些操作
但用户不一定全程 都在 App 中使用功能 例如 运动轨迹记录之类的功能 可能会自然运行超过几秒钟 之后用户很可能会让它在后台运行 将注意力转移到某个并行任务上 比如在另一个 App 中 挑选合适的音乐 然后在锻炼结束后 再回到运动跟踪 App
如果功能在概念上持续运行 所有隐式会话也会持续运行 你创建的所有显式 CLServiceSession 对象也应该这样做 你可能会问为什么 原因有三个 首先 这样做表明你的 App 持续关注它的授权目标 当你的 App 在后台运行时 Core Location 不会要求用户 调整这个 App 的授权级别 但通过了解当你的 App 意外回到 前台时你的需求是什么 Core Location 可以在需要时 立即采取行动来恢复授权
如果你的 App 获得了“始终”授权 则在不使用 App 时 liveUpdates 和 CLMonitor.events 将不会产生结果 除非在前台启动的会话 或者另一个有效会话 表明 App 在持续关注授权目标
如果前两个理由不够有说服力 还有最后一个理由 即 这种方法更简单 将会话生命周期直接与 功能运行时间相关联 并且无需担心任何其他因素 隐式会话会这样做 所以 你创建的会话也应该这样做 即使 App 在一段时间后 因为没有收到更新内容 而暂停运行 也可以 让会话在后台保持运行 同时它仍然随时可以跟踪锻炼情况
更重要的是 即使 App 在暂停一段时间后 终止运行 会话也仍然可以运行 请注意 这时仍然 需要跟踪锻炼情况 这是用户要求 App 执行的操作 但 App 进程以及进程包含的所有 CLServiceSession 对象 或隐式会话 现在都不复存在 如果没有要向 App 传输的数据 Core Location 不会采取措施 来让 App 持续运行 也不会为没有获得授权的 App 生成或提供更新 例如 由于获得的是 .whenInUse 授权 如果没有有效的 LiveActivity 或 CLBackgroundActivitySession App 就会待在后台 但这也就是说 当 App 暂停或终止时 Core Location 确实会 跟踪每个未完成的 API 对象 包括 CLServiceSession、 liveUpdates 或各种其他序列 并继续执行相关操作 这意味着 当新信息等待传输 并且 App 获得的授权允许传输时 Core Location 会在后台 恢复或重新启动 App
正因为如此 尽管 App 的 实际进程发生了变化 示意图中的蓝色会话条 仍在持续推进
但 Core Location 不会 永远跟踪这些显式会话和隐式会话 而是仅在 App 再次启动后的 几秒钟内进行跟踪 无论启动是由于 Core Location API 事件、 用户互动还是任何其他原因导致的 如果 App 最终没有 恢复运行相应功能 那么这段有限的时间 是防止会话状态泄露的重要措施 因此 你在编写 这类 App 的代码时 需要确保进程启动逻辑 知道自己的任务是处理哪些功能 并尽快重新获取会话对象 或恢复迭代之前中断的 liveUpdates 或 Monitor.events 这样一来 新对象 可以取代之前的对象 Core Location 也会知道 你的 App 仍在关注授权目标 这就引出了诊断属性系统这个主题 诊断属性系统会指明 无法实现授权目标的原因 虽然只需要保持 CLServiceSession 就能获得这个会话的效果 但每个实例还公开一个 诊断 AsyncSequence 你可以使用“for try await” 对它进行迭代 从而了解发生的情况 就像这段代码显示的那样
接下来 注意这个布尔属性 这个属性就是一个诊断属性 它会在发生意外情况时 告知你相关信息 在这个示例中 你的 App 用户 不想与 App 共享自己的位置 除了 .authorizationDenied 以外 还有其他几个属性 包括 .insufficientlyInUse 表示 Core Location 还无法 代表你的 App 请求授权 或者 .alwaysAuthorizationDenied 表示你设置了“始终”授权目标 但没有获得授权 需要特别注意的是 .authorizationDeniedGlobally 当整个系统范围内都停用 定位服务时 这个属性为 true 如果用户以这种方式 配置他们的设备 那么你的 App 也会被拒绝授权 因此 .authorizationDenied 属性也会为 true 诊断属性系统会将这两个属性 视为两个不同但相关的属性 因此 如果你的 App 只想知道 是否被用户明确拒绝访问 例如当用户停用 UI 中的行程时间估算时 只需查看更广泛的那个属性即可
但如果你希望为那些 早在安装你的 App 之前 就已决定全局拒绝授权的用户 提供不同的指导 你也可以查看更具体的属性 了解情况是否确实是这样 回过头再来看这段代码 你可以编写更中性一点的 诊断处理逻辑来利用这些代码 也许你只需要知道 用户是何时做出的决定 请注意 在运行这段代码时 如果用户已经向你的 App 授予了 相应权限或拒绝了授权 那么 Core Location 不需要询问用户任何内容 因此在生成的第一个诊断中 .authorizationRequestInProgress 属性已经为 false 并且代码将立即中断运行 这可能就是你想要的结果 但请记住 如果用户 在“设置”中调整了授权 或者实际上只授予了临时授权 App 的授权可能就会随之变化 因此 只要你的 App 提供会话所对应的功能 你可能就需要持续侦听 会话诊断信息 或者设置一些 @Published var 以便你的 UI 做出响应
如果你有一个在 App 需要 获得位置信息时运行的 Task 并且你在 Task 中定义了 会话和迭代的范围 并让会话和迭代在任务完成后取消 那么你的会话将拥有 合适的生命周期 你的 UI 也将能够根据 App 用户选择提供的 授权做出适当的响应 当 CLServiceSession 无法实现授权目标状态时 它可以利用诊断属性系统提供的 出色灵活性和强大功能 为你提供切实可行的信息 大家都知道 liveUpdates 和 CLMonitor.Event 也提供隐式会话 因此每个 CLLocationUpdate 或 CLMonitor.Event 中 也会提供诊断属性系统 我之前展示过这些对象 显然 CLLocationUpdate 已经 有一个名为 .stationary 的 布尔诊断属性 如果因为设备停止移动而导致 更新暂停 这个属性会在暂停前的 最后一次更新时显示为 true
但现在每个对象 都将包含其他布尔属性 以解决它们作为 隐式服务会话运行时 可能导致的问题 此外还有更多属性 CLLocationUpdate 新增了两个属性 用于解释为什么一段时间内 可能不会有进一步更新 .accuracyLimited 表示大致位置 每 15 到 20 分钟更新一次 .locationUnavailable 表示 Core Location 目前完全无法确定设备位置 CLMonitor.Event 也可以向你报告 由于精确性受到限制 无法针对某个条件生成事件 或者是出于一些其他原因 比如会话已经在监控 太多同类型的其他条件 综合起来看 通过查看每个 API 对象上的这些诊断属性 你应该可以避免超时 并且不必再像过去那样 靠猜测编写代码 现在 如果 Core Location 出于某种原因无法提供 你期望的更新或事件 它至少会提供一个 包含诊断属性集的结果来表明原因 以上就是全部内容 请记住 如果你自然迭代的 API 更新和事件 没有自动处理你需要的授权 那么你可以使用 CLServiceSession 来告知 Core Location 你需要哪些授权 现在 每当更新、事件 或服务会话无法生成预期结果时 Core Location 都会通过 诊断属性系统告知你相应原因 如需详细了解 CLLocationUpdate 和 CLMonitor 请观看去年的这些讲座 请尝试这些新工具 并与我们分享你的使用感受!
-
-
0:31 - CLLocationUpdate and CLMonitor
// Iterating liveUpdates to reflect current location Task { let updates = CLLocationUpdate.liveUpdates() for try await update in updates { if let loc = update.location { updateLocationUI(location: loc) } } } // Iterating monitor events to report condition state changes Task { let monitor = await CLMonitor(monitorName) await monitor.add(CLMonitor.CircularGeographicCondition(center: applePark, radius: 50), identifier: "ApplePark") for try await event in await monitor.events { updateConditionsUI(for: event.identifier, state: event.state) } }
-
0:52 - Handle updates with CLLocationManagerDelegate
// Adapting location authorization to Swift with a MainActor singleton @MainActor class LocationReflector: NSObject, CLLocationManagerDelegate, ObservableObject { static let shared = LocationReflector() private let manager = CLLocationManager() override init() { super.init() manager.delegate = self } func locationManagerDidChangeAuthorization(_ manager: CLLocationManager){ if (manager.authorizationStatus == .notDetermined) { manager.requestWhenInUseAuthorization() } } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations:[CLLocation]) { // Process locations[0] } // ... }
-
1:07 - CLServiceSession simplifies
// CLServiceSession in action Task { let session = CLServiceSession(authorization: .whenInUse) for try await update in CLLocationUpdate.liveUpdates { // Process update.location or update.authorizationDenied } }
-
7:15 - Implicit service sessions
// CLServiceSession in action Task { let session = CLServiceSession(authorization: .whenInUse) for try await update in CLLocationUpdate.liveUpdates { // Process update.location or update.authorizationDenied } }
-
7:34 - Implicit service sessions
Task { for try await update in CLLocationUpdate.liveUpdates { // Process update.location or update.authorizationDenied } }
-
13:37 - Diagnostics – Following the progress of location authorization
// Following the progress of location authorization with CLServiceSession let mySession = CLServiceSession(authorization:.whenInUse) for try await diagnostic in mySession.diagnostics { if (diagnostic.authorizationDenied) { // Ok, let’s let them pick a location instead? } }
-
15:00 - Diagnostics – Following the progress of location authorization
// Following the progress of location authorization with CLServiceSession let mySession = CLServiceSession(authorization:.whenInUse) for try await diagnostic in mySession.diagnostics { if (!diagnostic.authorizationRequestInProgress) { // They’ve decided (maybe already). We can move on! break } }
-
15:25 - Diagnostics – Following the progress of location authorization
// Following the progress of location authorization with CLServiceSession let mySession = CLServiceSession(authorization:.whenInUse) for try await diagnostic in mySession.diagnostics { if (!diagnostic.authorizationRequestInProgress) { reactToChanges(authorized:!diagnostic.authorizationDenied) } }
-
15:46 - Diagnostics – Following the progress of location authorization
// Following the progress of location authorization with CLServiceSession Task { let mySession = CLServiceSession(authorization:.whenInUse) for try await diagnostic in mySession.diagnostics { if (!diagnostic.authorizationRequestInProgress) { reactToChanges(authorized:!diagnostic.authorizationDenied) } } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。