在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ iOS/ Build 過程
與四軸無人機(jī)的通訊
在沙盒中編寫腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動開發(fā)
Collection View 動畫
截圖測試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進(jìn)的自動布局工具箱
動畫
為 iOS 7 重新設(shè)計 App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動畫解釋
響應(yīng)式 Android 應(yīng)用
初識 TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項目介紹
Swift 的強(qiáng)大之處
測試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機(jī)制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計優(yōu)雅的移動游戲
繪制像素到屏幕上
相機(jī)與照片
音頻 API 一覽
交互式動畫
常見的后臺實踐
糟糕的測試
避免濫用單例
數(shù)據(jù)模型和模型對象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場
照片框架
響應(yīng)式視圖
Square Register 中的擴(kuò)張
DTrace
基礎(chǔ)集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類的設(shè)計
置換測試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機(jī)項目
Mach-O 可執(zhí)行文件
UI 測試
值對象
活動追蹤
依賴注入
Swift
項目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測試 View Controllers
訪談
收據(jù)驗證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場
游戲
調(diào)試核對清單
View Controller 容器
學(xué)無止境
XCTest 測試實戰(zhàn)
iOS 7
Layer 中自定義屬性的動畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴(kuò)展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫支持
Fetch 請求
導(dǎo)入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機(jī)捕捉
語言標(biāo)簽
同步案例學(xué)習(xí)
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識別
玩轉(zhuǎn)字符串
相機(jī)工作原理
Build 過程

Build 過程

近些日子我們被寵壞了 -- 我們只需要單擊 Xcode 中的一個按鈕(這個按鈕看起來有點像是在播放一些音樂的動作),過幾秒鐘之后,我們的程序就會運行起來了,除非遇到一些錯誤,這非常的神奇。

在本文中,我們將從更高級別的角度來解讀 Build 過程,并探索一下在 Xcode 界面中暴露出的 project setting 信息與 Build 過程有什么關(guān)系。為了更加深入的探索 Build 過程中,每一步實際執(zhí)行的工作,我都會在本文中引入一些別的文章。

解密 Build 日志

為了了解 Xcode build 過程的內(nèi)部工作原理,我們首先把突破口瞄準(zhǔn)完整的 log 文件上。打開 Log Navigator ,從列表中選擇一個 Build ,Xcode 會將 log 文件很完美的展現(xiàn)出來。

http://wiki.jikexueyuan.com/project/objc/images/6-1.png" alt="" />

默認(rèn)情況下,上面的 Xcode 界面中隱藏了大量的信息,我們通過選擇任務(wù),然后點擊右邊的展開按鈕,就能看到每個任務(wù)的詳細(xì)信息。另外一種可選的方案就是選中列表中的一個或者多個任務(wù),然后選擇組合鍵 Cmd-C,這將會把所有的純文本信息拷貝至粘貼板。最后,我們還可以選擇 Editor 菜單中的 "Copy transcript for shown results",以此將所有的 log 信息拷貝到粘貼板中。

本文給出的示例中,log 信息將近有 10,000 行(其實大多數(shù)的 log 信息是編譯 OpenSSL 時生成的,并不是我們自己所寫的代碼生成的)。下面我們就開始吧!

注意觀察輸出的 log 信息,首先會發(fā)現(xiàn) log 信息被分為不同的幾大塊,它們與我們工程中的targets相互對應(yīng)著:

Build target Pods-SSZipArchive
...
Build target Makefile-openssl
...
Build target Pods-AFNetworking
...
Build target crypto
...
Build target Pods
...
Build target ssl
...
Build target objcio

本文涉及到的工程有幾個依賴項:其中 AFNetworking 和 SSZipArchive 包含在 Pods 中,而 OpenSSL 則以子工程的形式包含在工程中。

針對工程中的每個 target,Xcode 都會執(zhí)行一系列的操作,將相關(guān)的源碼,根據(jù)所選定的平臺,轉(zhuǎn)換為機(jī)器可讀的二進(jìn)制文件。下面我們詳細(xì)的了解一下第一個 target:SSZipArchive。

在針對這個 target 輸出的 log 信息中,我們可以看到每個任務(wù)被執(zhí)行的詳細(xì)情況。例如第一個任務(wù)是處理一個預(yù)編譯頭文件(為了增強(qiáng) log 信息的可讀性,我省略了許多細(xì)節(jié)):

