资讯动态

SwiftGen 资源加载定制指南:用 `bundle` 与 `lookupFunction` 参数接管资源查找逻辑

发布时间:2026/9/25 11:42:14 来源:尧图企业网站定制
开发工具代码生成【免费下载链接】SwiftGenThe Swift code generator for your assets, storyboards, Localizable.strings, … — Get rid of all String-based APIs!项目地址https://gitcode.com/gh_mirrors/sw/SwiftGen点击查看免费下载导读SwiftGen 生成的代码默认从“生成代码所在的那个 Bundle”中加载资源——主 App 的代码从mainbundle 加载Framework 的代码则从该 Framework 自己的 bundle 加载。这对大多数项目够用但当你使用 CocoaPodsresource_bundles把资源打进独立子 bundle、需要运行时热更新翻译文件、或搭建多 white-label App 共用一个 Framework 的场景时就需要更高级的方案。本文以 Customize-loading-of-resources.md 为主体结合仓库内各模板的源码与测试完整讲解如何通过bundle模板参数和lookupFunction模板参数接管资源查找逻辑读完你可以在配置文件中精确指定“从哪里加载”乃至“如何加载”。默认行为代码在哪资源就在哪SwiftGen 内建模板默认通过一个名为BundleToken的私有类来定位资源 bundle。以 structured-swift5.stencil 为例模板尾部会生成private final class BundleToken { static let bundle: Bundle { #if SWIFT_PACKAGE return Bundle.module #else return Bundle(for: BundleToken.self) #endif }() }这段代码的语义就是原文档开篇所述的两条默认规则生成代码位于主 App target 时Bundle(for: BundleToken.self)指向mainbundle生成代码位于某个 Framework 时它指向该 Framework 的 bundle使用 Swift Package Manager 时则回退为Bundle.module。对大多数用户这已经够用。BundleToken在strings、fonts、files、ib、json、plist等几乎所有模板的实现细节段中都会按需生成条件是“未提供bundle且未提供lookupFunction”。一旦你显式提供了这两个参数中的任意一个BundleToken便不再生成。场景一CocoaPods Pod 的resource_bundles子 Bundle问题背景CocoaPods 官方建议用resource_bundles选项把 Pod 的资源打包进一个独立的 bundle例如MyAwesomePodResources.bundle。这样做的好处是无论终端用户以何种方式集成你的 Pod你的资源都不会与用户的资源冲突——这一点在静态链接时尤其重要因为静态链接下 CocoaPods 不会为每个 Pod 生成 Framework而是把所有内容塞进用户主 App 的 bundle。代价则是你的资源不再与生成代码位于同一 bundleSwiftGen 生成的代码默认会加载失败。解决bundle模板参数修复方法是在配置文件中为每个相关输出设置bundle参数让它指向你代码中代表“资源 bundle”的那个表达式。原文档给出了一个典型的资源 bundle 定位器final class MyAwesomePod { // This is the bundle where your code resides in static let bundle Bundle(for: MyAwesomePod.self) // Your resources bundle is inside that bundle static let resourcesBundle: Bundle { guard let url bundle.url(forResource: MyAwesomePodResources, withExtension: bundle), let bundle Bundle(url: url) else { fatalError(Cant find MyAwesomePodResources bundle) } return bundle }() }仓库中确实存在与之同构的测试辅助实现见 Common-CustomBundle.swift它同样通过Bundle(for:)获取自身所在 bundle再在其中查找名为SwiftGenResources的子 bundle。这说明“Bundle(for:)url(forResource:withExtension:)定位子 bundle”是官方测试环境验证过的标准写法。随后在swiftgen.yml配置文件中把bundle: MyAwesomePod.resourcesBundle加到需要该行为的模板输出下原文档完整示例input_dir: Resources output_dir: Sources files: inputs: files/dir outputs: templateName: swift5 output: Generated/Files.swift params: bundle: MyAwesomePod.resourcesBundle fonts: inputs: Fonts outputs: templateName: swift5 output: Generated/Fonts.swift params: bundle: MyAwesomePod.resourcesBundle strings: inputs: en.lproj/Localizable.strings outputs: templateName: structured-swift5 output: Generated/Strings.swift params: bundle: MyAwesomePod.resourcesBundle重新运行 SwiftGen 后生成代码就会改为从MyAwesomePod.resourcesBundle加载本地化字符串与字体而不再使用代码所在 bundle。模板源码中bundle的落点在 SwiftGen 的 Stencil 模板中bundle参数会直接替换默认的BundleToken.bundle替换点在各个模板的“Implementation Details”段strings 模板structured-swift5.stencilBundleToken.bundle被替换后调用bundle.localizedString(forKey:value:table:)fonts 模板swift5.stencilbundle.url(forResource:withExtension:)用于定位字体文件 URLfiles 模板flat-swift5.stencilbundle.url(forResource:withExtension:subdirectory:localization:)用于定位普通文件ib 模板scenes-swift5.stencilNSStoryboard(name:bundle:)/UIStoryboard(name:bundle:)用于加载 storyboardjson/plist 运行时模板runtime-swift5.stencilbundle.url(forResource:withExtension:)用于在运行时读取数据文件。一个通用的表达是bundle参数接受任何“能求值为Bundle的 Swift 表达式”实际生效点取决于模板类型。注意事项模板支持范围并非所有模板都支持bundle。XCAssets 模板xcassets/swift5、xcassets/swift4就不支持bundle/lookupFunction参数——因为资产目录根据资源类型图片集、颜色集、数据集、符号等会调用多种不同的加载方法无法用单一 bundle 或单一查找函数统一接管。原文档也明确提示请查阅 templates 文档 确认你要用的模板是否支持该参数。各模板的说明页如 strings/structured-swift5.md会列出参数表其中bundle的默认值为BundleToken.bundle。场景二用lookupFunction完全接管查找逻辑当“换一个 bundle”已经不够用时可以进一步覆盖“lookup function”——即让 SwiftGen 生成的代码调用你自定义的查找函数而不是直接访问某个 bundle。原文档给出三类典型动机应用内切换语言想用 App 内自选的语言覆盖设备系统语言重写本地化字符串的加载路径多 white-label App 共享 Framework多个 App target 使用同一个 Framework希望每个 App 能覆盖 Framework 提供的翻译运行时热更新翻译App 定期检查并下载更新版本的翻译文件本地嵌入的翻译作为兜底。解决lookupFunction模板参数原文档的示例是一个TranslationService它优先返回内存中更新的翻译表否则回退到Bundle.main.localizedString(forKey:value:table:)final class TranslationService { static let shared TranslationService() private typealias TranslationTable [String: String] private var updatedTables: [String: TranslationTable] [:] func lookupTranslation(forKey key: String, inTable table: String) - String { if let table updatedTables[table], let translation table[key] { return translation } else { return Bundle.main.localizedString(forKey: key, value: nil, table: table) } } }updatedTables如何从网络下载并更新原文档明确留给读者自行实现。随后在配置文件中为strings输出添加lookupFunction参数注意必须提供函数的完整签名含外部参数名input_dir: Resources output_dir: Sources strings: inputs: en.lproj/Localizable.strings outputs: templateName: structured-swift5 output: Generated/Strings.swift params: lookupFunction: TranslationService.shared.lookupTranslation(forKey:inTable:)生成代码中tr(_:_:_:fallback:)的实现会从“bundle.localizedString”切换为调用你的函数见 structured-swift5.stencilprivate static func tr(_ table: String, _ key: String, _ args: CVarArg..., fallback value: String) - String { let format TranslationService.shared.lookupTranslation(forKey:inTable:)(key, table, value) // 等价于模板渲染结果let format TranslationService.shared.lookupTranslation(forKey:inTable:)(key, table, value) return String(format: format, locale: Locale.current, arguments: args) }更精确地说模板渲染后生成的是let format TranslationService.shared.lookupTranslation(forKey:inTable:)(key, table, value)即参数值key、table、value按位置传入你提供的函数引用。lookupFunction的签名随模板而异原文档特别强调该函数签名取决于你使用的 parser 与模板例如strings模板和fonts模板的要求就完全不同。仓库中各模板的文档页给出了精确签名模板类型要求签名示例stringsflat/structured(key: String, table: String, fallbackValue: String) - StringyourFunctionName(forKey:table:fallbackValue:)fonts(name: String, family: String, path: String) - URL?myFontFinder(name:family:path:)ib scenes(name: NSStoryboard.Name) - NSStoryboardmacOS(name: String) - UIStoryboard其他平台myStoryboardFinder(name:)json / plist runtime(path: String) - URL?myJSONFinder(path:)以上签名分别见 fonts/swift5.md、ib/scenes-swift5.md、json/runtime-swift5.md。在 Stencil 源码中的实际调用点fonts 模板swift5.stencilreturn {{param.lookupFunction}}(name, family, path)用于计算字体文件 URLib 模板scenes-swift5.stencilreturn {{param.lookupFunction}}(name)用于构造 Storyboard 实例json/plist runtime 模板runtime-swift5.stencilguard let url {{param.lookupFunction}}(path)用于定位数据文件。两个关键行为规则bundle与lookupFunction互斥只要设置了lookupFunctionbundle参数就会被忽略各模板文档的参数表均有此注明见 strings/structured-swift5.md。命名参数必须提供完整签名函数参数可以使用任意名称甚至省略外部名但只要使用了命名参数就必须在配置值中写出包含全部命名参数的完整签名例如TranslationService.shared.lookupTranslation(forKey:inTable:)。这是模板把配置字符串直接拼进生成代码的必然要求——模板不会也无法替你推断签名。测试中的实际用法参数怎么传仓库的模板测试展示了这两个参数的真实书写格式。以 FontsTests.swift 为例context: try StencilContext.enrich(context: context, parameters: [bundleResourcesBundle.bundle]) // 以及 parameters: [lookupFunctionmyFontFinder(name:family:path:)]也就是说测试代码里传参格式是bundle...与lookupFunction...与配置文件 YAML 中params:下的键值写法一一对应。而 Common-CustomBundle.swift 正是测试编译环境里用来模拟“Pod 资源子 bundle”的辅助类型——它验证了“Bundle(for:) 查找子 bundle”这种模式可以被 SwiftGen 生成的代码正常编译使用。总结与使用建议你的需求推荐参数说明资源打进了独立子 bundleCocoaPodsresource_bundles、手动打包等bundle提供一个求值为Bundle的表达式即可最简单直接需要自定义加载逻辑应用内换语言、热更新翻译、white-label 覆盖、自定义字体查找lookupFunction提供完整函数签名按模板要求匹配参数类型两者同时出现—lookupFunction优先bundle被忽略XCAssets 模板均不支持资产目录按类型分派多种加载方法无法用单一参数接管动手之前请先确认目标模板的参数支持情况——bundle/lookupFunction在 Documentation/templates 下每个模板的专属文档中都有明确参数表生成后也建议像 TemplatesTests 那样用真实的资源 bundle 做一次编译验证确保Bundle(for:)定位与url(forResource:)查找路径在目标平台iOS/macOS/tvOS/watchOS、SPM 或 CocoaPods上都正确无误。赞分享开发工具代码生成【免费下载链接】SwiftGenThe Swift code generator for your assets, storyboards, Localizable.strings, … — Get rid of all String-based APIs!项目地址https://gitcode.com/gh_mirrors/sw/SwiftGen点击查看免费下载相关推荐PixiJS 资源清单Manifest与资源包Bundle加载实战指南PixiJS 资源清单Manifest与资源包Bundle加载实战指南 本文将深入讲解 PixiJS 的 Manifest资源清单 与 Bundle前端图形学SwiftGen与Combine框架响应式资源加载SwiftGen与Combine框架响应式资源加载 在iOS开发中资源加载往往依赖于字符串硬编码和手动管理这不仅容易出错还难以适应现代应用的响应式需求。开发工具代码生成SwiftGen配置文件加密保护敏感资源路径与参数SwiftGen配置文件加密保护敏感资源路径与参数 在iOS/macOS开发中配置文件常包含API密钥、私有资源路径等敏感信息。SwiftGen默认以明文存开发工具代码生成创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