---
title: "iOS 中资源的条件编译"
description: "Debug 专用代码如何不进入线上包？对比 CocoaPods 的 configurations 限制与条件编译两种方案，并说明各自的适用边界。"
date: "2020-11-21"
tags: ["Xcode"]
canonical: "https://vonglo.cn/posts/xcode-condition-build/"
license: "CC BY-NC-SA 4.0"
licenseUrl: "https://creativecommons.org/licenses/by-nc-sa/4.0/"
---
# iOS 中资源的条件编译

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

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

## Pod

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

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

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

## 条件编译

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

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

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

## Xcode Build Options

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

![Xcode Build Settings 中的 Excluded Source File Names 配置](/media/Xcode-Condition-Build/Xcode-Condition-Build-01-15855806858197.jpg)

> 确切来说这个编译选项是 `Xcode 9` 才引入的，之前版本需要自行定义，可以参考这个[回答](https://stackoverflow.com/a/41495430)。
> 摘录一段来自[Xcode Build Settings](https://xcodebuildsettings.com/#excluded_source_file_names)的解释
> ![-w849](/media/Xcode-Condition-Build/Xcode-Condition-Build-02-15855826179239.jpg)

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

```
# 使用 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
```

```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](/media/Xcode-Condition-Build/Xcode-Condition-Build-03-15859854120322.jpg)
> 实际用到项目中可根据自己项目自行对不同 `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](/media/Xcode-Condition-Build/Xcode-Condition-Build-04-15859945623293.jpg)

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

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

## Development Assets

![-w1219](/media/Xcode-Condition-Build/Xcode-Condition-Build-05-15859936373369.jpg)

> 关于这个配置项可以查看 [WWDC 2019 Session 233 Mastering Xcode Previews](https://developer.apple.com/videos/play/wwdc2019/233/)

`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 中的该资源](/media/Xcode-Condition-Build/Xcode-Condition-Build-06-16640814305662.jpg)


具体入口在选中 Target 之后，选中顶部 General 即可。
![Target 的 General 页面中的 Development Assets 配置入口](/media/Xcode-Condition-Build/Xcode-Condition-Build-07-16640814434504.jpg)
---

Canonical: https://vonglo.cn/posts/xcode-condition-build/
License: [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)
Author: Vong