(1) ProcessPCH /.../Pods-SSZipArchive-prefix.pch.pch Pods-SSZipArchive-prefix.pch normal armv7 objective-c com.apple.compilers.llvm.clang.1_0.compiler
    (2) cd /.../Dev/objcio/Pods
        setenv LANG en_US.US-ASCII
        setenv PATH "..."
    (3) /.../Xcode.app/.../clang 
            (4) -x objective-c-header 
            (5) -arch armv7 
            ... configuration and warning flags ...
            (6) -DDEBUG=1 -DCOCOAPODS=1 
            ... include paths and more ...
            (7) -c 
            (8) /.../Pods-SSZipArchive-prefix.pch 
            (9) -o /.../Pods-SSZipArchive-prefix.pch.pch

在 build 處理過程中,每個任務(wù)都會出現(xiàn)類似上面的這些 log 信息,我們就通過上面的 log 信息進(jìn)一步了解詳情。

  1. 類似上面的每個 log 信息塊都會利用一行 log 信息來描述相關(guān)的任務(wù)作為起點。
  2. 接著輸出帶縮進(jìn)的3行 log 信息,列出了該任務(wù)執(zhí)行的語句。此處,工作目錄發(fā)生了改變,并對 LANG 和 PATH 環(huán)境變量進(jìn)行設(shè)置。
  3. 這里是發(fā)生奇跡的地方。為了處理一個.pch文件,調(diào)用了 clang,并附帶了許多可選項。下面跟著輸出的 log 信息顯示了完整的調(diào)用過程,以及所有的參數(shù)。我們看看其中的幾個參數(shù)...
  4. -x 標(biāo)示符用來指定所使用的語言,此處是 objective-c-header。
  5. 目標(biāo)架構(gòu)指定為 armv7。
  6. 暗示 #defines 的內(nèi)容已經(jīng)被添加了。
  7. -c 標(biāo)示符用來告訴 clang 具體該如何做。-c 表示:運行預(yù)處理器、詞法分析器、類型檢查、LLVM 的生成和優(yōu)化,以及 target 指定匯編代碼的生成階段,最后,運行匯編器以產(chǎn)出一個.o的目標(biāo)文件。
  8. 輸入文件。
  9. 輸出文件。

雖然有大量的 log 信息,不過我不會對每個任務(wù)做詳細(xì)的介紹。我們的重點是讓你全面的了解在整個 build 過程中,哪些工具會被調(diào)用,以及背后會使用到了哪些參數(shù)。

針對這個 target ,雖然只有一個 .pch 文件,但實際上這里對 objective-c-header 文件的處理有兩個任務(wù)。通過觀察具體輸出的 log 信息,我們可以知道詳情:

ProcessPCH /.../Pods-SSZipArchive-prefix.pch.pch Pods-SSZipArchive-prefix.pch normal armv7 objective-c ...
ProcessPCH /.../Pods-SSZipArchive-prefix.pch.pch Pods-SSZipArchive-prefix.pch normal armv7s objective-c ...

從上面的 log 信息中,可以明顯的看出 target 針對兩種架構(gòu)做了 build -- armv7 和 armv7s -- 因此 clang 對文件做了兩次處理,每次針對一種架構(gòu)。

在處理預(yù)編譯頭文件之后,可以看到針對 SSZipArchive target 有另外的幾個任務(wù)類型。

CompileC ...
Libtool ...
CreateUniversalBinary ...

顧名思義:CompileC 用來編譯 .m.c 文件,Libtool 用來從目標(biāo)文件中構(gòu)建 library,而 CreateUniversalBinary 則將上一階段產(chǎn)生的兩個 .a 文件(每個文件對應(yīng)一種架構(gòu))合并為一個通用的二進(jìn)制文件,這樣就能同時在 armv7 和 armv7s 上面運行。

接著,在工程中其它一些依賴項也會發(fā)生于此類似的步驟。AFNetworking 被編譯之后,會與 SSZipArchive 進(jìn)行鏈接,以當(dāng)做 pod library。OpenSSL 編譯之后,會接著處理 crypto 和 ssl target。

當(dāng)所有的依賴項都 build 完成之后,就輪到我們程序的 target 了。Build 該 target 時,輸出的 log 信息會包含一些非常有價值,并且之前沒有出現(xiàn)過的內(nèi)容:

