大多数浏览器和
Developer App 均支持流媒体播放。
-
优化 Core Data 和 CloudKit 的使用
和我们一起探索开发周期的三个组成部分,帮助您优化 Core Data 和 CloudKit 的实施。我们将介绍如何分析您 App 的架构和功能集以便验证假设,探索提取大型数据集后的行为变化,并获得可操作的反馈来优化您的工作流程。为能更好地理解此讲座,我们建议您先熟悉将数据模型同步到 CloudKit 的基本流程。
资源
相关视频
WWDC22
WWDC21
WWDC20
WWDC19
-
下载
Nick: 大家好 我叫 Gillett 是 Apple Core Data 团队的一名工程师 在本期讲座中 我将向你展示如何 使用开发者工具了解更多关于使用 NSPersistentCloudKitContainer 的 App 信息 首先 我们将详细介绍 如何以一种富有成效 和具有教育意义的方式 来探索应用程序
然后 我们将使用一些我最喜欢的 工具来分析应用程序的行为 最后 我们将研究如何就使用 NSPersistentCloudKitContainer 的 体验提供详细的 可操作的反馈
我喜欢把工程学想象成水循环 通常 我从探索功能所在的空间 开始研究其功能 然后 根据我学到的东西 我会使用工具和测试的组合 在可复制的环境中分析我的工作 最后 我会与同事和合作者 一起回顾结果 并收集他们的反馈 这个循环的目标是持久地捕捉到 我在工作中 学到的东西 Apple 平台包括很多工具 比如 Xcode Instruments 和 XCTest 我用它们来获取我所学的内容 这些工具还可以收集大量的诊断信息 我可以用这些信息 来提供可操作的反馈
本讲座参考了过去几年的许多知识 我已经讨论过 NSPersisentCloudKitContainer 和 Core Data CloudKit 示例应用程序 我今天将在 “Build Apps that share data through CloudKit and Core Data” 和 “Using Core Data with CloudKit.” 这两个讲座中详细介绍这两个应用程序 我还将演示如何使用 Xcode 和 Instruments 来运行测试 以及如何使用 Device Organizer 从设备中捕获数据 如果需要 我建议你查看讲座 “Getting Started With Instruments” 和 “Diagnose performance issues with the Xcode Organizer” 以更多地了解工具链的 这两个重要部分 好了 让我们开始本循环的 第一部分 探索 对我来说 探索的首要目标是学习 我想挑战并验证 应用程序工作原理的所有假设 我可能会问 如果我点击这个按钮会发生什么 当我将数据保存到永久存储时 NSPersistentCloudKitContainer 是否同步 在处理大型数据集时 应用程序是否会耗尽内存 从 Core Data 的角度来看 所有这些问题都受到 应用程序处理的数据的影响 例如 Core Data CloudKit 示例应用程序使用这个数据模型
它管理一组帖子 其中包含 一些标题和内容的文本字段 帖子可以与附件 通常是图像 相关 这些附件可能非常大
因此 ImageData 存储在 一组对一关系中 以便按需加载 我将重点研究该数据集 特别是当我改变该数据的形状 结构和变化时 该示例应用程序会发生什么
自从发布以来 该示例应用程序 已经包含了一个内置的方法来探索它 Generate 1000 Posts 按钮 完全按照标签上的内容执行 点击后 它会生成一个 包含 1000 篇短文标题的 样本数据集 帖子的表格视图可以轻松处理 这种级别的数据 因此 我要问的下一个问题是 如何在这个应用程序中 探索不同形状或大小的数据集 Generate 1000 Posts 按钮运行的是 我所说的算法数据生成器 算法数据生成器遵循 一组预先确定的规则 如 “插入 1000 个对象” 或“确保每个字段都有值 或者没有字段有值” 事实证明 我们也是数据生成者 我们可以在代码中 在 SQL 中 或通过直接与应用程序交互 来手工创建特定的数据集 这些生成的数据集可以被保留 以供以后使用或分析 为了探索更大的数据集 我可以定义一个新的数据生成器 LargeDataGenerator 并提供一个方法 来构建我的新数据集 即 generateData 只需两个 for 循环 我就可以生成一组 60 个帖子 每个帖子都有 11 个关联的图片附件 总共有 660 张图片 每个图像的平均大小为 10 到 15 MB 生成的数据集消耗了近 10 GB 的数据 通过这样一个简单的接口 数据生成器很容易 在这样的测试中调用 这一行代码生成了超过 10 GB 的 代表性数据 供该测试使用
此外 我们可以在测试中 构建验证方法 以验证数据生成器的行为是否正确 比如断言每个帖子确实有 11 个图像附件
当然 如果我们不同步这些数据 这就不是 NSPersistentCloudKitContainer 所以让我们设计一个新的测试 来完成这个任务
我首先需要使用 NSPersistentCloudKitContainer 的一个实例 我已经创建了一个助手方法 来简化这一过程 接下来 我使用 LargeDataGenerator 用所需的数据集填充容器 最后 我等待容器完成数据导出 在这个特定的测试中 我最多等待 20 分钟 以便为大数据集提供上传时间
你们当中目光敏锐的人 可能已经注意到 这个测试似乎在等待不同类型的事件 在这里 当我创建容器时 我等待容器完成设置 在这里 我使用我编写的助手方法 为容器中的导出事件 创建 XctestExpections 我们来详细了解一下
此方法将所需的事件类型和 NSPersistentCloudKitContainer 的 实例作为参数 它使用 XCTestCase 的 expectationForNotification 方法 来观察 NSPersistentCloudKitContainer 的 eventChanged 通知 从而为容器中的每个持久性存储 创建一个预期 在通知处理程序模块中 我验证传入事件的类型 是否与这个预期所针对的 特定存储的类型一致 并通过检查 endDate 不等于 nil 来完成 通过使用这种技术 我们可以将测试中的控制点与来自 NSPersistentCloudKitContainer 的 事件紧密关联起来 回到测试中 我添加了一个新容器 来导入刚刚导出的数据 这项技术使用了一种技巧 它使用空存储文件创建 NSPersistentCloudKitContainer 的 新实例 这使得测试可以利用 NSPersisentCloudKitContainer 的 第一次导入 来探索当设备下载 所有这些数据时会发生什么 现在 测试很顺利 但有时我想知道 数据集在应用程序中是如何工作的 为此 我可以将数据生成器 绑定到用户界面 就像我们在示例应用程序中 所做的那样 当我点击 Generate Large Data 按钮时 我可以看到 数据生成器填充数据集 在另一个设备上 当 NSPersistentCloudKitContainer 下载生成的数据时 我可以看到表视图被填充 点击单个帖子 我可以看到附件下载 和增量填充 就像该应用程序用户所做的那样 此特定用户界面由警报控制器驱动 LargeDataGenerator 的简单接口 使得仅使用这两行代码 就可以轻松地添加新的警报操作 清晰 简洁 易理解
在这个部分 我们使用数据生成器的概念 探索了应用程序的行为 数据生成器可在我们的应用程序中 以我们选择的任何方式驱动 无论是通过测试或自定义 UI 如我所演示的那样 或是通过命令行参数之类的东西 或任何针对特定用例的东西 现在我们已经知道 如何用数据填充应用程序 那么我们就可以分析 数据如何改变应用程序的行为了 在这个部分 我们将了解一些工具和技术 来分析应用程序如何处理 大型数据集
具体来说 我们将使用工具 分析 LargeDataGenerator 创建的 数据集的时间和内存复杂性 然后 我们将查看系统日志中 丰富的可用信息 这样 我们就可以从 NSPersistentCloudKitContainer CloudKit 系统调度器和推送通知中 找到活动记录 我们先从 Instruments 开始 我喜欢测试的一个原因是 Xcode 使分析测试行为变得很容易 在我的测试用例中 我可以在 gutter 区域点击右键 显示并选择 Profile Xcode 将构建测试 然后自动启动工具 我可以双击 Time Profiler 工具 来检查我的测试在哪里花费了时间
当我点击记录按钮时 Instruments 将启动应用程序 并执行所选的测试 这个测试似乎要花很长时间来运行 我们跳过这一步 看看为什么 Instruments 已经选择了主线程 在右边 我可以看到测试运行中 最重的堆栈轨迹
我们让它更易阅读
好了 现在 如果我滚动到底部 可以看到 LargeDataGenerator 正在花费大量时间来生成缩略图 我们如何判断这是一个漏洞 还是一个功能呢
在 LargeDataGenerator 中 我用这行代码 为每个附件生成一个新的缩略图 但我从应用程序的数据模型中知道 缩略图是特殊的 它们是根据相关的 imageData 按需计算的 这意味着这一行代码不是必要的 我的数据生成器在这上面 浪费了很多时间 所以我可以把它删除掉 我们来看看这会如何改变测试的性能 在使用更新的数据生成器 重新构建应用程序后 我可以在 Instruments 中 重新运行测试 老实说 我看不出有什么变化 但再过几秒钟 测试就完成了 这比上一次运行快得多 我们看看测试大部分时间花在哪里
在右侧的边栏抽屉型菜单中 我现在看到最重的堆栈轨迹 是将图像保存到持久存储中 这正是我对管理这么多 数据的测试所期望的
这一改变将 generateData 测试的 运行时间从这么多 减少到这么多 它的执行时间只有十分之一 以这种方式分析测试 并不总能发现漏洞 有时 我们只是进一步了解了 应用程序在处理特定数据集时 将时间花在哪里 但不管怎样 这都是有价值的学习
这就是 Time Profiler 工具 如何帮助探索 应用程序在数据集上花费的时间 现在 由于这个数据集的大小 我也很好奇这个测试 使用了多少内存 让我们使用 Allocations 工具 来试一试 我将使用 Xcode 启动 Instruments 来分析我的测试 我将双击 Allocations 而不是选择 Time Profiler 工具
然后点击 Record
尽管此测试执行速度很快 但它使用了大量内存 实际上超过 10 GB 这告诉我 在测试运行期间 几乎整个数据集都保存在内存中 让我们来找出原因
我可以选择要查看的分配范围 在底部窗格中 我可以看到有许多大的分配 我可以通过单击这个公开信息 来深入研究这些内容 然后单击分配给测试的 一个大数据团 此特定 blob 已分配 但在将近两秒钟内未释放 这是一个永恒的测试时间 为什么它能存在这么久呢
我可以通过展开右边的 堆栈轨迹来探究这一点
根据经验 分配和解除堆栈轨迹告诉我 此对象是由于 CoreData 而出现了故障 然后在托管对象上下文 完成其工作时释放 这通常表示对象是由提取 autoreleasepool 或测试中的对象保留的
代码中有问题的部分在我的验证器中 我从附件中加载图像并进行验证 但这会将附件和关联的图像数据 注册到托管对象上下文中 我们可以通过多种方式 来解决这个问题 例如 在表格视图中 当表格滚动到帖子上时 我们可以使用批处理提取 来释放图像 然而 此测试执行得太快 无法有效执行 我需要改变方法 我可以提取附件 而不是通过提取帖子来验证 如果我也只提取 objectID 那么托管对象上下文 将不会捕获任何加载的对象 除非我要求它这样做
在进行验证时 我可以使用 NSManagedObjectContext 的 objectWithID 方法提取附件 最后 对于我验证的每 10 个附件 我会重置上下文 释放所有缓存状态和相关内存
如果我用这个变化重新运行测试 我可以看到它将引起 更可预测和更可调的内存消耗水平 事实上 在插入这些对象时 验证器使用的内存 甚至比 LargeDataGenerator 更少
让我们深入了解特定的分配 以了解修复的工作方式
首先 我将选择一系列要使用的分配 然后 我将选择一个 特定的大小进行检查
我需要启用已销毁的对象 来查找在此期间释放的对象 然后我可以选择一个 特定的分配进行检查
在右侧 Instruments 向我显示了 一个分配堆栈轨迹 但我想知道它在哪里被释放 所以我将选择 deallocation 事件 我碰巧知道这个堆栈轨迹意味着 NSManagedObjectContext 正在异步释放 保留此 blob 的对象 从而释放已使用的内存 这种技术使我能够为测试 建立一个高水位线 使其能够在内存较少的系统上运行
通过将测试 与 Instruments 相结合 我发现这个特定的测试有一些 不太理想的行为 我做了有针对性的改变 来直接解决这种行为 然后验证结果 此外 系统日志还包含 大量关于应用程序及其依赖的 系统服务的信息 如 CloudKit 进程表 和推送通知 我将在我的 MacBook Pro 和 iPhone 之间同步一篇帖子 当我在 Mac 上插入一篇新帖子 给它一个简短的标题 并将其上传到 iCloud 时 系统日志会捕获一些事件
当它与我的 iPhone 同步时 有时甚至会捕获 中间状态 系统日志会捕获相应的一组事件 在 MacBook Pro上 NSPersistentCloudKitContainer 在应用程序进程内部工作 在本例中 它是 CoreDataCloudKitDemo 当数据被写入持久存储时 它会询问名为 DASD 的系统服务 现在是否适合将数据导出到 CloudKit 如果是 那么DASD 将通知 NSPersistentCloudKitContainer 运行任务 然后 NSPersistentCloudKitContainer 将使用一个 名为 cloudd 的进程来调度工作 以将更改后的对象导出到 CloudKit 我们可以使用 Console app 观察每个进程的日志 对于应用程序日志 我们只需查找应用程序进程 CoreDataCloudKitDemo 在这里 我选择了一个 显示导出完成的选项 对于调度日志 我们希望查看来自进程 dasd 和来自应用程序特定存储的日志 在这里 我为应用程序的私有存储 选择了导出任务开始 让我们更详细地检查一下这个日志 NSPersistentCloudKitContainer 使用 dasd 创建的任务 遵循特定的格式 任务标识符由 NSPersistentCloudKitContainer 使用的特定前缀以及任务所属存储的 存储标识符组成 dasd 日志包含有关服务如何决定 任务是否可以运行的信息 影响应用程序工作能力的策略 将与最终决策一起列在日志中
最后 流程 cloudd 记录 来自 CloudKit 的信息 我喜欢根据正在处理的 容器标识符过滤这些日志 在这里 我为前面提到的导出内容 选择了相应的修改记录操作
当在接收设备上导入更改时 还有一个额外的过程需要观察 apsd 进程负责接收推送通知 并将其转发给应用程序 这会导致 NSPersistentCloudKitContainer 启动一系列类似于导出过程的任务 它询问 dasd 执行导入的时间 然后使用 cloudd 从 CloudKit 提取 所有更新的对象 并将它们导入本地存储
Apsd 在收到应用程序的推送通知时 会进行日志记录 该日志记录了许多重要的细节 日志消息包括这里的容器标识符 以及触发推送通知的 订阅名称和区域标识符 这些由 NSPersistentCloudKitContainer 管理 并将始终以前缀 com.apple.coredata.cloudkit 开头
现在 console app 很棒 但当我在 Mac 上开发时 我喜欢 使用终端中的 log stream 命令 将这些日志显示在我的 App 旁边
我为以下每个 predicate 打开一个终端窗口或选项卡 首先是应用程序 接下来是 cloudd 中的日志 这样我就 可以看到 CloudKit 服务器发生了什么 接下来 是用于推送通知日志的 apsd 最后是 dasd 这样我就可以看到 NSPersistentCloudKitContainer 为我安排的任务发生了什么 这些 predicate 还可以用于 指导 console app 中的查询
在我们使用的设备上 有很多信息可供我们使用 真正的挑战是知道使用什么工具 来发现和分析信息 仅使用 Instruments 我们就可以了解许多主题 如运行时间和内存性能等 系统日志捕获事件 这些事件描述应用程序所做的工作 以及系统在幕后为它所做的工作 开发周期的最后阶段 是收集和提供可操作的反馈 在这个部分 我将演示如何从设备收集 诊断信息 我们的目标是利用这些信息 来产生可行的反馈 并与特定的目标保持一致 这些技术可以帮助你 从任何设备收集反馈 无论是你自己的设备还是客户的设备 从设备收集诊断信息有三个步骤 首先 我们需要安装 CloudKit 日志记录配置文件 该文件允许使用日志来识别问题 并对其进行有效分类 接下来 我们将从受影响的设备 收集 sysdiagnose 最后 如果我们对设备有物理访问权 我们还可以从 Xcode 收集持久存储文件 要安装日志配置文件 我们只需访问 开发人员网站上的 Profile and Logs 页面 我可以搜索 CloudKit 配置文件 然后点击配置文件链接进行下载 在某些设备上 会显示安装配置文件的通知 然而 在 iOS 上 我们需要通过 Settings app 手动安装
在 Settings 中 我可以导航到 点击 Profile Downloaded 单元格 然后我可以点击下载的 配置文件进行安装 按照步骤完成安装 配置文件安装完成后 重启设备即可生效
一旦设备重新启动 我们就可以重现 我们想要捕获的行为 然后进行系统诊断 进行系统诊断是通过 keychord 即一系列特殊的按钮来完成的 这些在配置文件的说明页面中 进行了描述 我碰巧知道 在 iPhone 中 我们按住音量键 和侧边按钮几秒钟 然后松开 稍后 sysdiagnose 便在 Settings 中可用 查找它的说明包含在概要文件的 说明文件中 在 Settings 中 我导航到 Privacy & Security Analytics and Improvements 然后选择 Analytics Data 在日志中滚动 直到找到 sysdiagnose
如果我点击 sysdiagnose 然后点击 Share 按钮 我可以选择多种方式来分享它 例如 我喜欢将它们隔空投送到 我的 Mac 上进行分析 最后 如果可能的话 我可以使用 Device Organizer 从 Xcode 收集存储文件 我可以从这台 iPhone 上收集文件 方法是单击已安装 App 列表中的 示例应用程序 单击公开信息按钮 选择 Download Container 并将其保存到我的 Downloads 目录中
完成所有这些操作后 现在可以对系统日志 和存储文件进行分析了 我们已经讨论了 log stream 命令 但使用 sysdiagnose 时 我可以使用 log show 命令 打印出系统诊断中的日志 在这里 我复制了我们前面谈到的 apsd 日志的 predicate
log show 命令的最后一个参数 是要使用的 logarchive 如果未指定任何内容 它将显示 其运行所在机器的系统日志 这里 我指定了 system_logs.logarchive 以便读取我从 sysdiagnose 中获取的日志 例如 我可以指定一个精确的 时间范围 以关注我感兴趣的事件发生的时间
我还可以将我们前面讨论的 许多 predicate 组合起来 形成一个与应用程序相关的 所有任务的统一日志 首先是这里的应用程序日志 这里的 cloudd 日志 这里的 apsd 日志 最后是这里的 dasd 日志 这个强大的命令可以包含在 反馈报告中 也可以与队友共享 让每个人都可以专注于一组 特定的日志进行分析
在本讲座中 我们讨论了如何使用数据生成器 来探索应用程序的行为 如何使用工具和系统日志 来分析应用程序 以及如何从使用 NSPersistentCloudKitContainer 的 应用程序中提供或收集可操作的反馈
我是 Nick Gillett 很高兴为大家带来这个演讲 感谢收看 保持活跃 双手合十 祝你余下的 WWDC 之旅一切顺利
-
-
4:35 - Define a large data generator
class LargeDataGenerator { func generateData(context: NSManagedObjectContext) throws { try context.performAndWait { for postCount in 1...60 { //add a post for attachmentCount in 1...11 { //add an attachment with an image let imageFileData = NSData(contentsOf: url!)! } } } } }
-
5:07 - Testing a large data generator
class TestLargeDataGenerator: CoreDataCloudKitDemoUnitTestCase { func testGenerateData() throws { let context = self.coreDataStack.persistentContainer.newBackgroundContext() try self.generator.generateData(context: context) try context.performAndWait { let posts = try context.fetch(Post.fetchRequest()) for post in posts { self.verify(post: post, has: 11, matching: imageDatas) } } } }
-
5:33 - Sync generated data in test
func testExportThenImport() throws { let exportContainer = newContainer(role: "export", postLoadEventType: .setup) try self.generator.generateData(context: exportContainer.newBackgroundContext()) self.expectation(for: .export, from: exportContainer) self.waitForExpectations(timeout: 1200) }
-
6:35 - Expectation helper method
func expectation(for eventType: NSPersistentCloudKitContainer.EventType, from container: NSPersistentCloudKitContainer) -> [XCTestExpectation] { var expectations = [XCTestExpectation]() for store in container.persistentStoreCoordinator.persistentStores { let expectation = self.expectation( forNotification: NSPersistentCloudKitContainer.eventChangedNotification, object: container ) { notification in let userInfoKey = NSPersistentCloudKitContainer.eventNotificationUserInfoKey let event = notification.userInfo![userInfoKey] return (event.type == eventType) && (event.storeIdentifier == store.identifier) && (event.endDate != nil) } expectations.append(expectation) } return expectations }
-
7:18 - Import data model in test
func testExportThenImport() throws { let exportContainer = newContainer(role: "export", postLoadEventType: .setup) try self.generator.generateData(context: exportContainer.newBackgroundContext()) self.expectation(for: .export, from: exportContainer) self.waitForExpectations(timeout: 1200) let importContainer = newContainer(role: "import", postLoadEventType: .import) self.waitForExpectations(timeout: 1200) }
-
8:23 - Data generator alert action
UIAlertAction(title: "Generator: Large Data", style: .default) {_ in let generator = LargeDataGenerator() try generator.generateData(context: context) self.dismiss(animated: true) }
-
10:50 - Eagerly generating thumbnail in data generator
func generateData(context: NSManagedObjectContext) throws { try context.performAndWait { for postCount in 1...60 { for attachmentCount in 1...11 { let attachment = Attachment(context: context) let imageData = ImageData(context: context) imageData.attachment = attachment imageData.data = autoreleasepool { let imageFileData = NSData(contentsOf: url!)! attachment.thumbnail = Attachment.thumbnail(from: imageFileData, thumbnailPixelSize: 80) return imageFileData } } } } }
-
11:13 - Lazily generating thumbnail in data generator
func generateData(context: NSManagedObjectContext) throws { try context.performAndWait { for postCount in 1...60 { for attachmentCount in 1...11 { let attachment = Attachment(context: context) let imageData = ImageData(context: context) imageData.attachment = attachment imageData.data = autoreleasepool { return NSData(contentsOf: url!)! } } } } }
-
14:14 - Problematic verifyPosts implementation
func verifyPosts(in context: NSManagedObjectContext) throws { try context.performAndWait { let fetchRequest = Post.fetchRequest() let posts = try context.fetch(fetchRequest) for post in posts { // verify post let attachments = post.attachments as! Set<Attachment> for attachment in attachments { XCTAssertNotNil(attachment.imageData) //verify image } } } }
-
14:49 - Efficient verifyPosts implementation
func verifyPosts(in context: NSManagedObjectContext) throws { try context.performAndWait { let fetchRequest = Attachment.fetchRequest() fetchRequest.resultType = .managedObjectIDResultType let attachments = try context.fetch(fetchRequest) as! [NSManagedObjectID] for index in 0...attachments.count - 1 { let attachment = context.object(with: attachments[index]) as! Attachment //verify attachment let post = attachment.post! //verify post if 0 == (index % 10) { context.reset() } } } }
-
20:41 - Display logs using `log stream`
# Application log stream --predicate 'process = "CoreDataCloudKitDemo" AND (sender = "CoreData" OR sender = "CloudKit")' # CloudKit log stream --predicate 'process = "cloudd" AND message contains[cd] "iCloud.com.example.CloudKitCoreDataDemo"' # Push log stream --predicate 'process = "apsd" AND message contains[cd] "CoreDataCloudKitDemo"' # Scheduling log stream --predicate 'process = "dasd" AND message contains[cd] "com.apple.coredata.cloudkit.activity" AND message contains[cd] "CEF8F02F-81DC-48E6-B293-6FCD357EF80F"'
-
24:36 - Display logs with `log show`
log show --info --debug --predicate 'process = "apsd" AND message contains[cd] "iCloud.com.example.CloudKitCoreDataDemo"' system_logs.logarchive log show --info --debug --start "2022-06-04 09:40:00" --end "2022-06-04 09:42:00" --predicate 'process = "apsd" AND message contains[cd] "iCloud.com.example.CloudKitCoreDataDemo"' system_logs.logarchive
-
25:17 - Provide a predicate to `log show`
log show --info --debug --start "2022-06-04 09:40:00" --end "2022-06-04 09:42:00" --predicate '(process = "CoreDataCloudKitDemo" AND (sender = "CoreData" or sender = "CloudKit")) OR (process = "cloudd" AND message contains[cd] "iCloud.com.example.CloudKitCoreDataDemo") OR (process = "apsd" AND message contains[cd] "CoreDataCloudKitDemo") OR (process = "dasd" AND message contains[cd] "com.apple.coredata.cloudkit.activity" AND message contains[cd] "CEF8F02F-81DC-48E6-B293-6FCD357EF80F")' system_logs.logarchive
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。