ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Swift Package Manager 资源捆绑完整指南:从 Bundle.module 到资源规则源码解析

Swift Package Manager 资源捆绑完整指南:从 Bundle.module 到资源规则源码解析 开发工具构建工具【免费下载链接】swift-package-managerThe Package Manager for the Swift Programming Language项目地址https://gitcode.com/gh_mirrors/sw/swift-package-manager点击查看免费下载本篇技术指南围绕 Swift Package ManagerSwiftPM的资源Resources捆绑机制展开适用于所有需要在 Swift 包中随源码分发静态资源资产目录、测试夹具、属性列表、图片、本地化文本等的开发者。读完本文你将掌握swift-tools-version: 5.3及以上版本的资源声明语法、process/copy两种核心规则与embedInCode扩展规则的区别、exclude排除机制以及如何通过Bundle.module在代码中安全访问资源并理解资源捆绑在 SwiftPM 内部的实现原理。什么是 Swift 包资源捆绑在 Swift 5.3 之前Swift Package Manager 构建的包只能包含源码无法把图片、plist、测试数据等文件随包一起分发。从swift-tools-version: 5.3开始SwiftPM 引入了资源捆绑resource bundling能力只要在Package.swift中声明// swift-tools-version: 5.3或更高版本就可以把非源文件作为资源与源码一起打进包里。典型可捆绑的资源包括资产目录asset catalog、XIB、storyboard、Core Data 数据模型等 Apple 平台资源测试夹具test fixtures、示例数据文件属性列表plist、JSON 配置文件图片、音频等媒体文件本地化资源localized resources。其核心设计原则是资源与目标target强绑定资源作用域限定在所属 target 内与源码一样必须放在该 target 对应的目录中无法跨 target 共享。添加资源文件目录约定与放置规范SwiftPM 把目标 sources 目录中发现的非源文件一律视为该目标的资产asset并自动纳入资源处理流程。例如MyLibrary目标的所有资源默认位于Sources/MyLibrary目录下。为了清晰地区分源码与资源官方文档与 SwiftPM 都推荐为资源单独建立子目录。最常见的做法是建立一个名为Resources的目录使所有资源文件位于Sources/MyLibrary/Resources下。这一约定在 Runtimes/PackageDescription/Resource.swift 的类型注释中也有明确说明使用子目录组织资源文件可以简化文件识别与管理。目录结构示意MyPackage/ ├── Package.swift └── Sources/ └── MyLibrary/ ├── MyLibrary.swift └── Resources/ ├── text.txt ├── settings.plist └── data/ └── fixture.json需要强调的是资源被严格限定在所属 target 内部因此不要把资源文件放在 target 目录之外否则 SwiftPM 无法将其归属到任何 target。显式声明资源process 与 copy 规则并非所有资源都会被编译器自动处理。对于编译器无法自动识别的文件类型例如普通的图片文件必须在包清单中将其显式声明为资源而在 Xcode 中构建时Xcode 会自动处理若干常见资源类型如 XIB、storyboard、asset catalog 等这些无需在清单中声明。process 规则首选的处理方式对于绝大多数使用场景官方推荐使用process(_:localization:)规则。它请求编译器根据目标平台对该类型资源应用已知的处理流程。例如在支持图片优化的平台上Xcode 可能对图片做针对性优化如果没有可用的特殊处理编译器会将该资源原样拷贝到资源包的顶层目录。process规则作用于目录路径时会递归地应用于目录内的所有文件。在Package.swift中显式声明一个资源文件的完整示例// swift-tools-version: 5.3 import PackageDescription let package Package( name: MyPackage, targets: [ .target( name: MyLibrary, resources: [ .process(Resources/text.txt) ] ), ] )上面的示例将Sources/MyLibrary/Resources/text.txt声明为MyLibrary目标的资源。copy 规则保留原样与目录结构某些 Swift 包要求资源文件保持原样不被处理或必须保留特定的目录结构例如插件模板、脚本资产。此时应使用copy(_:)规则传入文件路径时文件被原样拷贝到资源包的顶层目录传入目录路径时编译器会保留该目录的完整结构。这正好与process规则形成对照process处理目录时会递归展开而copy处理目录时会保留目录层级。embedInCode 规则把资源编译进可执行代码除文档重点讲解的process与copy外从 Swift 5.9available(_PackageDescription, introduced: 5.9)见 Runtimes/PackageDescription/Resource.swift开始SwiftPM 还提供了第三种规则embedInCode(_:)将资源文件的字节内容直接嵌入可执行代码生成一个PackageResources结构体其中每个嵌入资源对应一个以资源名命名的静态属性点号替换为下划线。例如嵌入内容为Hello Swift的identifier.txt生成的代码等价于struct PackageResources { static let identifier_txt: [UInt8] [72,101,108,108,111,32,83,119,105,102,116,10] }embedInCode适合希望在运行时彻底摆脱文件系统依赖、把数据直接放进二进制镜像的场景。三种规则的 API 定义与参数在 Runtimes/PackageDescription/Resource.swift 中Resource的公开 API 如下API引入版本说明process(_ path: String, localization: Localization? nil)5.3按平台应用已知处理无处理则原样拷贝目录递归处理copy(_ path: String)5.3原样拷贝到资源包顶层目录保留结构embedInCode(_ path: String)5.9将资源字节嵌入可执行代码其中process的localization参数接受Localization枚举取值为.default默认本地化与.base基础国际化。资源在包内的落点destination 计算在 SwiftPM 内部Sources/PackageModel/Resource.swift每个资源由Resource结构体描述包含rule规则与path路径两个字段并通过destination计算属性确定其在资源包中的相对位置使用process且指定了非Base的localization时资源落在localization.lproj/文件名路径下localizationDirectoryExtension常量即lproj且局部化标识会被统一转为小写其他情况process无本地化、copy、embedInCode下资源落在资源包顶层仅保留文件名。Resource.Rule枚举在内部共有三个 caseprocess(localization: String?)、copy、embedInCode与 PackageDescription API 一一对应。值得一提的是process本地化目录还支持以Base命名的目录用于基础国际化。排除资源exclude 参数与警告机制如果一个文件位于 target 的目录内但你不希望它成为包资源可以把它传给 target 初始化器的exclude参数。例如Sources/MyLibrary/instructions.md仅用于本地文档、不应被打包targets: [ .target( name: MyLibrary, exclude: [instructions.md] ), ]关于exclude官方文档给出如下实践建议并有源码层面的印证尽量避免把非资源文件放进 target 的 sources 目录如果不可避免不要逐个文件地排除而是把要排除的所有文件集中到一个目录再把目录路径加入exclude数组SwiftPM 会对 target 的Sources目录中无法识别的文件发出警告。在实现层面Sources/PackageLoading/TargetSourcesBuilder.swift 会将target.exclude解析为绝对路径集合excludedPaths在遍历 target 目录时跳过这些路径见excludedPaths相关的文件扫描逻辑并对无效的 exclude 路径、重复的源文件声明以及未处理的资源分别发出诊断警告例如Found unhandled resource at ...。此外Runtimes/PackageDescription/Target.swift 明确指出exclude中的路径相对于 target 路径且该参数优先于sources与resources参数——也就是说被排除的路径不会同时被当作源文件或资源处理。在代码中访问资源Bundle.module当一个 target 包含资源时编译器会为该模块自动创建一个资源包resource bundle并生成一个对Bundle的内部静态扩展用于定位包内资源。在代码中使用该扩展即可访问资源无需关心资源的实际物理位置。例如读取随包分发的属性列表settings.plist的 URLlet settingsURL Bundle.module.url(forResource: settings, withExtension: plist)重要提示访问资源时必须始终使用Bundle.module。Swift 包不应假设资源的准确存放位置——不同平台、不同构建方式下资源包的物理路径可能不同只有通过Bundle.module才能获得 SwiftPM 保证的正确路径。Bundle.module 是如何生成的从源码可以确认Bundle.module并非手写代码而是 SwiftPM 在构建期生成的。在 Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift 中needsResourceBundle检查目标资源中是否存在非embedInCode规则的资源——只要存在就需要生成资源包bundlePath根据 target 的potentialBundleName与构建参数中的bundlePath(named:)计算资源包的最终路径构建阶段会生成名为resource_bundle_accessor.swift的访问器源码generateResourceAccessor()方法其中定义Bundle.module静态属性同时还会生成资源包的Info.plist对应 Sources/Build/BuildPlan/BuildPlan.swift 中的generateResourceInfoPlist。也就是说Bundle.module会在每个包含资源的模块中自动可用开发者直接使用即可。向依赖方暴露资源如果希望把某个包资源提供给依赖该 Swift 包的 App 使用可以为它声明一个public 常量。例如向使用本包的应用暴露settings.plist的 URLpublic let settingsURL Bundle.module.url(forResource: settings, withExtension: plist)通过公开 API 暴露资源可以让外部消费者通过稳定的符号访问资源而无需关心资源包内部的具体路径。资源捆绑的完整实战示例仓库中的 Fixtures/Resources/Simple 是 SwiftPM 自身的测试夹具展示了资源捆绑的真实用法。其 Package.swift 声明了swift-tools-version:5.3并在多个 target 中使用copy规则捆绑foo.txt// swift-tools-version:5.3 import PackageDescription let package Package( name: Resources, targets: [ .target( name: SwiftyResource, resources: [ .copy(foo.txt), ] ), // SeaResource、ClangResource、CPPResource、MixedClangResource 同理 .testTarget( name: ClangResourceTests, dependencies: [ClangResource] ), ] )对应的目录结构节选Fixtures/Resources/Simple/ ├── Package.swift ├── Sources/ │ ├── SwiftyResource/ │ │ ├── foo.txt │ │ └── main.swift │ ├── ClangResource/ │ │ ├── include/Package.h │ │ ├── Package.m │ │ └── foo.txt │ └── ... └── Tests/ └── ClangResourceTests/ └── ClangResourceTests.m从这个示例可以看出两点实践资源文件直接放在 target 源码目录下即可被识别无需Resources子目录子目录只是推荐的组织方式资源声明对 Swift、ClangC/Objective-C、C 等各类 target 均适用且测试 target 可以依赖携带资源的 target。工作原理小结与最佳实践把以上内容串联起来SwiftPM 资源捆绑的完整工作流为放置将资源文件放入 target 的 sources 目录推荐Sources/TargetName/Resources子目录声明在Package.swift的 target 初始化器中用.process(...)首选、.copy(...)需保留原样/结构或.embedInCode(...)Swift 5.9嵌入代码显式声明或用exclude排除不应打包的文件构建SwiftPM 构建资源包、生成Info.plist与resource_bundle_accessor.swift内含Bundle.module访问代码内通过Bundle.module.url(forResource:withExtension:)等 API 访问资源需要对外暴露时通过 public 常量发布。实用建议汇总始终使用Bundle.module定位资源不要硬编码路径能用process就用process让平台有机会优化资源如图片压缩仅在需要保持字节原样或保留目录层级时使用copy用Resources子目录组织资源保持源码目录整洁把临时文件、文档等非资源文件统一放进一个目录并用exclude排除整个目录避免逐个文件排除和产生警告资源作用域限定在 target 内跨 target 共享资源没有官方支持路径应通过目标依赖dependency间接使用携带资源的库。如果你希望进一步深入可以继续阅读仓库中的相关实现Sources/PackageModel/Resource.swift内部资源模型、Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift资源包与访问器生成、Sources/PackageLoading/TargetSourcesBuilder.swift资源扫描与警告以及测试夹具 Fixtures/Resources/Simple。赞分享开发工具构建工具【免费下载链接】swift-package-managerThe Package Manager for the Swift Programming Language项目地址https://gitcode.com/gh_mirrors/sw/swift-package-manager点击查看免费下载相关推荐Swift Package Manager 资源支持SE-0271完全指南从 manifest 声明到 Bundle.module 运行时访问Swift Package Manager 资源支持SE 0271完全指南从 manifest 声明到 Bundle.module 运行时访问 本篇技术指文档Swift Package Manager 资源管理实战Bundle、Localized 资源与插件生成资源的正确姿势Swift Package Manager 资源管理实战Bundle、Localized 资源与插件生成资源的正确姿势 本文是 Swift Package M开发工具构建工具如何从源码构建 Swift Package Manager贡献者开发环境搭建完整指南如何从源码构建 Swift Package Manager贡献者开发环境搭建完整指南 如果你想在本地 从源码构建 Swift Package Manager开发工具构建工具上一篇douyin-downloader 完整指南抖音无水印批量下载 3 步跑通整个作者主页搬进本地下一篇react-native-image-picker iOS隐私清单配置NSPhotoLibraryUsageDescription最佳实践创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表