iOS 中资源的条件编译

  • Xcode
  • 发布于2020年11月21日

日常开发中常常遇到某些类及其引用的资源文件仅在 Debug 或内测版本中生效,而不希望带到线上版本。因为一来会增加包体积,二来会把一些内部功能的相关接口暴露,导致可能的一些动态调试。那么有没有避免的方案呢?

Pod

答案肯定是有的,通常的做法是把对应功能抽成单独的 pod 库,然后仅 Debug 模式才集成到主工程,发出去的版本不集成。通常方式如下,以 FLEX 为例:

pod 'FLEX', '~> 2.0', :configurations => ['Debug']

但是如果某些强耦合主工程无法拆成独立 pod 的功能,这种方式可能就不再适用了。

条件编译

pod 这条路走不通,有没有其他方式呢?答案也是肯定的,也是大家经常采用的方式,使用条件编译。也就是将对应类文件及引用到的地方使用类似下面的方式:


#if DEBUG
// 放入仅Debug模式下生效的代码
#endif

以上两种方式将大部分场景 cover 住了,但是假设对应的类文件还引用到了图片、StoryboardXib 等资源时,该如何让这些资源仅在 Debug 下才参与编译,而其他场景不参与编译呢?

Xcode Build Options

既然都写了这篇文章了,那答案也是肯定的,就是使用 Xcode 提供的一个编译选项 Excluded Source File Names 来将不需要的资源排除。

确切来说这个编译选项是 Xcode 9 才引入的,之前版本需要自行定义,可以参考这个回答。 摘录一段来自Xcode Build Settings的解释 -w849

假设有下面目录结构的工程


# 使用 tree 命令行得到下面结构
├── ConditionBuildDemo
│   ├── A
│   │   ├── Debug
│   │   │   ├── DebugA.swift
│   │   │   ├── icon_test@2x.png
│   │   │   ├── Sub
│   │   │   │   ├── DeepSub
│   │   │   │   │   └── DeepSubA.swift
│   │   │   │   ├── SubDebugA.swift
│   │   │   │   └── icon.png
│   │   │   └── SubA.xcassets
│   │   │       ├── Contents.json
│   │   │       └── logo.imageset
│   │   │           ├── Contents.json
│   │   │           └── logo@2x.png
│   │   └── ModelA.swift
│   ├── B
│   │   ├── Debug
│   │   │   └── DebugB.swift
│   │   └── ModelB.swift
//测试代码
override func viewDidLoad() {
    super.viewDidLoad()
    let _ = ModelA()
    let _ = ModelB()
    let _ = DebugA()
    let _ = DebugB()
    let _ = SubDebugA()
    let _ = DeepSubA()
    if let _ = UIImage.init(named: "logo") {
        print("has logo")
    }
    if let _ = UIImage.init(named: "icon") {
        print("has icon")
    }
    if let _ = UIImage.init(named: "icon_test") {
        print("has icon_test")
    }
}

这里为了方便验证,直接设置 Debug Configuration 下的 Excluded Source File Names。如图 -w984 实际用到项目中可根据自己项目自行对不同 Configuratio 进行配置。

下面我们来探索下这个路径的配置规则。

如果我们想要排除 AB 目录下的 Debug 文件夹的所有内容,按照常规思路,理所当然直接写上 Debug/*,然后编译发现上面的测试代码编译报错,找不到类 DebugA,DebugB


ViewController.swift:18:22: Use of unresolved identifier 'DebugA'
ViewController.swift:19:22: Use of unresolved identifier 'DebugB'

屏蔽掉这两处代码运行,发现 has logohas icon 都输出了。

所以 Debug/* 只能屏蔽 Debug 根目录下的文件,子文件夹无法屏蔽(因为 .xcasset 实质上也是一个文件夹,所以无法被屏蔽)。

如果确实想要屏蔽 Debug 下所有内容,要怎么处理呢?目前我还没找到比较好的方式😂,只能一个个目录排除了。拿上面的的结构来说,如果要排除文件夹 A B 目录下的 Debug,可能就得这样配置 Debug/* Debug/*/* Debug/*/*/*,如图

-w589

所以这里支持的匹配模式主要有以下两种:

  • 文件名完全匹配,比如 ModelA.Swift
  • 通配符,如 Debug/*.Swift Debug/*.* */Debug/* Debug/*/*/*.png

Development Assets

-w1219

关于这个配置项可以查看 WWDC 2019 Session 233 Mastering Xcode Previews

Xcode 11 新增了一个 Development Assets 的配置项,简单尝试了下,但是似乎没有生效。我把上面目录结构中的 SubA.xcassets 加到里面,然后用release 模式跑起来还是能读到对应的图片,不知道是不是姿势不对。

其实重点还是 Excluded Source File Names 这个编译配置,有时候会有事半功倍的效果。

update 2022-09-22

最近使用 Development Assets 无意间发现,这个目录下的资源在 archive 出来的包会被排除(但 release 模式不会),具体情况如下

具体入口在选中 Target 之后,选中顶部 General 即可。

本博客所有文章除特别声明外,均采用CC 4.0许可协议。转载请注明出处和作者。

关注微信公共号Vong或在微博上关注@Vong_HUST,永远不会错过新内容! 您的支持和鼓励将为我的博客写作增添更多的动力!

动态更新