历届 WWDC 苹果在研发效率上的改动都让开发者眼前一亮。今年虽然头戴式设备 Vision Pro 占据了大部分 Sessions,但苹果工程团队在 Xcode、构建系统、调试、语言能力等研发效率方向也做出很多更新。其中最让人关注的三个点:
-
新链接器 ld-prime 的发布,苹果工程师声称比 ld64 快 5 倍。
-
Mergeable libraries:Rlease 构建中使动态库获得类似于静态链接的应用程序启动时间,而在 Debug 构建中不会损失动态链接的构建时间。
-
使用 os_log 在 Debugger Console 获得优秀的日志展示。
Xcode IDE
概览
-
各平台运行时被分成单独的安装。当你从开发者网站下载 Xcode 或者首次启动 Xcode 时,可以选择你要开发的平台,有效的减小了包体积。并且你可以随时添加或移除平台运行时。详见安装和管理模拟器运行时。
-
在Xcode项目设置中访问和配置你的团队通过 App Store Connect 授予的功能。参见功能。
-
新的集成菜单来暂存和提交源代码仓库的更改,并创建和管理你的 Xcode Cloud 工作流程。
-
根据你的应用和应用的三方 SDK 中的隐私清单生成应用的隐私报告,开发人员可以使用 Xcode 来创建一个隐私报告,汇总应用程序和三方 SDK 中所收集的数据信息。。详见在隐私清单中描述数据使用。
-
验证项目中 XCFramework 的代码签名。如果签名发生变化或被删除,Xcode 构建系统进行报错。详见验证XCFramework的来源。
示例:新的 Quick Actions 菜单
通过快捷键 Cmd shift + A 呼起 Quick Actions,可以执行多种操作比如说断点操作、导航栏、Refacotr。
Related sessions from WWDC23
Session 10165: What’s new in Xcode 15
Code and build system
概览
-
更有效地使用代码补全。Xcode 使用更多的输入源进行代码补全,根据上下文进行补全优化,并改进了函数参数显示。
-
Swift 新版本支持宏的使用,Xcode 同样进行了相关适配:在 source editor 中可以查看 Swift 宏的展开形式,并在宏生成的代码中设置断点。有关 Swift 宏的更多信息,请参见 Applying Macros.。
-
为代码行创建书签。将书签成组进行组织,并创建待办事项列表,在处理完后可以将项目标记为已完成。
-
在构建时验证与模块链接的能力。模块验证默认启用,但可以通过设置构建设置中的 Enable Module Verifier 来启用和禁用验证。参见构建设置参考。
-
使用可合并的动态库,在发布版本中获得类似静态链接的应用启动时间,而在调试版本中保持动态链接的构建时间。参见配置项目以使用可合并库。
详解:使用可合并的动态库
静态库在构建时将代码链接到二进制文件中完成符号决议,而动态库则在运行时由动态链接器加载。静态库在构建时间上较慢,但对应用程序的启动时间没有影响,而动态库则相反。Mergeable libraries 可以实现静态库和动态库两种链接策略的优势。
Mergeable libraries 其原理是针对动态库及其依赖项在构建过程中会生成元数据,这些动态库便可以在 Debug/Rlease 时选择静态链接或动态链接。合并后的输出可以是应用程序的二进制文件或其他动态库。
Xcode 中提供了手动开启和自动开启两种模式,操作起来非常方便。自动合并可以合并所有 Framework 目标的直接依赖项,手动合并可以精确控制哪些 Framework 需要合并。
Mergeable libraries 在调试和符号化方面会有所变化。调试模式下,链接器不会合并库,并且会将库保留在磁盘上以支持迭代开发,这里使用了链接器的 Reexporting 功能保证你只依赖合并库而能使用所有合并库直接依赖的 API。 不过在发布模式下会进行合并。对于符号化,合并库仍保留了源代码的符号信息,以便进行调试和分析,不过在 crash 堆栈中所展示的 image 是合并之后的库的路径。
Mergeable libraries 开启时动态库的 segments 直接链接到二进制中会导致包变大,为了避免这些不必要的符号建议通过 -no_exported_symbols 将没必要的符号进行排除,从而减小应用程序包的大小。
此外合并库还会影响 dlopen API 的路径、 Bundle 资源加载。但如果你将 iOS 部署版本升级到 12,这些便不需要担心,官方增加了一个 hook 使 bundle 能正确进行查找,如果你不依赖这个功能可以在新链接器上使用 -no_merged_libraries_hook 来进行关闭。
详解:新链接器 ld-prime
新的链接器在 WWDC 中并没有占据任何篇幅,只是在一些 Session 中几句概过,但是我们从大家的讨论来看他的性能提升非常明显,Davide Italiano 在 twitter 上提到重写过的链接器比 ld64 要快 5 倍。一些工程上实测也得到了 4 倍的性能提升。
新的链接起在 Xcode15 Toolchain 路径中可以找到,其二进制名为 ld-primer,使用行为同 ld 基本一致。在上层 clang 命令中也可以通过选项 -ld64 切换到 ld64 链接器,通过 -ld_prime 切换到新链接器,默认使用 ld-prime。
新链接器挖掘了并发能力,工程规模越大收益越明显。在 WWDC 官方提供的 Backyard Birds 示例中测试链接时间从 180ms 降低到 120ms,一些大型工程在 Xcode15 实践下来在链接阶段也有 4 倍的性能提升。各方面的测试数据表明这是一个非常不错的链接器,开发者可以尽快推进 Xcode15 的适配啦!
构建系统上,Xcode 15 会自动为动态库生成 TBD 从而加速增量构建,这个 feature 也是一个非常不错的尝试,遗憾的是 WWDC 也没有提及。
Related sessions from WWDC23
Session 10166: Write Swift macros
Session 10268: Meet mergeable libraries
Preview and documentation
-
使用#Preview Swift 宏为所有UI技术生成预览,生效范围包括 SwiftUI、AppKit、UIKit 和 WidgetKit。
-
Xcode 中使用字符串目录以可视化编辑器本地化和翻译应用程序的所有文本。在一个地方托管翻译、配置不同地区和语言环境的复数消息,并更改文本在不同设备上的显示方式。参见使用字符串目录进行本地化和多样化文本。
-
自动为 Assets 资源生成编码符号,这样就不需要通过字符串名称对资源进行引用,从而避免比较多的编译问题。
示例:预览界面
为代码编写的文档可以实时预览。从 Assistant 入口便可以跳转到新的文档预览助手。快速帮助提供新的视觉样式,支持图像,并根据您的编辑器字体进行动态字体大小调整。
DocC 现在支持视频、更多布局配置和主题。
Related sessions from WWDC23
Session 10252: Build programmatic UI using Xcode Previews
Session 10244: Create rich documentation with Swift DocC
Tuning and debugging
概览
-
使用 os_log 输出的调试信息能够在调试控制台展示结构化的日志。Debug Console 会提供日志的信息来源,还可以按元数据进行过滤。单个日志消息可以跳转到源代码。这些新的功能在 macOS 13.3 及更高版本、iOS 17 及更高版本、iPadOS 17 及更高版本、watchOS 10 及更高版本和 tvOS 17 及更高版本的设备和模拟器运行时可用。
-
使用 devicectl 指令可以和设备进行交互。该指令可以查看所有连接设备、通过 CoreDevice 服务连接设备、对设备连接失败等情况进行诊断。有关 devicectl 的详细信息,运行 xcrun devicectl help。
-
LLDB 需要记住许多打印变量命令,如”expression”、”v”、”vo”、”frame variable”等,使用时会有选择困难。因此,引入了 Do What I Mean Print 来简化这个过程。在 Xcode15 之后你直接使用 p var 的方式进行输出,它可以评估多个不同的代码表达式,并以最快的方式返回结果,帮助开发人员节省时间。
示例:devicectl 设备连接失败问题诊断
设备连接失败后执行指令 xcrun devicectl diagnose 可以得到以下详细日志
Host System: Finished 'Gather network state'. Results at ifconfig.txt
Host System: Finished 'List information about all devices'. Results at CoreDevice-listDevices.json
Copying CoreDevice database
Host System: Finished 'Copy the Core Device database'. Results at /private/var/folders/b1/0fd1b6hs7lz0fm_mh346lybm0000gn/T/TemporaryItems/NSIRD_devicectl_y8X0a4/devicectl_diagnose_2023_06_12.08-58-24/host/CoreDevice-db.sqlite
Copying DDI version.plists from /Library/Developer/DeveloperDiskImages
ERROR: Host System: 'Copy the DDI version.plists from /Library/Developer/DeveloperDiskImages' failed: The file “DeveloperDiskImages” couldn’t be opened because there is no such file. (NSCocoaErrorDomain error 260.)
NSFilePath = /Library/Developer/DeveloperDiskImages
NSURL = file:///Library/Developer/DeveloperDiskImages/
--------------------------------------------------------------------------------
ERROR: The operation couldn’t be completed. No such file or directory (NSPOSIXErrorDomain error 2.)
Copying DDI version.plists from ~/Library/Developer/DeveloperDiskImages
Host System: Finished 'Copy the DDI version.plists from ~/Library/Developer/DeveloperDiskImages'.
Host System: Finished 'Get CoreDevice version'.
Host System: Finished 'Get default Xcode version'.
Host System: Finished 'Gather RemotePairing defaults'.
Host System: Finished 'Gather CoreDevice defaults'.
Waiting for tasks to finish:
- Host System: List preferred DDIs for all platforms
- Host System: Gather Bonjour adverts
Host System: Finished 'Gather Bonjour adverts'.
Wrote file to /tmp/devicectl_diagnose_2023_06_12.08-58-24.zip
示例:结构化调试日志
通过下面代码我们可以快速对 OSLog 有个初步的认识:
import OSLog
// 填写 subsystem、catgory 初始化 logger
let logger = Logger(subsystem: "OSLogTest", category: "Account")
func login(password: String) -> Error? {
var error: Error? = nil
let username = "kuper"
logger.info("Logging in user '\(username)'...")
// ...
logger.info("Start fetch user info")
if let error {
logger.error("User '\(username)' failed to log in. Error: \(error)")
} else {
logger.notice("User '\(username)' logged in successfully.")
}
logger.error("Network connection failure")
Swift.print("normal print")
return error
}
执行代码后调试控制台中以更丰富的信息对日志进行了展示,鼠标每放置到一行日志便会在右侧展示源代码点击便可以跳转到对应的代码行。除了丰富的信息展示功能,还有强大的筛选能力,可以按 Type、Subsystem、Category 进行筛选日志。
示例程序中出现报错,通过颜色或者错误类型筛选定位到报错日志,通过 Debug Console 的源码定位能力快速跳转到目标代码,进行代码修改,非常便捷。OSLog 的 API 使用很简单,项目迁移起来非常方便,这个功能在日常开发中使用起来一定会成为开发利器。
Related sessions from WWDC23
Session 10226: Debug with structured logging
Session 10175: Fix failures faster with Xcode test reports
Distribution and continuous integration
概览
Xcode Organizer 简化了使用选项可以更轻松地进行 distribution。用 Xcode Cloud 加速你的工作:通过Xcode Cloud,可以创建一个工作流,在推送代码更改时自动构建和发布应用程序。可以轻松的使用 TestFlight 进行测试发布。
使用 Xcode Cloud 进行 Notarization,开发者可以设置 Notarization 工作流程,并在构建过程中自动进行 Notarization。完成 Notarization 后,可以直接从 Xcode Cloud 下载已经 Notarized 的应用程序。
时至今日,Xcode Cloud 已发布整整两年。从产品上来看 Xcode Cloud 是一个不错的产品同苹果生态深度结合并有着良好的体验,但开发者所重视的性能上提升却并没有在该产品中充分体现比如说分布式能力。Xcode Cloud 小结,修修补补又一年。
Related sessions from WWDC23
Session 10224: Simplify distribution in Xcode and Xcode Cloud