大多数浏览器和
Developer App 均支持流媒体播放。
-
架构您的 App 以支持多窗口
深入了解在 iOS 13 中支持多任务处理的意义所在。了解如何将以前的最佳做法与新的概念相结合。认识构建 app 来支持多窗口的细微差别,学习如何实例化 UI、处理窗口的显示与隐藏,以及管理 app 的底层窗口资源。
资源
-
下载
我的名字是 Janum Trivedi 我是 UIKit 框架团队的 一名工程师
首先 我们来谈一谈 构建适用于多个窗口的 App 在 iOS 13 中 支持多个窗口 是一个很好的方法 来使现有的 App 变得更有用 同时大大提高 用户的工作效率
那么今天我们将讨论 三个主要的话题 首先 我们将会 快速浏览一下 对 App 生命周期的更改 它将允许在 iOS 13 上 出现多个窗口
接着 我们会深入研究 新的 UI Scene Delegate 在那里我们还会讨论 你需要完成的工作 最后 我们会回顾一些 ArchitectureKit 中的最佳范例 这样你和你的团队就可以 利用它们来保证 可以给你的用户提供一个 持续的 无缝的 且多任务的体验 那么 首先让我们来看看 在 iOS 12 及之前的版本中 App 代理的一些 作用和职责 App 代理有两个主要的作用 第一是将进程级事件 通知给你的 App 那么 系统会在进程 启动或将要结束的时候 通知你的 App 代理 但它还有第二个作用 就是让你的 App 知道其 UI 的状态
那么 通过一些方法 如 didEnterForeground 和 willResignActive 系统就会让你知道 你的 UI 的状态 这在 iOS 12 及之前的版本中 都是没有问题的 因为 App 有一个进程 同时也只有一个用户界面实例 来与它匹配 那么 今天你的 App 代理 可能看起来会像这样 只是可能没这么短 那么在 didFinishLaunchingWithOptions 中 你完成了几件事 首先你完成了一些 一次性的非 UI 全局设置 比如连接到一个数据库 或者初始化数据结构 之后你立刻设置了 你的用户界面 而同样的 这在 iOS 12 及之前的版本中完全有效 但在 iOS 13 的模式中是无效的 为什么呢 因为现在的 App 虽然只共享一个进程 但可能会有多个 用户界面实例或场景会话 这意味着 App 代理的职责 需要做出一些改变 它仍然会负责 进程事件和生命周期 但它不再负责任何 与 UI 生命周期相关的事情 相反 这些事情都将交给 UI Scene Delegate 来处理 那么 这对你来说意味着什么呢 任何你曾在你的 App 代理中 做过的 UI 设置或拆卸工作 现在都需要迁移到 Scene Delegate 中的相应方法 事实上 在 iOS 13 中 如果你的 App 采用新的 场景生命周期 那 UIKit 将停止调用与 UI 状态相关的 旧的 App 代理方法 相反 我们将会调用新的 Scene Delegate 方法 这很简单 因为这些 大多数都是一一对应的映射 但是不要担心 如果你想要 在 iOS 13 上采用多窗口支持 这并不意味着你需要 丢掉 iOS 12 及之前的支持 如果你要向后部署 你可以简单地将两组方法 都保留下来 UIKit 就会 在运行时调用正确的方法 那么 在我们深入讨论 具体的代理方法之前 App 代理还有 一个另外的职责 即系统会通知 你的 App 代理 当创建一个新的场景会话 或丢弃一个现有的场景会话时 我们来将这个生命周期 变得更具体一点 假设我一直在做 这个蓝色的 App 而现在我要首次将它启动 我们来看看调用栈
首先 你的 App 代理 要进行相同的 didFinishLaunchingWithOptions 调用 同样 在这里进行一次性的 非 UI 设置 是没有问题的 之后 系统会立即 创建一个场景会话 但在它创建具体的 UIScene 之前 它会要求我们的 App 进行 UIScene 配置 这个配置会指定 Scene Delegate 和 Storyboard 以及假设你指定了 你想要用什么场景子类 来创建场景
值得注意的是 你可以定义 这些场景配置 可以动态地在代码中定义 或静态地在 info.plist 中定义
这也为你提供了选择正确的配置的机会 你可以有一个主要的场景配置 你也可以有一个附属场景 那么 你应该看看 这里提供的可选参数 并将它作为上下文 来选择正确的场景配置 一旦你定义了这些 比如说在 info.plist 文件中 这个很简单 你只需要按名称引用它 确保你将传入的作用是 引入的会话的作用
好了 现在我们的 App 就启动了 我们有一个场景会话 但我们没有看到任何 UI 这就是我们的 Scene Delegate 连接到场景会话的地方 我们来看看在这里 我们应该做什么呢 在这里 你用新指定的 UI 窗口初始化器 设置 UI 窗口 我们会注意到我们传入了 提供的窗口场景 但重要的是我们还需要 检查任何相关的 用户活动或状态恢复 以配置我们的窗口 我们待会再详细讨论这个
好 现在来看我们的 App 那么 当我们的一个用户 向上划动返回主页时会发生什么呢
我们熟悉的 willResignActive 和 didEnterBackground 方法 会在你的 Scene Delegate 上被调用 但现在有一些有趣的事情 在此之后的某个时刻 你的场景可能会断开连接 那么 这意味着什么呢 好 为了回收资源 系统可能会 在你的场景 进入后台的某个时候 从内存中释放该场景 这也意味着你的 Scene Delegate 将会 从内存中被释放 而该代理中所持有的 任何窗口层级或视图层级也将被释放
这给你提供了机会 来释放 App 中 与此场景相关的 保留在其他地方的 内存中的任何大型资源 但是重要的是 不要用它 来永久删除任何 用户数据或状态 因为该场景稍后可能会 重新连接并返回
接着我们讨论一下 当我们的用户真的 在切换器上上划一个场景会话 并明确地想要销毁它时 会发生什么呢 那么 系统会调用我们的 App 代理 didDiscardSceneSessions
这让我们终于能够真正永久地 代理任何与该场景有关的 用户状态或数据 比如一个文档编辑 App 中 未保存的草稿 现在 也有可能 你的一个用户从切换器中 移除了一个或多个 UI 场景 通过在你的 App 进程 还未真正运行时向上划动 如果你的进程还没有运行 系统就会记录下 00:07:25.746 --> 00:07:27.536 A:middle 被丢弃的会话
在你的 App 下次启动后 快速调用该会话
现在 我们来讨论一些 构建模式 你可以考虑 将它们融入你的 App 中 我们首先来说说状态恢复
在 iOS 13 中 状态恢复 不再是一个好东西 至关重要的是 你的 App 要实现 基于场景的状态恢复 我们来看看这是为什么
这是我们的 App 切换器 我有一个文档 App 我正在计划一次自驾游 我现在打开了四个 不同文档的不同会话 但是我真正关注的 是“行李清单”和“日程安排”
在某一时刻 另外两个 在后台上的“自驾游” 和“出席者”的场景 已经被系统断开连接并释放 如果我在这里 不实现状态恢复的话 当我回到“自驾游”时 我就不会回到之前 所在的状态 我不会回到我之前 正在编辑的文档的场景 而是会直接重新开始 就像打开一个全新的窗口 但这并不是一个很好的用户体验
那么 如何解决这个问题呢 iOS 13 有一个全新的 基于场景的状态恢复的 API 它非常简单
就是不再对视图层级进行编码 而是对允许重新 创建窗口的状态进行编码
这也全部都基于 NSUserActivity 因此 如果你的 App 利用了功能强大的技术 比如聚焦搜索或接力 那你就可以用这些相同的活动 来对你 App 的状态进行编码 我们也要注意 在 iOS 13 中 你返回给 系统的状态恢复存档 将会匹配 App 中的其余部分的 相同数据保护类
这在代码中是怎样的呢 那么 在我们的 Scene Delegate 中 我们实现场景的 状态恢复活动 然后我调用一个方法 来在现有的窗口中寻找 最活跃的相关用户活动表 然后我们回到该表 过一些时候 当该场景 重新进入前台且连接成功时 我们检查该会话是否含有一个 状态恢复活动 如果有 我们就用该活动 如果没有 我们可以创建一个 全新的没有任何状态的窗口
这意味着无论如何 当场景在后台断开连接时 我们的用户永远不会注意到 因为这不应该被注意到
最后 我们来说说 在采用多个窗口的支持时 你可能会遇到的 一个更重要的问题 也就是如何最大程度地 保持 App 场景的同步 让我来把它具体化 我一直在做一个新的 聊天 App 就在这里 而我们可以看到 我最近刚在 iOS 13 上添加了 对多个窗口的支持
我有一个与我的朋友 Giovanni 的聊天 几分钟后他将和我一起上台 注意 我们正在同时 查看相同的对话 在两个不同的视图控制器 及两个不同的场景中
那么 我们假设我要 给 Giovanni 发送一条信息 告诉他我准备好吃午饭了
那么 只有一个场景更新了 所以这是为什么呢
好 这是因为在 iOS 中 许多 App 的结构都是这样 视图控制器会 接收一个事件 可能是通过点击按钮 即我按下发送按钮 然后视图控制器本就 就会更新它自己的 UI 之后 我们的视图控制器 会通知模型或模型控制器 而当我们只讨论 一个用户界面实例时 这基本上是没问题的 但现在如果我们在一个 显示相同数据的不同场景中 引入第二个视图控制器 那么无论什么时候 这个新的视图控制器都不会 收到通知来更新这个新数据 这是一个问题 那么我们可以解决这个问题 在架构上 如果我们的 视图控制器能够 在接收到一个事件后 立即并且只通知 我们的模型控制器 那我们就可以让模型控制器 通知任何相关的订阅者 或者视图控制器 来更新这个新数据
有许多方法都可以完成这个 我们可以使用代理和通知 我们甚至可以用全新的 今年发布的 Swift Combine 框架 但我们先来看一个 简单的 Swift 示例 你可以考虑将其整合进你的 App 中 这是现在的方法 当我在发送信息时按下 返回键时 它就会被调用 我创建了一个信息模型对象 我的视图控制器更新 其自己的视图 接着我们通知模型控制器 来保存这个 我们首先要做的 就是让该视图控制器 不改变它自己的视图状态 相反 我们要去掉这些代码 稍后我们会把它加回去
现在 我们来看看 该模型控制器 add() 方法到底在做什么 其实很简单 我们要做的就是 保存该新消息 但实际上我们想让 模型控制器立刻进行通知 如果任何其他视图控制器 或连接的场景需要的更新的话
我们如何发送这个更新呢 我们需要一种结构化的方法 来打包这个事件 这样它就成为强类型 且易于调试和测试 那么 我们来继续创建 一个新的类型 我们称之为 UpdateEvent 它是一个带有相关值的 Swift 枚举 我们将添加一个新的消息类型 这是我们的模型控制器 将要在接收新的消息时 创建的对象 然后会发送给任何的 相关的视图控制器或场景
因为我们想发送这个 我们将用 NSNotificationCenter 作为这个的备份存储 因此 我们会添加这个便利的 post() 方法 它允许我们在一行中 创建一个新的更新事件 然后将其发送给任何订阅者 实现这个相当简单 我们只要发送一个通知 到新消息的通知名称通道 但这里的技巧是 我们要将更新事件对象本身 包含在通知对象中 这会派上用场 我们很快就会看到 现在 当我们的模型控制器 被通知添加了一个新消息时 在我们保存它之后 我们就可以创建这个新事件 并调用 post()
接着 如果我们看看 要如何改变我们的视图控制器 我们会注意到这个新事件 在本例里 就是新消息通知名称 然后我们创建一个 handler() 方法 从参数中获取通知 记住 当我们将更新事件 作为通知对象进行传递时 我们现在就可以从通知中 直接拉出该事件 然后我们可以很容易地 打开这种事件类型情况
因为我们创建了一个相关枚举 我们就可以把信息拉出来 现在 我们可以更新这里的用户界面
那么 让我们来看看 在实现这个新的构架之后 当我向 Giovanni 发送相同的消息时会发生什么呢 好了 所有的场景都更新了
那么 我们今天讲了很多 我们已经讲了一些 App 代理和 Scene Delegate 的不同 及其职责的不同 我们之前还说了一些 主要的 Scene Delegate 方法 及你应该做的工作
我们也讨论了为什么 状态恢复在你使用 iOS 13 时是如此的重要 以及如何利用新的 基于场景的 API 来完成它 最后 我们说了一些 创建一个单向数据流的 高级的模式 这样我们 就可以在共享相同的数据时 保持所有的场景同步 谢谢
[掌声]
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。