PhaseScriptExecution ...
DataModelVersionCompile ...
Ld ...
GenerateDSYMFile ...
CopyStringsFile ...
CpResource ...
CopyPNGFile ...
CompileAssetCatalog ...
ProcessInfoPlistFile ...
ProcessProductPackaging /.../some-hash.mobileprovision ...
ProcessProductPackaging objcio/objcio.entitlements ...
CodeSign ...

在上面的任務(wù)列表中,根據(jù)名稱不能區(qū)分的唯一任務(wù)可能就是 Ld,Ld 是一個 linker 工具的名稱,與 libtool 非常相似。實際上,libtool也是簡單的調(diào)用 ldlipo。'ld'被用來構(gòu)建可執(zhí)行文件,而libtool則用來構(gòu)建 library 文件。閱讀DanielChris兩篇文章,可以了解到更多關(guān)于編譯和鏈接的工作原理。

上面每一個步驟,實際上都會調(diào)用相關(guān)的命令行工具來做實際的工作,這跟之前我們看到的的 ProcessPCH 類似。至此,我將不會繼續(xù)介紹這些 log 信息了,我將帶領(lǐng)大家從另外一個不同的角度來繼續(xù)探索這些任務(wù):Xcode 是如何知道哪些任務(wù)需要被執(zhí)行?

Build過程的控制

當(dāng)你選擇 Xcode 5 中的一個工程時,會在 project editor 頂部顯示出 6 個 tabs:General, Capabilities, Info, Build Settings, Build Phases 以及 Build Rules。

http://wiki.jikexueyuan.com/project/objc/images/6-2.png" alt="" />

對于我們理解 build 過程來說,其中最后 3 項與 build 過程緊密相連。

Build Phases

Build Phases 代表著將代碼轉(zhuǎn)變?yōu)榭蓤?zhí)行文件的最高級別規(guī)則。里面描述了 build 過程中必須執(zhí)行的不同類型規(guī)則。

http://wiki.jikexueyuan.com/project/objc/images/6-3.png" alt="" />

首先是 target 依賴項的構(gòu)建。這里會告訴 build 系統(tǒng),build 當(dāng)前的 target 之前,必須先對這里的依賴性進(jìn)行 build。實際上這并不屬于真正的 build phase,在這里,Xcode 只不過將其與 build phase 顯示到一塊罷了。

接著在 build phase中是一個 CocoaPods 相關(guān)的腳本 script execution,接著在 Compile Sources section 中規(guī)定了所有必須參與編譯的文件。需要留意的是,這里并沒有指明這些文件是如何被編譯處理的。關(guān)于處理這些文件的更多內(nèi)容,我們將在研究 build rules 和 build settings 時學(xué)習(xí)到。此處列出的所有文件將根據(jù)相關(guān)的 rules 和 settings 被處理。

當(dāng)編譯結(jié)束之后,接下來就是將編譯所生成的目標(biāo)文件鏈接到一塊。注意觀察,Xcode 中的 build phase 之后是:"Link Binary with Libraries." 這里面列出了所有的靜態(tài)庫和動態(tài)庫,這些庫會參與上面編譯階段生成的目標(biāo)文件進(jìn)行鏈接。靜態(tài)庫和動態(tài)庫的處理過程有非常大的區(qū)別,相關(guān)內(nèi)容請參考 Daniel的文章 Mach-O 可執(zhí)行文件

當(dāng)鏈接完成之后,build phase 中最后需要處理的就是將靜態(tài)資源(例如圖片和字體)拷貝到 app bundle 中。需要注意的是,如果圖片資源是PNG格式,那么不僅僅對其進(jìn)行拷貝,還會做一些優(yōu)化(如果 build settings 中的 PNG 優(yōu)化是打開的話)。

雖然靜態(tài)資源的拷貝是 build phase 中的最后一步,但 build 還沒有完成。例如,還沒有進(jìn)行 code signing (這并不是 build phase 考慮的范疇),code signing 屬于 build 步驟中的最后一步 "Packaging"。

定制Build Phases

