通過Xcodeproj深入探究Xcode工程檔案
你是否好奇Cocoapods是如何修改掉Xcode工程的結構?你也是否曾被Xcode工程的配置檔案裡面雜亂的內容搞得摸不清頭腦?你又是否知道Xcodeproj這個神奇的Ruby庫?下面我將通過這個系列來解除你的困惑。
Cocoapods是如何修改Xcode工程結構的?
我們知道Cocoapods是用ruby創作的一套第三方庫,它很方便的可以刪除、新增、更新第三方庫?當你執行修改完PodFile
執行pod update
的時候,你會驚訝的發現Xcode工程被神奇的修改掉了。那麼它是如何做到的呢?細心的你會發現,每個Xcode工程都有一個project.pbxproj
檔案,這個檔案記錄著該工程的檔案結構。Cocoapods正是通過它的元件
project.pbxproj這個檔案裡面的內容到底是什麼含義?
如果你使用過SVN
或者Git
進行團隊協作開發肯定會不可避免的遇到在合併程式碼的時候往往由於有過新增和刪除檔案的操作導致Xcode工程報錯打不開,這時候一般的解決思路是開啟project.pbxproj
檔案,Command+F
鍵入======
或者<<<<<
來找到衝突的地方,將衝突的內容刪除。然而有些人並不知道為何要這樣解決甚至不知道里面的內容是何意思?下面的內容或許對你有些許幫助。
project.pbxproj介紹
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
,為方便理解,文章後面的內容我都將這些元素稱為類,將元素的例項成為物件。
project.pbxproj的整體結構(根節點)
``` // !$UTF8$! { archiveVersion = 1; classes = { }; objectVersion = 45; objects = {...}; rootObject = 0867D690FE84028FC02AAC07 /* Project object */; }
```
如果你已經打開了一個project.pbxproj
,你就會很容易看到這種結構,只不過objects
裡面的各種類屬於第二層結構,rootObject
位於檔案的最後一行。
唯一標識碼
細心的你會看到,上面的根節點裡面的rootObject
後面是一串24位的16進位制數
,它就是每個物件的唯一標識碼,它可以唯一標識檔案的每個物件,也就是說 每個元素的標識碼都是不同的。Xcode生成唯一標識碼的演算法可能引入了日期、序列和其它一些預定義的值,但是並沒有確切的文件說明具體的生成過程。值得注意的是,該唯一標識碼不僅在所在的工程中唯一,而且還是跨工程唯一。
PBXBuildFile
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
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
PBXGroup
用於組檔案,或者巢狀組。讓我們來看下例項,如圖6
怡然是通過唯一識別符號組裝,每個PBXGroup
物件都有一個children
屬性,裡面可以是任何一種類的物件。但是這時候的PBXGroup
指的是Xcode裡面組織的分組結構,和實際檔案系統中的結構並不相同。 指的注意的是,children
中的每個檔案物件都屬於PBXFileReference
類,而不是PBXBuildFile
類
PBXNativeTarget
PBXNativeTarget
就是工程中的target,如果工程中有多個target,都會在這個section中有所體現。 例項中如圖7所示
我們都知道每個target都有Compile Sources
、Copy Bundle Resources
、Link Binary With Libiaries
這三個需要在編譯時確定的內容。 而在PBXNativeTarget
中通過buildPhases
屬性可以找到對應的內容。
PBXSourcesBuildPhase和PBXResourcesBuildPhase
PBXSourcesBuildPhase
用於構建階段中編譯原始檔,PBXResourcesBuildPhase
用於構建階段需要複製的資原始檔,如圖8
需要注意的是,PBXSourcesBuildPhase
這個section中放著所有的target的同類物件,PBXResourcesBuildPhase
也是一樣。
PBXProject
PBXProject
標識著整個工程,由根元素的rootObject
引入。如圖9所示
該物件記錄著targets
、mainGroup
等重要資訊,甚至每個target在建立時候的Xcode版本都會記錄在其中。
其他元素
還有其他很多重要的元素,如記錄工程配置資訊的XCConfigurationList
和XCBuildConfiguration
等,大家可以自行研究研究。
總結
由此看來,以前看到就頭疼的project.pbxproj
配置檔案的內容並沒有想象中的複雜,也可以看出Xcode檔案組織的嚴密和周整。
大家自己研究的時候,不妨可以動手改改專案中的內容,再去觀察配置檔案的變化,這樣既可以有更深的理解,或許有新發現也說不定奧。
下篇文章,我將帶大家用Xcodeproj這個庫來,通過幾行程式碼修改project.pbxproj
中的內容以達到通過指令碼去修改Xcode工程和分析工程的目的。