你是否好奇Cocoapods是如何修改掉Xcode工程的結構?你也是否曾被Xcode工程的配置文件裡面雜亂的內容搞得摸不清頭腦?你又是否知道Xcodeproj這個神奇的Ruby庫?下面我將通過這個系列來解除你的困惑。
我們知道Cocoapods是用ruby創作的一套第三方庫,它很方便的可以刪除、添加、更新第三方庫?當你執行修改完PodFile執行pod update的時候,你會驚訝的發現Xcode工程被神奇的修改掉了。那麼它是如何做到的呢?細心的你會發現,每個Xcode工程都有一個project.pbxproj文件,這個文件記錄著該工程的文件結構。Cocoapods正是通過它的組件Xcodeproj來對工程結構進行修改。
如果你使用過SVN或者Git進行團隊協作開發肯定會不可避免的遇到在合並代碼的時候往往由於有過添加和刪除文件的操作導致Xcode工程報錯打不開,這時候一般的解決思路是打開project.pbxproj文件,Command+F鍵入======或者<<<<<來找到沖突的地方,將沖突的內容刪除。然而有些人並不知道為何要這樣解決甚至不知道裡面的內容是何意思?下面的內容或許對你有些許幫助。
project.pbxproj采用的是老式風格的plist文件(old ASCII plist),這最早是Next公司采用的一種文件格式,它跟XML格式很多地方類似,但是又有些許的不同。為了更方便理解,我建議你新建一個工程或者在以後的工程上打開project.pbxproj,在實例的基礎上便於直觀感受,更有助於
加深理解。
首先我要介紹它裡面的眾多元素,例如
objc
根節點
PBXBuildFile
PBXBuildPhase
PBXAppleScriptBuildPhase
PBXCopyFilesBuildPhase
PBXFrameworksBuildPhase
PBXHeadersBuildPhase
PBXResourcesBuildPhase
PBXShellScriptBuildPhase
PBXSourcesBuildPhase
PBXContainerItemProxy
PBXFileElement
PBXFileReference
PBXGroup
PBXVariantGroup
PBXTarget
PBXAggregateTarget
PBXLegacyTarget
PBXNativeTarget
PBXProject
PBXTargetDependency
XCBuildConfiguration
XCConfigurationList
在萬物皆對象的概念下,你尚可將他們理解為一個個類,它們裡面的各個子元素就是一個個對象。最外層的每個元素如PBXBuildFile被稱為一個個Section,為方便理解,文章後面的內容我都將這些元素稱為類,將元素的實例成為對象。
``` // !$UTF8$! { archiveVersion = 1; classes = { }; objectVersion = 45; objects = {...}; rootObject = 0867D690FE84028FC02AAC07 /* Project object */; }
```
如果你已經打開了一個project.pbxproj,你就會很容易看到這種結構,只不過objects裡面的各種類屬於第二層結構,rootObject位於文件的最後一行。
細心的你會看到,上面的根節點裡面的rootObject後面是一串24位的16進制數,它就是每個對象的唯一標識碼,它可以唯一標識文件的每個對象,也就是說
每個元素的標識碼都是不同的。Xcode生成唯一標識碼的算法可能引入了日期、序列和其它一些預定義的值,但是並沒有確切的文檔說明具體的生成過程。值得注意的是,該唯一標識碼不僅在所在的工程中唯一,而且還是跨工程唯一。
PBXBuildFile是文件類,被PBXBuildPhase等作為文件包含或被引用的資源。此時我已經新建了一個名為Xcode工程Demo的工程,此時的工程結構是這樣,如圖1所示。而此時的project.pbxproj中PBXBuildFile的結構如圖2所示。


可以清楚的看到每個PBXBuildFile對象都是由以下的結構組成
objc
4D05CA6B1193055000125045 /* xxx.c in Sources */ = {
isa = PBXBuildFile;
fileRef = 4D05CA411193055000125045 /* xxx.c */;
};

其中isa跟Objc中的對象的isa指針一樣,指向的是它的類,而fileRef則指向的是一個PBXFileReference對象,這個類將在下面介紹。
細心的你又會發現,為什麼圖1和圖2中的文件個數不一致,卻和圖3中編譯時的文件和資源統一。前者的差異是由於PBXFileReference所致,通過後者我們可以大膽猜測,PBXBuildFile中的對象是編譯時候需要確認的文件和資源的集合,如果不信的話可以拖幾張圖片資源扔進工程中
,經過驗證結果和預測的情況一致。
PBXFileReference用於跟蹤項目引用的每一個外部文件,比如源代碼文件、資源文件、庫文件、生成目標文件等。具體表現如圖4。

它的結構如下:
objc
87293F901153D870007AFD45 /* objc.mm */ = {
isa = PBXFileReference;
fileEncoding = 4;
lastKnownFileType = sourcecode.cpp.objcpp;
name = monobjc.mm;
path = sources/monobjc.mm;
sourceTree = "";
};
裡面的每個key的含義,對照著實際工程,大家不妨自行揣測。
我們再將PBXBuildFile和PBXFileReference放一起進行對比,如圖5。

AppDelegate.swift對象通過fileRef指向標識符為F3E1481A1DA50A180059397C的PBXFileReference對象,通過這個引用,一個PBXBuildFile對象就可以查到自己的具體信息,如fileType、name和path等信息。
PBXGroup用於組文件,或者嵌套組。讓我們來看下實例,如圖6

怡然是通過唯一標識符組裝,每個PBXGroup對象都有一個children屬性,裡面可以是任何一種類的對象。但是這時候的PBXGroup指的是Xcode裡面組織的分組結構,和實際文件系統中的結構並不相同。
指的注意的是,children中的每個文件對象都屬於PBXFileReference類,而不是PBXBuildFile類
PBXNativeTarget就是工程中的target,如果工程中有多個target,都會在這個section中有所體現。
實例中如圖7所示

我們都知道每個target都有Compile Sources、Copy Bundle Resources、Link Binary With Libiaries這三個需要在編譯時確定的內容。
而在PBXNativeTarget中通過buildPhases屬性可以找到對應的內容。
PBXSourcesBuildPhase用於構建階段中編譯源文件,PBXResourcesBuildPhase用於構建階段需要復制的資源文件,如圖8

需要注意的是,PBXSourcesBuildPhase這個section中放著所有的target的同類對象,PBXResourcesBuildPhase也是一樣。
PBXProject標識著整個工程,由根元素的rootObject引入。如圖9所示

該對象記錄著targets、mainGroup等重要信息,甚至每個target在創建時候的Xcode版本都會記錄在其中。
還有其他很多重要的元素,如記錄工程配置信息的XCConfigurationList和XCBuildConfiguration等,大家可以自行研究研究。
由此看來,以前看到就頭疼的project.pbxproj配置文件的內容並沒有想象中的復雜,也可以看出Xcode文件組織的嚴密和周整。
大家自己研究的時候,不妨可以動手改改項目中的內容,再去觀察配置文件的變化,這樣既可以有更深的理解,或許有新發現也說不定奧。
下篇文章,我將帶大家用Xcodeproj這個庫來,通過幾行代碼修改project.pbxproj中的內容以達到通過腳本去修改Xcode工程和分析工程的目的。
通過Xcodeproj深入探究Xcode工程文件 一
通過Xcodeproj深入探究Xcode工程文件 二