大多数浏览器和
Developer App 均支持流媒体播放。
-
统一类型标识符 - 再介绍
是否想过系统如何决定应该用哪个 app 打开指定的文件?我们一起来探索统一类型标识符框架,它可帮助简化在 app 中支持标准或专有文件格式的流程。你将学习如何使用新框架和 Xcode 声明 app 所支持的类型,了解如何在采用 UTType 时提高性能,并查看支持 UTType 的最新平台 API。
资源
相关视频
WWDC23
WWDC22
-
下载
大家好 欢迎来到 《统一类型标识符的再介绍》 我叫 Jonathan Grynspan 在 Apple 的 App 研发与参与小组工作 我们上次讨论统一类型标识符 还是在 WWDC 2004 上 那是很久以前的事了 但我觉得就像是昨天的事一样 Apple 2004 年的一系列产品都堪称经典 比如 全铝金属设计的 PowerBook G4 拥有绚丽色彩的 iPod Mini 以及有神奇家庭影院体验的 iMac G5 Apple 一直很出色 但必须承认 2004 年 确实是很久以前的事了
因此 今天我们将从 Apple 的操作系统 如何确定文件类型开始 介绍一些基础知识
然后 我们将探讨 如何告知系统未知的新文件类型 以及如何进行处理
最后 我们将在 iOS 14 和 macOS Big Sur 中探索一些新的 API 使处理文件类型更为轻松 不管你是否刚接触这一领域 我们都有适合你的内容 所以请继续观看
让我们来看看 任何人电脑上都可能存在的一个文件: 一张猫咪使用 iPad Mini 的照片 当你将该图片保存到磁盘时 会生成所谓的普通文件 这是 POSIX 术语 表示存储在磁盘上的字节序列 稍后 当你想再次打开这张照片时 双击该文件即可开启 照片就会在如预览之类的 图像查看器中打开 但是系统如何知道这是图像 在本例中将其保存为 JPEG 格式 而不是存为文本文件或 MP3 音频 或 Pages 文稿文档呢? 文件是字节序列 所以你可能会认为 当系统需要知道文件类型时 会打开文件 并读取文件内部的字节来得出结论 实际上 系统几乎从来没有这样做过 因为这种做法消耗巨大 并且需要大多数进程没有的读取权限
实际上 操作系统的大部分决策 都基于文件的路径扩展 这就是在句点字符后面 标准情况的子字符串 只要其不包含任何空格或控制字符 Apple 平台上 我们通常不会向用户显示 可识别的路径扩展 但他们仍然存在 如果我们显示此文件的路径扩展 我们可以看到是 J-P-E-G 所以这是一个 JPEG 图像 那么 .jpeg 意味着 JPEG 图像 对吗? 没错 但是 JPEG 图像还有其他路径扩展 .jpg 和 .jpe 是最常见的 然而在网络上 情况就不同了 网络服务器通常不会通过 其路径扩展来识别文件 而是使用一种 称为 MIME 类型或 媒体类型的东西
对于 JPEG 则为 image/jpeg 当然 并非都是这么简单的 有些服务器使用的 是常见但非标准的 image/jpg MIME 类型 这是五个不同的元数据片段 都代表着完全相同的东西 在 Apple 的平台上 这是没关系的 因为我们使用一个 称为统一类型标识符的单个字符串 来规范地标识此文件格式
对于 JPEG 图像 统一类型标识符为 public.jpeg 这个字符串表示所有的 JPEG 图像 无论是本地的还是网络上的
文件类型的一个有趣特性是 其存在于层级结构中 每个 JPEG 图像也是一个更抽象的图像 PNG 图像、TIFF 图像等也是如此 在讨论统一类型标识符时 我们说 JPEG 类型符合更抽象的图像类型
这种顺应性结构默许多级继承 仔细想想 这有点像 Swift 和 Objective-C 中的 协议工作方式 具体的类或结构体 可以从任意数量的协议中继承 我来演示一下什么意思
这是 public.jpeg 这是 public.image 一种描述所有图像文件格式的 抽象类型 但所有的图像文件格式 都可以表示为字节序列 因此 public.image 顺应于 public.data 这是一个非常抽象的类型 可表示磁盘上的任何普通文件 或者任何一种字节序列 不管其存储在哪里 接着 public.data 顺应于最后一种类型 public.item 所有的文件系统对象 不管是文件、文件夹、符号链接 还是更复杂的东西 如 POSIX 管道 都由 public.item 表示
就像我们可以沿着层级结构往上走一样 也可以往下走 除了 JPEG 之外 还有其他图像格式 比如 public.png 和 public.tiff 这些也是 public.image 的子类型 但他们不属于 public.jpeg 顺应性层级结构的一部分 只是同级关系 如果我们向上移动一个层级 聚焦于 public.data 我们可以看到有许多顺应类型 因为计算机上几乎所有东西 都可以由字节序列表示 在这里 我们看到 public.audio 一种描述可听声音的抽象类型 以及 public.text 一种描述易读文本的抽象类型 他们是 public.data 的子类型 也是 public.image 的同级类型
我前面提到过 统一类型标识符 可以实现多级继承 图像也是如此 因为图像是数据 但同时也是用户关心的内容 用户可能希望将其上传到 iCloud Drive 或通过隔空投送共享
因此 public.image 也顺应于 public.content 其在文件系统中不代表任何具体的内容 但确实告诉我们 我们应该将其子类型 视为对用户重要的事物
我着重于 统一类型标识符如何表示文件方面 就用户而言 这或许是最常见的用法 但我们在平台上使用这些 也有其他目的 例如 我们还将其用作 粘贴板内容的规范类型 毕竟 如果可以将其保存到磁盘 则也可以进行复制粘贴 我们还将其用于与文件无关的层级结构 这算是 Apple 的独家了 但我们也使用统一类型标识符 来识别我们不同型号的硬件 例如 所有 Mac 机型 均顺应于 com.apple.mac 并且如果我们假设一下 甚至可以使用统一类型标识符 来指代与计算机无关的层次结构 就像生命之树一样 这就是统一类型标识符 以及我们在平台上使用的方法 许多 app 创建和维护自己的数据格式 并且这些格式应具有自己的独特类型 创建新类型时 如何将其添加到类型层级结构中? 如果你使用的是系统声明的类型 则无需执行太多操作 我们在/系统/资料库/ CoreServices 中 称为 CoreTypes 的 bundle 中 包含了大量类型
你的 app 可以引用该 bundle 的 Info.plist 中任何统一类型标识符 而无需进行其他更改 但是对于你自定义的类型 或要从其他 app 中借用的类型 你可能需要将其告知系统 为此 你需要创建自己的统一类型标识符
创建自己的统一类型标识符时 需要遵循一些命名规则 首先 统一类型标识符 始终是不区分大小写的 ASCII 并且始终是反向 DNS 例如 com.example.file 最好使用一些更具描述性的标识符 om.example.file 无法真正简化调试 com.example.imagetemplate 或 com.example.encrypteddatabase 或与之类似的会更好
Apple 在标识符中 保留了一些前缀或命名空间 不要使用这些命名空间 创建你自己的标识符 如果这样做的话 可能会被系统忽略
首先是“public” 这是 Apple 用来声明标准类型的 如果我们缺少标准类型 请通过反馈助理告知我们
dyn 是 dynamic 的缩写 是预留给操作系统的 在生成兼容性垫片时供其使用 它们通常是不透明字符串 并且可以在 OS 版本之间进行更改 所以不要硬编码其值 实际上 现在这种情况很少见 通常只有在遇到 路径扩展无法识别的文件时 才能看到这些
com.example 是为模板、示例和示例代码等保留的
最后 com.apple 保留供 Apple 使用
在向 app 添加系统将识别的类型时 需执行两个主要步骤 第一步是声明类型 声明类型就是表示你的 app 在说: “此类型存在” 但并不表示你可以打开该类型 如果你确实想打开一个类型 则需要支持该类型 你的 app 则会说: “无论谁声明了该类型 我都能将其打开” 这两个步骤是不同的
声明类型时 你需要决定是导入还是导出 如果你使用的类型 是由其他人创建或设计的 或者主要用于其他 app 则通常应该导入该类型 这就是告知系统: “此类型存在 以下是其有关信息” 但如果创建该类型的 app 已被安装 则该 app 可以提供更多权威信息
另一方面 如果你创建了这个类型 或是专门为你的 app 所使用 则可以将其导出 这就是告知系统:“我对此类型具有权威” 最后 如果你使用的类型 作为核心类型的一部分随系统附带 则无需导入或导出 系统已经提供了声明 你可以即刻使用类型的统一类型标识符
我将向你展示如何在 app 中声明类型 以及如何告知系统你支持将其打开 那么让我们看一下 Xcode 我正在努力开发一种 全新的基于 web 体验的餐饮服务 该 app 可以读取我们设计的 基于 JSON 格式保存的餐厅菜单 我们希望能够在我们的 app 中 打开这些菜单文件 因此我们首先需要为其声明一个类型 因为我们自己创建了这种类型 并拥有此格式 所以导出类型是顺理成章的 我们在 Xcode 中要做的第一件事 就是选择项目本身以显示其设置 我们要选择与我们 app 相对应的目标 现在我展示的是 iOS app 但对于 macOS app 也是一样的 然后我们将切换到信息标签 该标签表示 app 的 Info.plist 文件的内容 展开导出的类型标识符一栏 点击该栏底部的“添加”按钮 以添加新类型 首先要添加的 是我们自定义的统一类型标识符
在本演示版本中 我们将使用 com.example.restaurantmenu
请记住 com.example 是 Apple 保留 用于示例和演示的 通常 你需使用自己拥有的反向 DNS 域名 例如 Claris 公司会使用 com.Claris 而 Beats 公司则会使用 com.beatsbydre 现在我们要考虑到顺应原则
表示文件系统中文件的类型 如果是普通文件 即字节序列 则需顺应于 public.data 如果是操作系统应当视为文件的目录 则需顺应于 com.apple.package 这种类型将使用 Swift 的 JSON 编码器 保存到磁盘 这意味着它是一个字节序列 代表是普通文件 也就是 public.data
看来我们得做个决定了 因为这些文件都将包含 JSON 数据 所以任何可以读取 JSON 文件的东西 都可以读取这些文件 这意味着类型也可以顺应于 public.json 对于某些文件类型来说 这是一个非常有用的属性 因为对其进行手动编辑的能力非常重要 但对于其他类型而言 这只是一个执行细节 我们将为此类型 添加与 public.json 的顺应性 你可以在创建类型时 跳过这些执行细节 如果你不希望其他开发人员 依赖这些实现细节 或者如果你认为类型格式 以后可能会更改的话 这种文件类型是用户认为 值得直接交互的文档或内容
因此 我们还要确保其 与 public.content 顺应
接下来 我们需要确定文件扩展名 以与我们的类型相关联
此扩展会将类型绑定回文件系统中 并允许系统识别出 适当命名的文件具有此类型 我们可能会倾向于使用三个字符的扩展 但自 1995 年以来 没有一个主要平台供应商 要求使用三个字符的扩展 我们在这里有很大的发挥空间 避免与其他开发人员的类型声明冲突 是很重要的 因此 我们将使用较长的扩展名 restaurantmenu 请注意 我们没有在此处加点 操作系统会在创建文件名时 为我们添加上 最后 这也是个好机会 为此类型输入一个易于人们理解的名称 由于我们的 app 简单地称为餐厅 并且我们选择了 restaurantmenu 为扩展 因此我们将其简化并命名为餐厅菜单
此字符串可在 InfoPlist.strings 文件中进行本地化 在本地化时 使用我们刚刚键入的字符串 作为密钥 现在我们已经声明了类型 接下来我们需要告知系统 我们的 app 可将其打开 展开文档类型一栏
点击该栏底部的添加按钮 以添加新的支持类型 这部分比导出类型标识符简单 我们只需指定 app 支持的统一类型标识符列表 考虑到灵活性 我们可以在此处为单个条目指定多个类型 也可以为每个条目 指定一种类型 我个人更喜欢每个条目制定一种类型 但这主要是个人偏好问题 比如是用 tab 键还是空格 总之 我们将统一类型标识符 添加到该字段
我们还应将此条目的处理程序级别 设置为“Owner” 这一步不是必需的 但强烈建议这样做 因为这可以帮助系统智能地 为给定工作选定合适的 app 由于是我们创建了此种类型 所以我们是所有者 在 macOS 上 还有一个附加的规则字段 可让我们指定是否可以 用编辑器或查看器打开文件 编辑者可以打开和保存给定类型的文件 而查看器只能将其打开 由于我们的 app 可以读取和 编写这些文件 因此我们要指定编辑器 我们已经编辑完信息栏 现在我们的 app 可以打开餐厅菜单文件了 我们需要编写一些 SwiftUI、UIkit 或 AppKit 代码 来处理系统传递给我们的文档 有关在 SwiftUI 内 构建基于文档的 app 的更多信息 请查看 WWDC20 中的 《在 SwiftUI 内构建基于文档的 app》 使用 UIkit 和 AppKit 构建 基于文档的 app 的说明文档 可在 developer.apple.com 上在线获得
我们 app 在 App Store 上有个竞争对手 Compy’ s Food 许多餐厅老板都使用该 app 并且已将菜单保存为 该 app 拥有的文件格式 我们希望可支持读取这些文件 该类型是别人创建的 我们只是借用 所以我们需要导入而不是导出 让我们回到 app 目标的信息标签 然后选择导入类型标识符 请注意 由于我们创建了一个 新的 SwiftUI app 因此我们在此处已有示例类型的现有条目 我们可以用自己的数据替换表单的内容 这就是我要做的 如果我们的目标中没有现有的示例类型 则当然可以使用下面的添加按钮 添加一个新类型
我来填一下 Compy's Food 指定的信息栏 当然 这里的一些细节仅是示例 如果要添加对真正竞争对手类型的支持 则一定要使其与 Info.plist 文件中 包含的内容相匹配
由于此类型是导入的 因此会告知系统 Compy's Food app 生成的文件种类 但如果用户安装了该 app 则系统可以选择其声明 该声明应以更具权威性的方式导出
现在 我们要添加对打开这些文件的支持 以便当用户想要打开其中一个文件时 系统会选择我们的 app 我们将回到文档类型部分
我们将为 Compy's Food List 创建一个单独的条目 并添加新的类型标识符
这个条目与其他的略有不同 因为我们不是该类型的所有者 我们需将处理程序等级设为“Alternate”
因为我们的 app 可以读取 但不能编写这些文件 所以我们要在 macOS 上 指定一个查看器角色 系统现在已知我们可以打开该文件 但如果用户安装了 Compy's Food 我们可能还不是最佳选择 因为这个 app 才是文件类型的所有者 我们在平台上要以礼待人 所以尊重类型所有权是很重要的 虽然我们都知道我们的代码 比竞争对手的要好得多 就像我们自己的餐厅菜单类型一样 我们需要编写一些 Swift 或 Objective-C 才能在我们的 app 中实际读取这些文件 请访问 developer.apple.com 了解更多信息 现在我们已经声明了一个类型 并告知系统我们支持该类型 那么现在该在代码中使用该类型了 本部分内容假设你至少大致熟悉 Core Services 框架中 统一类型标识符的 API 这里我们有一个小程序 用于遍历目录或一组文件 URL 它获取每个表示文件的统一类型标识符 然后打印该类型标识符的本地化描述 如果文件是图像 则将其绘制到某处 如果是音频片段 则将其在用户的扬声器 或 AirPods 上播放 这里立刻出现了一些明显的问题 我们必须将字符串显式转换为 CFString 才能与全局 UTType 函数一起使用 我们必须手动管理 从 UTTypeCopyDescription 获得的 CFString 的生存期 但很高兴地跟大家汇报 在 iOS 14 和 macOS Big Sur 中 我们引入了全新的统一类型标识符框架 该框架将一流的 Swiftiness 引入统一类型标识符中 我们以前使用 cfstring 表示类型标识符 现在可以使用 UTType 表示类型及其属性 让我们看看这段代码 并将其转换为使用新框架
我已经导入了该框架 以访问其包含的 API 现在将类型标识符 URL 属性 更改为 contentType 其值是新结构体 UTType 的实例 该结构体将统一类型标识符 和与之相关的元数据一起封装 接下来 我们将修复这个未保留的值
UTType 具有本地化的描述属性 像这样的属性是处理类型属性 和全局函数的更自然的方法 此属性的值与 你在 Xcode 中输入的描述字符串相对应 并且在本地化 app 时已本地化
接下来 让我们看看另一个全局函数 UTTypeConformsTo
此函数采用 CFString 这需要显式强制转换 当然 UTType 具有顺应成员的方法 但 .image 和 .audio 是什么?
还记得这张幻灯片吗? 在设计这个新框架时 我们查看了系统声明的类型集 以及旧 API 中公开的 CFString 常量集 并将其作为命名常量引入到新框架中 新框架中定义了超过 120 个这样的常量 涵盖了最常用的系统定义类型 当然 在本讲座的前面 我们在 app 中声明了一些类型 如果我们也可以为其公开常量 那不是很好吗?
今天我提供的都是有效信息 因为你完全可以做到这些 我们具有用于为你导出类型 和导入类型声明命名常量的 API 两者含义略有不同 导出类型是说: “我拥有此类型 我创建了此类型 我的 app 是该类型的权威” 所以我们就为你创建一个 UTType 的实例 并确保你了解有关此类型的所有信息 导入类型是说: “我知道这种类型 但其他人可能比我了解的更多” 并且该类型的所有者对类型的声明 可能与你的声明不同 如果发生这种情况 我们可以在最常见的用例中 即时替换该类型 不过我们需要你的配合 与其将导入的类型声明为常量 不如是将其声明为静态计算变量 这样 如果声明因 app 安装而发生更改 你会自动获取更新的类型
我们已在 Xcode 中内置了 对检测声明类型问题的支持 我们可以检测 你的 Info.plist 是否缺少 导出或导入的类型 或者是否使用了错误的 常量初始值设定项 我们会不断努力改善支持 你的意见非常宝贵 所以请提供反馈 许多框架在 iOS 14 和 macOS Big Sur 中支持我们的新 API Foundation 具有我刚刚展示的 URL 属性 还具有用于基于字符串 或 URL 和 UTType 实例 生成推荐文件名的实用程序
SwiftUI 非常支持 UTType 粘贴板和拖放 API 如 onDrop 事件处理程序可以专门使用
SwiftUI 对基于文档的 app 的新支持 是为了使用 UTType 我建议去查看 WWDC 20 中的 《在 SwiftUI 内构建基于文档的 app》 以了解更多信息
使用 AppKit 时 NSWorkspace 会允许 获取任何 UTType 的图标 NSOpenPanel 和 NSSavePanel 支持 UTType UIkit 支持 在显示文档选择器和 文档浏览器时使用 UTType Core Spotlight 允许 从 UTType 创建属性集
在此版本中 有许多不支持 UTType 的 API 你可能会发现自己需要与之进行交互 这并不难 如需将统一类型标识符传递给 API 请获取 UTType 的标识符属性 并将其强制转换为 CFString 如果要将统一类型标识符 强制转换回 UTType 请将其从 CFString 强制转换为 String 并使用该字符串初始化 UTType 这是一个失败的初始化器 所以不要忘记检查空值
在 Objective-C 中有点复杂 但思路是一样的 将 UTType 的标识符转换为 CFStringRef 或将 CFString 转换为 NSString 并从中创建 UTType 今天我们已经了解了 系统如何从其元数据中得出文件类型 以及 Apple 如何使用统一类型标识符 封装该信息 我们学习了如何声明和使用 我们自己的类型 以及如何与可能在 多个 app 之间共享的类型进行交互
我们还引入了一个 新的统一类型标识符框架 该框架提供了 一种现代的、面向对象的方式 来与类型进行交互
我期待看到你使用这些知识和新框架 去构建你的 app 谢谢
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。