至此,如果不考慮默認(rèn)設(shè)置的話,你已經(jīng)可以完全掌握了上面介紹的 build phases。例如,你可以在 build phases 中添加運行自定義腳本,就像CocoaPods使用的一樣,來做額外的工作。當(dāng)然也可以添加一些資源的拷貝任務(wù),當(dāng)你需要將某些確定的資源拷貝到指定的 target 目錄中,這非常有用。

另外定制 build phases 有一個非常好用的功能:添加帶有水?。òò姹咎柡?commit hash)的 app icon -- 只需要在 build phase 中添加一個 "Run Script",并用下面的命令來獲取版本號和 commit hash:

version=`/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "${INFOPLIST_FILE}"`
commit=`git rev-parse --short HEAD`

然后使用 ImageMagick 來修改 app icon。這里有一個完整的示例,可以參考。

如果你希望自己或者別人編寫的代碼看起來比較簡潔點,可以添加一個 "Run Script":如果一個源文件超過指定行數(shù),就發(fā)出警告。如下代碼所示,設(shè)置的行數(shù)為 200。

find "${SRCROOT}" \( -name "*.h" -or -name "*.m" \) -print0 | xargs -0 wc -l | awk '$1 > 200 && $2 != "total" { print $2 ":1: warning: file more than 200 lines" }'

Build Rules

Build rules 指定了不同的文件類型該如何編譯。一般來說,開發(fā)者并不需要修改這里面的內(nèi)容。如果你需要對特定類型的文件添加處理方法,那么可以在此處添加一條新的規(guī)則。

一條 build rule 指定了其應(yīng)用于哪種類型文件,該類型文件是如何被處理的,以及輸出的內(nèi)容該如何處置。比方說,我們創(chuàng)建了一條預(yù)處理規(guī)則,該規(guī)則將 Objective-C 的實現(xiàn)文件當(dāng)做輸入,解析文件中的注釋內(nèi)容,最后再輸出一個 .m 文件,文件中包含了生成的代碼。由于我們不能將 .m 文件既當(dāng)做輸入又當(dāng)做輸出,所以我使用了 .mal 后綴,定制的 build rule 如下所示:

http://wiki.jikexueyuan.com/project/objc/images/6-4.png" alt="" />

上面的規(guī)則應(yīng)用于所有后綴為 *.mal 的文件,這些文件會被自定義的腳本處理(調(diào)用我們的預(yù)處理器,并附帶上輸入和輸出參數(shù))。最后,該規(guī)則告訴 build system 在哪里可以找到此規(guī)則的輸出文件。

在腳本中,我使用了少量的變量來指定正確的路徑和文件名。在蘋果的 Build Setting Reference 文檔中可以找到所有可用的變量。build 過程中,要想觀察所有已存在的環(huán)境變量,你可以在 build phase 中添加一個 "Run Script",并勾選上 "Show environment variables in build log"。

Build Settings

至此,我們已經(jīng)了解到在 build phases 中是如何定義 build 處理的過程,以及 build rules 是如何指定哪些文件類型在編譯階段需要被預(yù)處理。在 build settings 中,我們可以配置每個任務(wù)(之前在 build log 輸出中看到的任務(wù))的詳細(xì)內(nèi)容。

你會發(fā)現(xiàn) build 過程的每一個階段,都有許多選項:從編譯、鏈接一直到 code signing 和 packaging。注意,settings 是如何被分割為不同的部分 -- 其實這大部分會與 build phases 有關(guān)聯(lián),有時候也會指定編譯的文件類型。

這些選項基本都有很好的文檔介紹,你可以在右邊面板中的 quick help inspector 或者 Build Setting Reference 中查看到。

工程文件

上面我們介紹的所有內(nèi)容都被保存在工程文件(.pbxproj)中,除了其它一些工程相關(guān)信息(例如 file groups),我們很少會深入該文件內(nèi)部,除非在代碼 merge 時發(fā)生沖突,或許會進(jìn)去看看。

建議你用文本編輯器打開一個工程文件,從頭到尾看一遍里面的內(nèi)容。它的可讀性非常高,里面的許多內(nèi)容一看就知道什么意思了,不會存在太大的問題。通過閱讀并完全理解工程文件,這對于合并工程文件的沖突非常有幫助。

首先,我們來看看文件中叫做 rootObject 的條目。在我的工程中,如下所示:

rootObject = 1793817C17A9421F0078255E /* Project object */;

根據(jù)這個 ID(1793817C17A9421F0078255E),我們可以找到 main 工程的定義:

