iOS开发xconfig和script脚本使用详解
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>引言</li><li>Xcode编译</li><ul class="second_class_ul"><li>Xcode target</li><li>Xcode project</li><li>Xcode scheme</li></ul><li>新建configuration</li><ul class="second_class_ul"><li>Configuration文件的使用</li><li>利用Configuration设置不同的项目名</li><li>设置Configuration</li><li>查看是否设置成功</li><li>设置Info.plist</li><li>测试是否生效</li></ul><li>利用xconfig文件实现OC条件编译</li><ul class="second_class_ul"><li>xconfig文件的设置</li></ul><li>Swift中条件编译的实现</li><ul class="second_class_ul"></ul><li>script的使用</li><ul class="second_class_ul"><li>script的初步认识</li><li>script的实际运用</li></ul><li>总结</li><ul class="second_class_ul"></ul></ul></div><p class="maodian"></p><h2>引言</h2><p>利用<code>Xcode</code>进行开发时需要进行很多<code>build setting</code>的设置以便能让项目按照设置的进行编译,同时有时候需要在编译时利用<code>script</code>脚本进行一些设置,本文主要介绍<code>xconfig</code>文件和<code>script</code>脚本在<code>Xcode</code>开发中使用。</p>
<p class="maodian"></p><h2>Xcode编译</h2>
<p>在使用<code>xconfig</code>时有几个关于<code>Xcode</code>的概念是需要理解的,这里我进行通俗简单的说明,同时需要知道<code>Xcode</code>在编译的过程中具体帮我们做了那几件事情。</p>
<p class="maodian"></p><h3>Xcode target</h3>
<p>在实际开发中一个<code>Xcode</code>创建的项目是可以有多个<code>taget</code>的,比如我们创建一个<code>widget</code>时<code>Xcode</code>会自动新建一个<code>target</code>对应这个<code>widget</code>,也可以自己新建,同一个项目有多个<code>target</code>可以满足不同的测试场景,比如在前期开发阶段使用一个<code>target</code>,到<code>UAT</code>阶段使用另外一个<code>target</code>。一个<code>target</code>对应一个<code>product</code>,也就是编译后安装到手机上的项目,<code>target</code>定义了生成的唯一 <code>product</code>, 它将构建该<code>product</code> 所需的文件和处理这些文件所需的指令集整合进 <code>build system</code> 中,这些指令以 <code>build setting</code> 和 <code>build phases</code>的形式存在,我们用<code>xconfig</code>文件来设置 <code>build setting</code>,同时将<code>script</code>脚本添加到<code>build phases</code> 中。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202209/20220908085858048.jpg" /></p>
<p>新建target</p>
<p class="maodian"></p><h3>Xcode project</h3>
<p><code>Xcode project</code> 是一个仓库,该仓库包含了所有的文件,资源和用于生成一个或者多个<code>software products</code> 的信息,它包含一个或者多个<code>targets</code>,其中的每一个 <code>target</code>指明了如何生成 <code>products</code>。<code>project</code>为其拥有的所有 <code>targets</code>定义了默认的<code>build settings</code>,例如<code>project</code>中默认包含<code>debug</code> 和<code>release</code> 两种<code>build settings</code> 当然,每一个 <code>target</code>能够制定其自己的 <code>build settings</code>,且<code>target</code> 的<code>build settings</code> 会重写<code>project</code> 的 <code>build settings</code>。</p>
<p class="maodian"></p><h3>Xcode scheme</h3>
<p>一个<code>project</code>可以有多个<code>target</code>,但是当前的<code>target</code>只能有一个,<code>scheme</code>就是用来确定当前的<code>target</code>的,并制定当前的<code>target</code>使用哪种<code>configuration</code>。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202209/20220908085858049.jpg" /></p>
<p class="maodian"></p><h2>新建configuration</h2>
<p>打开项目编辑栏选择上面的<code>progect</code>同时选择<code>info</code>栏,可以看到<code>Xcode</code>默认添加了二个<code>Debug</code>和<code>Release</code>的<code>configuration</code>,点击做下角的<code>+</code>号按钮选择复制<code>Debug</code>或者<code>Release</code>其中一个<code>configuration</code>来新建并命名一个自己想取的名字,我这里命名为<code>Mamba</code>。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202209/20220908085858050.jpg" /></p>
<p class="maodian"></p><h3>Configuration文件的使用</h3>
<p>平时手动的在<code>Xcode</code>中进行项目的一些<code>build setting</code>设置还是比较麻烦的,一个是需要在<code>Xcode</code>中进行搜索,另外一个是不好管理,例如需要在<code>debug</code>或者<code>release</code>下进行不同的设置的话就比较麻烦。利用<code>Configuration</code>文件来代替手动设置则更加的方便,直接新建<code>Configuration Setting file</code>类型文件,如下图所示:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202209/20220908085858051.jpg" /></p>
<p class="maodian"></p><h3>利用Configuration设置不同的项目名</h3>
<p><code>Configuration</code>文件是可以继承的,一般先建立一个<code>Common Configuration</code>文件用来作为父类,为此新建一个名为<code>Common</code>的<code>Configuration</code>文件,并加入如下代码:</p>
<div class="jb51code"><pre class="brush:cpp;">APP_NAME = TestDemo
</pre></div>
<p>然后分别新建名为<code>debug</code>,<code>Mamba</code>和<code>release</code>的<code>Configuration</code>文件,并加入如下代码:</p>
<ul><li>debug</li></ul>
<div class="jb51code"><pre class="brush:cpp;">#include "Common.xcconfig"
APP_NAME = $(inherited)Debug
</pre></div>
<ul><li>Mamba</li></ul>
<div class="jb51code"><pre class="brush:cpp;">#include "Common.xcconfig"
APP_NAME = $(inherited)Mamba
</pre></div>
<ul><li>release</li></ul>
<div class="jb51code"><pre class="brush:cpp;">```Swift
#include "Common.xcconfig"
APP_NAME = $(inherited)Release
</pre></div>
<p>上面利用<code>#include</code>进行导入依赖的<code>Configuration</code>文件,并利用<code>$(inherited)</code>来引用依赖的<code>Configuration</code>文件中的变量。</p>
<p><code>Configuration</code>文件中的语法一般是<code>SETTING_NAME = VALUE</code>,具体等式二边设置的值可见苹果官网.</p>
<p class="maodian"></p><h3>设置Configuration</h3>
<p>点击<code>PROJECT</code>导航栏并选择<code>Info</code>会发现多了一个上文我们添加的名为<code>Mamba</code>的<code>Configuration</code>。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202209/20220908085858052.jpg" /></p>
<p>点击左边的小三角箭头展开每个<code>Configuration</code>后可以设置项目的<code>project</code>级别的<code>Configuration File</code>和<code>target</code>级别的<code>Configuration File</code>,当然也可以默认不设置。分别设置三个<code>Configuration</code>下的<code>project</code>级别的<code>Configuration File</code>为<code>Base</code>,<code>target</code>级别的<code>Configuration File</code>则为对应的<code>Configuration File</code>,如下图所示:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202209/20220908085858053.jpg" /></p>
<p class="maodian"></p><h3>查看是否设置成功</h3>
<p>点击<code>TARGETS</code>导航栏,选择<code>Build Settings</code>并选中<code>All</code>和<code>Levels</code>滑到最下面可看见<code>APP_NAME</code>的值设置如下:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202209/20220908085858054.jpg" /></p>
<p>这里需要解释一下几个设置的级别:</p>
<ul><li><code>Resolved</code>: 最后生效的值</li><li><code>Target</code>: 显示在<code>Target</code>级别生效的值,<code>Target</code>级别的优先级是高于<code>Project</code>的,并且默认继承<code>Project</code>设置的值。</li><li><code>Project</code>: 显示在<code>Project</code>级别生效的值,往常在<code>Xcode</code>的<code>General</code>设置的值就是这一级别的。</li><li><code>iOS Default</code> : 显示<code>iOS</code>默认设置的值。</li></ul>
<p>加上<code>Configuration File</code>后优先级顺序从低到高如下:</p>
<ul><li>Platform defaults</li><li>Project.xcconfig file</li><li>Project file build settings</li><li>Target .xcconfig file</li><li>Target build settings</li></ul>
<p class="maodian"></p><h3>设置Info.plist</h3>
<p>最后为了通过<code>Configuration File</code>来控制<code>APP</code>运行时名字的显示,需要在<code>Info.plist</code>中链接<code>Bundle display name</code>属性(没有的话需要新增)到我们上面设置的<code>user-defined setting(APP_NAME)</code> 上,为此修改<code>Info.plist</code>中<code>Bundle display name</code>的值为 <code>$(APP_NAME)</code>。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202209/20220908085858055.jpg" /></p>
<p class="maodian"></p><h3>测试是否生效</h3>
<p>在<code>Scheme</code>页面分别选择<code>debug</code>,<code>release</code>和<code>mamba</code>三中不同的<code>Configuration</code>环境运行<code>APP</code>成功的根据不同的<code>Configtation</code>设置不同的项目运行名字。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202209/20220908085858056.jpg" /></p>
<p class="maodian"></p><h2>利用xconfig文件实现OC条件编译</h2>
<p>在开发中经常需要进行条件编译,在<code>OC</code>中可以利用<code>pch</code>文件配合宏来实现,例如如下:</p>
<div class="jb51code"><pre class="brush:cpp;">#ifdef DEBUG
#define BaseURL @"192.168.1.1:8080/appname/api"
#define PublicKEY @"QWE3R23WR09WURI220WR3TTY5ET3CR2X"
#else
#define BaseURL @"http://api.appname.com"
#define PublicKEY @"32GDG4575UB5M97O7M2X32RFH53QWT43"
#endif
</pre></div>
<p>通过在<code>pch</code>文件中利用条件编译定义不用的宏来实现项目的动态切换配置,上述宏定义一般定义在<code>.pch</code>中,通常<code>.pch</code>文件中定义的宏都比较杂乱,希望能单独放在一个独立的文件中,可以通过新建一个头文件<code>env.h</code>, 把上述宏定义放到<code>env.h</code>中,在需要使用的时候导入头文件即可,把环境参数单独放在一个独立的头文件中,更加简洁,职能更加专一,也便于维护但是这种做法还不是最好的,因为还需要手动导入头文件,而且生产环境参数和开发环境参数是放在同一个文件中而是不是独立分开的,要想独立分开并且使用时又不用导入头文件可以通过<code>Xcode</code>中的<code>Configurations Setting Fil(.xcconfig)</code>来解决,这应该是最优的实现方式。</p>
<p class="maodian"></p><h3>xconfig文件的设置</h3>
<p>在上面的<code>Debug.xconfig</code>和<code>Mamba.xconfig</code>文件中分别加入如下代码:</p>
<ul><li>Debug.xconfig</li></ul>
<div class="jb51code"><pre class="brush:cpp;">WEBSERVICE_URL = @"www.baidu.com"
</pre></div>
<ul><li>Mamba.xconfig</li></ul>
<div class="jb51code"><pre class="brush:cpp;">WEBSERVICE_URL = @"www.jd.com"
</pre></div>
<p>这样只是自定义了一个<code>Build Setting</code>变量,不能代码里像使用宏那样使用,<code>Xcode</code>是支持利用<code>GCC_PREPROCESSOR_DEFINITIONS</code>在定义宏的,在<code>Common.xconfig</code>文件中加入如下代码:</p>
<div class="jb51code"><pre class="brush:cpp;">GCC_PREPROCESSOR_DEFINITIONS = $(inherited) WEBSERVICE_URL='$(WEBSERVICE_URL)'
</pre></div>
<p>在<code>TARGET</code>导航栏中<code>Preprocessor Macros</code>即可看见我们定义的宏。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202209/20220908085858057.jpg" /></p>
<p>代码使用</p>
<p>可以在代码中直接使用定义的宏,当切换<code>Configuration</code>时则会根据<code>.xconfig</code>文件输入不同的打印。</p>
<div class="jb51code"><pre class="brush:cpp;">- (void)viewDidLoad {
;
NSLog(@"-----------%@-------------",WEBSERVICE_URL);
}
</pre></div>
<p>使用<code>#include</code>语法来包含其他配置文件,如<code>#include "Common.xcconfig"</code>, 最好是放在文件的最后面,放在文件的开头也可以。<code>Common.xconfig</code>中第一个键的配置必须有:<code>GCC_PREPROCESSOR_DEFINITIONS = $(inherited)</code>,没有<code>Xcode</code>会报错,暴露自定义键时的语法:宏名=<code>'$(key)'</code>,在代码或其他地方使用宏名来引用,<code>'$(key)'</code>:通过<code>key</code>来指定每个模式下的对应的自定义键的名字,通常将宏的名字和<code>key</code>的名字保持一致, 注意 等号前后一定不能有空,<code>Common.xconfig</code>中第一个<code>key</code>是<code>GCC_PREPROCESSOR_DEFINITIONS = $(inherited)</code> 后面跟自定义的<code>key</code>,注意在第一个<code>key</code>后面跟上自己定义的<code>key</code>的时候一定不要回车换行,敲一个空格,然后在同一行后面追加就行了,换行会编译错误, 不能换行,不能换行,不能换行!</p>
<p class="maodian"></p><h2>Swift中条件编译的实现</h2>
<p>在<code>Swift</code>中是不支持通过<code>GCC_PREPROCESSOR_DEFINITIONS</code>来定义宏的,但是可以通过定义<code>Custom Flags</code>进行定义,这里介绍另外一种方法,还是通过<code>.xconfig文件</code>进行获取我们需要的宏。前面我们通过<code>info.plist</code>获取到了<code>.xconfig</code>文件中自定义的变量,再次我们同样通过<code>info.plist</code>来获取自定义的变量的值来当做宏使用,首先在<code>info.plist</code>中新建一个<code>WEBSERVICE_URL</code>变量,并设置值为<code>'$(WEBSERVICE_URL)'</code>,由于需要解析<code>info.plist</code>中的变量,再次封装一个<code>config.swift</code>的类用来解析:</p>
<div class="jb51code"><pre class="brush:cpp;">import Foundation
enum Config {
static func stringValue(forKey key: String) -&gt; String {
guard let value = Bundle.main.object(forInfoDictionaryKey: key) as? String else {
fatalError("Invalid value or undefined key")
}
return value
}
}
</pre></div>
<p>代码使用</p>
<div class="jb51code"><pre class="brush:cpp;">import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print(Config.stringValue(forKey:"WEBSERVICE_URL"))
}
}
</pre></div>
<p>相比较于<code>OC</code>版本的是不能直接定义宏,需要通过在<code>info.plist</code>定义后并通过方法取出值后才能使用,稍微麻烦了一点。</p>
<p class="maodian"></p><h2>script的使用</h2>
<p>上文我们已经知道<code>xconfig</code>文件的使用,其实在编译之前不只是变量的自定义或者获取项目的一些默认参数,还可以在获取这些参数的基础上,将这些参数作为<code>script</code>脚本的变量来做一些更有意义的事情,<code>Xcode</code>是支持在编译之前链接<code>script</code>脚本的。</p>
<p class="maodian"></p><h3>script的初步认识</h3>
<p>脚本一般来说就是可执行的二进制文件,下面先制作一个简单的脚本加深认知(实例代码采用<code>Swift</code>),首先新建一个名为<code>HelloXcode.swift</code>文件,加入如下代码:</p>
<div class="jb51code"><pre class="brush:cpp;">import Foundation
@main
enum MyScript {
static func main() {
print("Hello Xcode")
}
}
</pre></div>
<p>下面我们用终端来编译上面的<code>HelloXcode.swift</code>文件,<code>cd</code>到文件所在的目录执行以下代码:</p>
<div class="jb51code"><pre class="brush:bash;">xcrun --sdk macosx swiftc -parse-as-library HelloXcode.swift -o CompiledScript
</pre></div>
<p>利用<code>macOS SDK</code>来编译<code>HelloXcode.swift</code>并输出名为<code>CompiledScript</code>的二进制脚本文件,此时可以直接在当前目录利用<code>./CompiledScript</code>执行该脚本文件,会直接输出打印<code>HelloXcode</code>。为了在<code>Xcode</code>编译阶段就能运行脚本,我们需要将脚本插入到<code>Xcode</code>的<code>Build Phases</code>中,首先我们先新建一个<code>Build Phases</code>如下所示:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202209/20220908085858058.jpg" /></p>
<p><code>Xcode</code>中的<code>Build Phases</code>选项卡是<code>Xcode build</code>项目的中心,<code>Xcode</code>在编译项目时其实帮我们做了如下几件事情:</p>
<ul><li>确定项目的一些依赖并编译</li><li>编译项目的代码</li><li>链接上面编译的依赖文件</li><li>复制资源文件例如图片等到项目bundle中</li></ul>
<p>这里我们是要在项目编译开始之前就运行脚本,所以需要调整新增加的<code>Build Phases</code>的顺序,直接拖到<code>Denpencies</code>下面,如下图所示:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202209/20220908085858059.gif" /></p>
<p><br />点击刚刚新加的<code>Build Phases</code>可以重命名,展开后加入如下代码:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202209/20220908085858060.jpg" /></p>
<p><br />下面的<code>Input Files</code>可以理解为脚本的变量,这里将<code>HelloXcode.swift</code>相对工程文件所在的路<code>$SCRIPT_INPUT_FILE_0</code>进行引用,<code>$(SRCROOT)</code>代表工程文件所在的目录,运行项目在<code>build log</code>(不是打印台)会看见如下输出:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202209/20220908085858061.jpg" /></p>
<p class="maodian"></p><h3>script的实际运用</h3>
<p>利用<code>script</code>来实现每当build的时候改变 <code>Info.plist</code>中<code>Bundle version</code>或者<code>Bundle version string (short)</code>的值,新建一个<code>IncBuildNumber.swift</code> 文件,加入如下代码:</p>
<div class="jb51code"><pre class="brush:cpp;">import Foundation
@main
enum IncBuildNumber {
static func main() {
guard let infoFile = ProcessInfo.processInfo
.environment["INFOPLIST_FILE"]
else {
return
}
guard let projectDir = ProcessInfo.processInfo.environment["SRCROOT"] else {
return
}
if var dict = NSDictionary(contentsOfFile:
projectDir + "/" + infoFile) as? {
guard
let currentVersionString = dict["CFBundleShortVersionString"]
as? String,
let currentBuildNumberString = dict["CFBundleVersion"] as? String,
let currentBuildNumber = Int(currentBuildNumberString)
else {
return
}
dict["CFBundleVersion"] = "\(currentBuildNumber + 1)"
if ProcessInfo.processInfo.environment["CONFIGURATION"] == "Release" {
var versionComponents = currentVersionString
.components(separatedBy: ".")
let lastComponent = (Int(versionComponents.last ?? "1") ?? 1)
versionComponents =
"\(lastComponent + 1)"
dict["CFBundleShortVersionString"] = versionComponents
.joined(separator: ".")
}
(dict as NSDictionary).write(
toFile: projectDir + "/" + infoFile,
atomically: true)
}
}
}
</pre></div>
<p>当<code>Xcode</code>在执行<code>run script phase</code>时会通过环境变量<code>environment variables</code>来共享<code>build settings</code>,可以将环境变量在这里理解为全局变量,这里通过环境变量拿到了<code>info.plist</code>中的<code>CFBundleShortVersionString</code>和<code>CFBundleVersion</code>变量,并根据<code>CONFIGURATION</code>配置的是<code>Release</code>还是<code>Debug</code>来修改对应的<code>BundleVersion</code>,至此每当<code>build时</code>都会改变相应的<code>BuildVersion</code>值。</p>
<p class="maodian"></p><h2>总结</h2>
<p>本文主要介绍了利用<code>xconfig</code>文件如何进行项目的动态配置,并进行了实际的演示,同时介绍了<code>script</code>在<code>Xcode</code>中编译的基本使用,并配合<code>xconfig</code>文件能让<code>Xcode</code>在编译前做更多有意义的事情,更多关于iOS使用xconfig script脚本的资料请关注琼殿技术社区其它相关文章!</p>
<div class="art_xg">
<b>您可能感兴趣的文章:</b><ul><li>iOS开发之WKWebViewJavascriptBridge Xcode9中导致crash的解决</li><li>IOS ObjectC与javascript交互详解及实现代码</li><li>JS IOS/iPhone的Safari浏览器不兼容Javascript中的Date()问题如何解决</li><li>iOS开发之用javascript调用oc方法而非url</li><li>iOS中使用JSPatch框架使Objective-C与JavaScript代码交互</li></ul>
</div>
</div>
<!--endmain-->
頁:
[1]