大多数浏览器和
Developer App 均支持流媒体播放。
-
使用 Xcode Playground 探索软件包和项目
Xcode Playgrounds 帮助开发人员探索 Swift 和 框架 API,并为快速试验提供便笺。了解 Xcode Playgrounds 如何利用 Xcode 的现代构建系统,为资源提供改进支持,并与你的项目、框架和 Swift 软件包集成,从而提升文档记录和开发工作流程。
资源
相关视频
WWDC23
WWDC20
WWDC19
-
下载
(你好 WWDC 2020) 你好 欢迎来到 WWDC
欢迎观看《使用 Xcode Playground 探索软件包和项目》 我叫 Chris Miles 我是 Xcode 团队的工程经理 开发者喜欢使用 Xcode Playgrounds 来快速尝试新想法 并学习和探索 API 以及框架 今天 我很高兴来跟大家介绍 我们对 Xcode 12 所做的增强 它们能让 Playground 在大家的工程中 与 Swift 软件包及框架 无缝配合 在 Xcode 12 中 Playground 已经完全 与 Xcode 的新构建系统整合 这样的整合使 Playground 在配合 软件包、工程和资源方面有了许多改进 首先 我会跟大家展示现在 Playground 在 Swift 软件包中的出色表现 它是包含可运行代码的软件包说明文档的 极佳选择 然后我会展示 Playground 在导入工程目标方面的改进 比如框架和软件包依赖关系 在本视频的最后 我会展示如何在 Playground 中 使用需要构建支持的资源 比如机器学习模型及资源目录 我们首先看看 Xcode Playgrounds 如何与 Swift 软件包配合 这里能看到我有一个 Swift 软件包 叫做 NutritionFacts 我双击“Package.swift” 在 Xcode 中打开它 NutritionFacts 针对许多常见的食物 提供准确的营养信息 在食谱和美食 app 中它非常有用 NutritionFacts 很棒的一点是 作者附上了详细的说明文档 解释如何使用其 API 更棒的是 这个说明文档 写成了一个 Playground 文件 Playground 非常适合写说明文档 而且现在它们在 Xcode 12 中与软件包 还能做到无缝配合 让我们看一下
在这个导航器中 我打开 Playground 文件夹 然后选择 NutritionFacts Playground 我们看到了这个软件包的描述 接着是如何使用其 API 的详细说明文档 另外还有几个示例代码片段 由于这是 Playground 文件 我们可以运行这段代码 我们点击工具栏的“执行”选项来运行它 Playground 会执行这段代码 在右边我们可以看到一个实时视图 这是软件包提供的视图 它以我们熟悉的格式列出了营养成分表
在编辑器中 我们看到一个例子 即利用标识符检索一个项目 然后获取与之相关的营养信息 Playground 帮助我们理解了代码 因为它在右侧的结果栏 显示了每一个语句的结果
如大家所见 Xcode 12 中的 Playground 可以和软件包无缝配合 之所以能实现这一点 靠的是 Playground 与新构建系统的融合 该系统知道如何构建软件包 并使其可以导入到 Playground 代码中 我推荐大家在下次需要写说明文档时 或为你们的 Swift 软件包写教程时 使用 Playground 大家不要忘记利用它的一个优势 那就是用户可以在 Playground 中 运行示例代码 并看到实时的结果 接下来我要跟大家展示 Xcode 12 是怎样便于 Playground 使用工程中的框架和软件包依赖关系的
我要用到一个叫 Fruta 的工程 我们在 Xcode 中打开它 并在模拟器中运行工程 Fruta 是一个 iOS 和 Mac app 它可以用来浏览和订购奶昔 你们甚至可以用 Apple Pay 购买奶昔的制作食谱 这样你们就可以在家自己做奶昔了 我们在模拟器中看看这个 app 我们看到了一个可供选择的奶昔列表 如果我们选择一个奶昔 我们可以看到 一个用 Apple Pay 购买的选项 向上滚屏之后 我们可以看到这种奶昔的部分配料 我们选择一种 看看详细的插图 然后轻点 “i” 按钮来查看 这个配料的营养成分 你们可能会认出来这正是 NutritionFacts 软件包提供的视图 本工程在那个软件包中存在依赖关系 大家在左边的导航器中可以看出来
即使该软件包是工程的依赖项 软件包中的任何 Playground 也仍然可供使用 让我们打开这个软件包看看 然后打开我们前面已经打开的 NutritionFacts playground 在这里我们看到了同样的内容 和前面一样 我们可以执行这个 Playground 查看结果
因此软件包中的 Playground 不论是像我们开始的那样 直接打开软件包 还是像现在作为工程的软件包依赖项 都可以运行 不过要记住 和所有软件包依赖项中的所有文件一样 Playground 会变成只读的 这个工程还包含一个框架 叫做 UtilityViews 我们来看看工程编辑器 这里能看到 UtilityViews 的框架目标 现在在 Playground 中使用框架 比以往都要容易 只要它们都在同一个工作区就可以 我给大家演示一个例子 在导航器中 我打开 Playground 文件夹 我们会看到一个 Playground 叫 SmoothieLab 我们打开 SmoothieLab 注意顶栏会有一些构建活动 这是因为 Xcode 12 中有个 Playground 新选项 叫 Build Active Scheme 我打开这个 inspector 让大家看看 我们看到在 Playground 设置中 这个新选项在设置的底部 当启用 Build Active Scheme 时 它让 Xcode 为当前选择的方案 自动构建所有目标 并使任何模块均能导入至 Playground 中 因此当我们打开 Playground 时 Xcode 已经为当前选中的方案 构建好了所有目标 使得 Playground 可以导入 UtilityViews 框架 以及 NutritionFacts 软件包 SmoothieLab 是一个实用型 Playground 用来设计新的奶昔 我们可以选择一些原材料 以不同的数量将其组合 然后检查它们的营养价值 我们甚至可以用图表来解析其热量组成 只需要使用 UtilityViews 框架 提供的图表视图 我们来运行 Playground 看一看 这种奶昔的热量组成
在实时视图下 我们可以看到 热量成分的解析图表 我们通常希望把脂肪成分 控制在 25% 以下 所以我们点击图表看一下百分比 本例中约占 33% 所以我们可能要调低奶昔中花生酱的用量 不过这个稍后再说 大家可以看到 Playground 是一个很方便的工具 可以帮我们组合配料 设计出各方面均衡的奶昔 至少是营养均衡的奶昔 我们还没有设计出可以 计算奶昔口味如何的算法 但我没意见 因为品尝的步骤 是我在奶昔设计中最喜爱的环节 现在 playground 可以无缝地 与工程目标对接 比如说框架和软件包 Xcode 会代劳构建这些目标 这样就可以将它们导入到 你们的 Playground 代码中 只要确定你们启用了 Build Active Scheme 有个好消息是 对于新的 Playground 该选项是自动启用的 另外还要保证 你们想导入的模块 要么应是活动方案中的一部分 要么对该方案构建的另一个目标 有依赖关系 在 Xcode 12 中的另一项极大的增强是 Playground 可以保留完整的构建日志 我们来看看 SmoothieLab 的构建日志 我们切换到报告导航器 在最顶端 我们可以看到 SmoothieLab Playground 的构建日志 我们选择它 打开构建日志 在构建日志中 我们能看到构建期间的全部详情 如作为活动方案的组成而构建的目标详情 以及为了支持 Playground 而构建的模块 包含 Playground 内的 编译后的源代码以及资源 对于诊断构建故障 构建日志极其重要 我们很高兴看到它 能在 Playground 中使用 Playground 与 Xcode 的 新构建系统的结合还有一个好处 那就是由构建系统所支持的一切资源类型 现在也在 Playground 中得到支持 我在此展示一个样例 在 Playground 中使用一些 需要构建系统支持的资源
我们回到 Fruta 工程 我想对 Fruta 增加一个特色功能 让用户可以把自己的 iPhone 摄像头 对准其最喜欢的水果 然后 Fruta 会像变戏法一样 向用户提议 与看到的水果相配的奶昔 要做到这点 我要使用机器学习 来进行对象检测和图像分类 有一些现成的分类工具已可供使用 所以我想用几张样例图像 试用其中一个工具 看看它能不能胜任这份工作 我要使用 Xcode 中的一个 Playground 来试验 首先 我们先找几张测试用的 样例水果图像 我们正好在 Fruta 工程中 已经有几张可用的图像 我们切回到模拟器并返回 还记得我们之前看过这张橙子图像吗? 如果我们退出这一屏 就能看到其他配料的图像 我们先在工程里找到这些图吧 我先把模拟器隐藏起来 现在我们用不到它了 展开共享文件夹 然后我们看一下资源目录 我们能看到 app 图标、一些配色 然后在 Ingredients 文件夹中 我们能看到我们加入奶昔的 各种配料的图像 我想这可以作为一个测试机器学习模型的 优秀样例数据集 所以我们先建立一个副本 按住 Option 键并把它拖动到桌面上
现在我们不需要保持这个工程打开 所以我们关闭工程 并创建一个新的 Playground 我们选择“文件”然后选“新 Playground”
我们使用一个 iOS 的空 Playground
我们给它起名叫 ML Fruta 并把它保存到桌面
我要扩大窗口的尺寸 增加一些工作空间 现在 我们把资源目录拖曳到 Playground 的资源文件夹中 注意 Playground 会替我们 编译资源目录 让我们可以直接从代码中 访问这些资源 我们可以使用 UIImage(named 参数
指定在资源目录中文件夹的名称 以及一张图像的名称 现在我们执行这个 Playground 看看结果如何 在结果中 我们看到图像的宽度 如果使用快速查看打开图像 我们就能看到预览 看来没问题 我们可以访问 这个资源目录中的图像 现在我们只需要一个机器学习模型 我要切换到 Safari 浏览器 我前面在用它浏览 Apple 开发者网站上的机器学习区 在 Core ML 模型的页面上 我们能看到多个图像分类 和对象检测模型 我想试试这个名叫 YOLOv3 的模型 它可以检测场景中的多个对象 并将其分别进行分类 我想这很适合于用摄像头对准一碗水果 并把拍到的每一种水果都识别出来 那么我们点击 View Models 就可以在上方下载到全精度的模型 为节省时间 我已经下载好了 现在最小化 Safari 浏览器
我来把模型从桌面上 拖曳到 Playground 的资源区 Xcode 会自动替我们将模型编译好 在它编译的时候我们来选择模型 以在编辑器里看到它的详细信息 有个很重要的细节是模型类 它显示在上方 它能告诉我们这个类的名称 该名称由构建系统自动生成 以在我们的 Swift 代码中使用 现在我们要用到它 回到 Playground 我们加入代码来导入 CoreML 然后我要把变量命名为 yoloModel 然后使用自动生成的类名称 YOLOv3 来创建一个有默认配置的新实例 然后我们来获取模型的属性 我们运行 Playground 看看结果
我们得到了一个模型的文本描述 可以在这里预览 看到输入数据 输出数据 以及其他属性的详细内容 很不错 我们有了机器学习的模型 还有用于测试的图像 只要把它们结合起来就可以了 做到这一点有个简单的方法 就是使用 Vision 框架 我要替换这段代码 改成导入 Vision 框架 我要定义一个数组 包括三种开始测试时使用的配料 然后我们可以使用我们的 yolo 模型 建立一个 Vision 框架的 CoreML 模型的实例 有了它 我们就可以创建一个 CoreML 请求 用它来为图片的机器学习发起请求 我想先构思一下各个请求的结果 就是先展示每张图片 并画出一个矩形 框起图像内检测到的任何对象 为了实现这点 我调用了一些 过去我用过的代码 我用快速查看让你们看一看 它定义了几个结构体 用于保存我们所做的对象检测结果 以及一些我们用来显示图像 并为图像中检测到的 任何对象画矩形框的 SwiftUI 视图 我们在此不深入讨论这些代码 但我们会将它们提供在演讲的网站上 把支持代码拖曳到 Playground 的源代码区后 Xcode 会将其编译 现在我们就可以利用这些代码了
我们对配料名称轮流进行识别 为资源目录中的每一项获取图像 然后我们创建一个对该图像的 图像请求处理程序 我们用它来执行我们的机器学习请求 之后我们只需要传递结果 每个结果都是一个 RecognizedObjectObservation 我们枚举这些观测 获取具有最高信心值的标签 以及图像中该对象的边界框 并将其返回为一个已识别对象
最后 对于每一幅图像 我们以一个 对象检测结果的形式返回结果 我们来预览一下最下方所得到的结果 Xcode 会轮流处理三幅图像 然后我们就可以预览结果 我们已经得到每幅图像的处理结果 下面要让结果可视化 我们可以设置一个 Playground 实时视图 将其置于 RecognizedObjectVisualizer 并传递结果 我们继续运行
在右侧我们可以看见对象检测的结果 我们传递的第一张图像是一根香蕉 橙色字样写着“香蕉” 它被以 100% 的准确度检出 这是个很不错的结果 第二幅图像是三个橙子 我们能看到三个橙子 以很高的信心值被检出 这个结果很不错
第三张图像是一瓶杏仁奶 我们可以看到机器学习模型 检测到了瓶子 并给出很高的信心值 不过并未将其详细分类成 一瓶牛奶或一瓶杏仁奶 这是因为这个模型接受过 检测瓶子的训练 但是它没有接受特别训练 去检测瓶装牛奶或者瓶装杏仁奶 我觉得这倒是没问题 就这个模型本来接受的训练而言 它的表现很出色了 我想我们的 app 可以使用这个模型 下一步就是继续深入训练这个模型 使用在我们的应用场景中想要检测的 各种原料作为训练数据 要了解如何训练机器学习模型的更多内容 我建议大家看一看 WWDC 2019 的一期演讲 即《在 CreateML 中训练对象检测模型》
这个例子演示了如何在 Playground 中 使用需要构建系统支持的资源 我们来总结一下本次演讲中涵盖的内容 在 Xcode 12 中 Playground 与新构建系统实现了完全整合 当启用新选项 Build Active Scheme 时 当前选中的方案内的所有目标 都会被自动构建 并可以在相同工作区内的 Playground 中 进行导入 这样任何框架或者 Swift 软件包都可以 导入到你们的 Playground 代码之中
一切构建系统所能识别的资源类型 现在都可以在 Playground 中使用 比如说资源目录和机器学习模型 如我演示的那样
完整的 Playground 构建日志 现在可在报告导航器中查看 让诊断 Playground 构建期间的故障 变得更加轻松
Playground 非常适合记载含有 可运行代码的软件包和框架 也不要忘了 Playground 是一件用来 快速以代码记录新想法或者 编写实用代码的称手工具 我期待着了解 Playground 如何帮助你们 让你们的软件包和工程做得更好 感谢大家的观看
-
-
8:53 - Playgrounds and resources Demo: Part 1
import UIKit let image = UIImage(named: "ingredient/orange")
-
10:18 - Playgrounds and resources Demo: Part 2
import CoreML let yoloModel = try YOLOv3(configuration: MLModelConfiguration()).model
-
10:54 - Playgrounds and resources Demo: Part 3
import UIKit import CoreML import Vision let ingredientNames = [ "banana", "orange", "almond-milk", ] let yoloModel = try YOLOv3(configuration: MLModelConfiguration()).model let model = try VNCoreMLModel(for: yoloModel) let request = VNCoreMLRequest(model: model) {_,_ in }
-
11:24 - Recognized Object Visualizer
import Foundation import SwiftUI import UIKit // MARK: Model /// The result of object detection on an image. public struct ObjectDetectionResult : Identifiable { public var name: String public var image: UIImage public var id: String public var objects: [RecognizedObject] public init(name: String, image: UIImage, id: String, objects: [RecognizedObject]) { self.id = id self.name = name self.image = image self.objects = objects } } /// An object recognized by an image classifier. public struct RecognizedObject : Identifiable { public var id: Int public var label: String public var confidence: Double public var boundingBox: CGRect public init(id: Int, label: String, confidence: Double, boundingBox: CGRect) { self.id = id self.label = label self.confidence = confidence self.boundingBox = boundingBox } } // MARK: Views public struct RecognizedObjectVisualizer : View { public var results: [ObjectDetectionResult] public var imageSize: CGFloat = 400 public init(withResults results: [ObjectDetectionResult]) { self.results = results } public var body: some View { List(results) { result in Spacer() VStack(alignment: .center) { RecognizedObjectsView( image: result.image, objects: result.objects ) .frame(width: imageSize, height: imageSize) Text(result.name.capitalized) Spacer(minLength: 20) } Spacer() } } } struct RecognizedObjectsView : View { var image: UIImage var objects: [RecognizedObject] var body: some View { GeometryReader { geometry in Image(uiImage: image) .resizable() .overlay( ZStack { ForEach(objects) { object in Rectangle() .stroke(Color.red) .shadow(radius: 2.0) .frame( width: object.boundingBox.width * geometry.size.width / image.size.width, height: object.boundingBox.height * geometry.size.height / image.size.height ) .position( x: (object.boundingBox.origin.x + object.boundingBox.size.width / 2.0) * geometry.size.width / image.size.width, y: geometry.size.height - (object.boundingBox.origin.y + object.boundingBox.size.height / 2.0) * geometry.size.height / image.size.height ) .overlay( Text("\(object.label.capitalized) (\(String(format: "%0.0f", object.confidence * 100.0))%)") .foregroundColor(Color.red) .position( x: (object.boundingBox.origin.x + object.boundingBox.size.width / 2.0) * geometry.size.width / image.size.width, y: geometry.size.height - (object.boundingBox.origin.y - 20.0) * geometry.size.height / image.size.height ) ) } } ) } } }
-
11:48 - Playgrounds and resources Demo: Part 4
let results = ingredientNames.compactMap { ingredient -> ObjectDetectionResult? in guard let image = UIImage(named: "ingredient/\(ingredient)") else { return nil } let handler = VNImageRequestHandler(cgImage: image.cgImage!) try? handler.perform([request]) let observations = request.results as! [VNRecognizedObjectObservation] let detectedObjects = observations.enumerated().map { (index, observation) -> RecognizedObject in // Select only the label with the highest confidence. let topLabelObservation = observation.labels[0] let objectBounds = VNImageRectForNormalizedRect(observation.boundingBox, Int(image.size.width), Int(image.size.height)) return RecognizedObject(id: index, label: topLabelObservation.identifier, confidence: Double(topLabelObservation.confidence), boundingBox: objectBounds) } return ObjectDetectionResult(name: ingredient, image: image, id: ingredient, objects: detectedObjects) } results
-
12:33 - Playgrounds and resources Demo: Part 5
import PlaygroundSupport PlaygroundPage.current.setLiveView( RecognizedObjectVisualizer(withResults: results) .frame(width: 500, height: 800) )
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。