本文目录 4 节

Vong Xcode

iOS 中资源的条件编译

Debug 专用代码如何不进入线上包?对比 CocoaPods 的 configurations 限制与条件编译两种方案,并说明各自的适用边界。

Xcode

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

Pod

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

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

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

条件编译

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

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

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

Xcode Build Options

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

Xcode Build Settings 中的 Excluded Source File Names 配置
Xcode Build Settings 中的 Excluded Source File Names 配置

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

-w849
-w849

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

# 使用 tree 命令行得到下面结构
├── ConditionBuildDemo
│   ├── A
│   │   ├── Debug
│   │   │   ├── DebugA.swift
│   │   │   ├── [email protected]
│   │   │   ├── Sub
│   │   │   │   ├── DeepSub
│   │   │   │   │   └── DeepSubA.swift
│   │   │   │   ├── SubDebugA.swift
│   │   │   │   └── icon.png
│   │   │   └── SubA.xcassets
│   │   │       ├── Contents.json
│   │   │       └── logo.imageset
│   │   │           ├── Contents.json
│   │   │           └── [email protected]
│   │   └── 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
-w984
实际用到项目中可根据自己项目自行对不同 Configuratio 进行配置。

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

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

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

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

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

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

-w589
-w589

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

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

Development Assets

-w1219
-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 模式不会),具体情况如下

Debug 包保留 test.txt,而 Archive 包排除 Development Assets 中的该资源
Debug 包保留 test.txt,而 Archive 包排除 Development Assets 中的该资源

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

Target 的 General 页面中的 Development Assets 配置入口
Target 的 General 页面中的 Development Assets 配置入口

相关阅读

动态更新

关注微信公众号 Vong 或在微博上关注 @Vong_HUST,第一时间收到新文章。您的支持和鼓励会让这份写作持续下去。