/* Begin PBXProject section */
    1793817C17A9421F0078255E /* Project object */ = {
        isa = PBXProject;
...

在這部分中有一些 keys,順從這些 key,我們可以了解到更多關(guān)于這個工程文件的組成。例如,mainGroup 指向了 root file group。如果你按照這個思路,你可以快速了解到在 .pbxproj 文件中工程的結(jié)構(gòu)。下面我要來介紹一些與 build 過程相關(guān)的內(nèi)容。其中 target key 指向了 build target 的定義:

targets = (
    1793818317A9421F0078255E /* objcio */,
    170E83CE17ABF256006E716E /* objcio Tests */,
);

根據(jù)第一個內(nèi)容,我們找到一個 target 的定義:

1793818317A9421F0078255E /* objcio */ = {
    isa = PBXNativeTarget;
    buildConfigurationList = 179381B617A9421F0078255E /* Build configuration list for PBXNativeTarget "objcio" */;
    buildPhases = (
        F3EB8576A1C24900A8F9CBB6 /* Check Pods Manifest.lock */,
        1793818017A9421F0078255E /* Sources */,
        1793818117A9421F0078255E /* Frameworks */,
        1793818217A9421F0078255E /* Resources */,
        FF25BB7F4B7D4F87AC7A4265 /* Copy Pods Resources */,
    );
    buildRules = (
    );
    dependencies = (
        1769BED917CA8239008B6F5D /* PBXTargetDependency */,
        1769BED717CA8236008B6F5D /* PBXTargetDependency */,
    );
    name = objcio;
    productName = objcio;
    productReference = 1793818417A9421F0078255E /* objcio.app */;
    productType = "com.apple.product-type.application";
};

其中 buildConfigurationList 指向了可用的配置項,一般是 DebugRelease。根據(jù) debug 對應(yīng)的 id,我們可以找到 build setting tab 中所有選項存儲的位置:

179381B717A9421F0078255E /* Debug */ = {
    isa = XCBuildConfiguration;
    baseConfigurationReference = 05D234D6F5E146E9937E8997 /* Pods.xcconfig */;
    buildSettings = {
        ALWAYS_SEARCH_USER_PATHS = YES;
        ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
        CODE_SIGN_ENTITLEMENTS = objcio/objcio.entitlements;
...

buildPhases 屬性則簡單的列出了在 Xcode 中定義的所有 build phases。這非常容易識別出來(Xcode 中的參數(shù)使用了它們原本真正的名字,并以 C 風(fēng)格進(jìn)行注釋)。buildRules 屬性是空的:因為在該工程中,我沒有自定義 build rules。dependencies 列出了在 Xcode build phase tab 中列出的 target 依賴項。

沒那么嚇人,不是嗎?工程中剩下的內(nèi)容就留給你去當(dāng)做練習(xí)來了解吧。只需要順著對象的 ID 走,即可,一旦你找到了敲門,理解了Xcode中工程設(shè)置的不同 section ,那么對于 merge 工程文件的沖突時,將變得非常簡單。甚至可以在 GitHub 中就能閱讀工程文件,而不用將工程文件 clone 到本地,并用 Xcode 打開。

小結(jié)

當(dāng)今的軟件是都用其它復(fù)雜的一些軟件和資源開發(fā)出來的,例如 library 和 build 工具等。反過來,這些工具是構(gòu)建于底層架構(gòu)的,這猶如剝洋蔥一樣,一層包著一層。雖然這樣一層一層的,給人感覺太復(fù)雜,但是你完全可以去深入了解它們,這非常有助于你對軟件的深入理解,實際上當(dāng)你了解之后,這并沒有想象中的那么神奇,只不過它是一層一層堆砌起來的,每一層都是基于下一層構(gòu)建起來的。

本文所探索 build system 的內(nèi)部機(jī)制猶如剝掉洋蔥的一層。其實當(dāng)我們點擊 Xcode 中的運行按鈕時,我們并沒必要理解這個動作涉及到的所有內(nèi)容。我們只是深入理解某一層,然后找到一個有組織的、并且可控的調(diào)用其它工具的順序,如果我們愿意的話,可以做進(jìn)一步的探索。我建議你閱讀本期中的其它文章,以進(jìn)一步了解這個洋蔥的下一層內(nèi)容!