1. 背景
平時(shí)開(kāi)發(fā)中韭寸,經(jīng)常會(huì)遇到xcodeproj沖突购岗,就需要打開(kāi)這個(gè)文件,進(jìn)行處理无埃。當(dāng)然現(xiàn)在也有很多工具或者自動(dòng)化的腳本來(lái)自動(dòng)merge徙瓶,比如 simonwagner/mergepbx, 但這個(gè)文件錯(cuò)綜復(fù)雜录语,尤其當(dāng)項(xiàng)目大到一定階段后倍啥,非常可怕澎埠,所以很多情況還是需要人工來(lái)處理各種沖突虽缕。
所以決定研究下,這個(gè)龐然大物內(nèi)部的結(jié)構(gòu)究竟是怎樣的...
2. XcodeProj的工程結(jié)構(gòu)
2.1 project.pbxproj文件
Xcode每個(gè)項(xiàng)目的工程文件都在xxx.xcodeproj中蒲稳,查看包內(nèi)容氮趋,可以看到真正的內(nèi)容都在project.pbxproj里面,其他有一些xcuserdata之類(lèi)的江耀,不重要剩胁,先忽略。我們主要來(lái)看pbxproj文件祥国。
這是一個(gè)plist文件昵观。記錄了所有代碼的和庫(kù)文件的索引和路徑信息,以及Target信息舌稀,包括Build Setting/Build Phase等等信息啊犬。
雖然xcodeproj提供了很多方便的文件管理和索引,但我自身還是更喜歡無(wú)project文件的代碼壁查,直接與物理目錄對(duì)應(yīng)觉至,不要與IDE產(chǎn)生太多的依賴(lài)和耦合。
2.2 pbxproj預(yù)覽
一個(gè)完整的pbxproj文件基本是如下這樣:
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
…
};
rootObject = 4B74E19C1AB185A200A5A377 /* Project object */;
}
核心內(nèi)容都在objects中睡腿,完整版如下:
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* 構(gòu)建所需的代碼文件语御,資源文件,庫(kù)文件等 */
/* 平時(shí)git發(fā)生沖突也主要是在這個(gè)區(qū)域內(nèi)沖突 */
/* 你每新建一個(gè).h/.m文件席怪,就會(huì)修改這個(gè)區(qū)域, 各個(gè)branch都在創(chuàng)建的時(shí)候应闯,容易沖突 */
/* Begin PBXBuildFile section */
...
/* End PBXBuildFile section */
/* 這里記錄了每個(gè)target的targetProxy,與PBXTargetDependency相對(duì)應(yīng) */
/* Begin PBXContainerItemProxy section */
...
/* End PBXContainerItemProxy section */
/* 主要記錄每個(gè)target的BuildPhase中的Embed App Extensions的部分 */
/* Begin PBXCopyFilesBuildPhase section */
/* End PBXCopyFilesBuildPhase section */
/* 記錄了每個(gè)代碼文件的文件類(lèi)型何恶、路徑path孽锥、sourceTree,不論引入文件的時(shí)候是create group還是create reference,都會(huì)在這里添加一條記錄 */
/* Begin PBXFileReference section */
/* End PBXFileReference section */
/* 工程中所依賴(lài)的Frameworks的信息惜辑,對(duì)應(yīng)Build Phases中的`Link Binary With Libraries` */
/* Begin PBXFrameworksBuildPhase section */
/* End PBXFrameworksBuildPhase section */
/* 工程中所有文件的group信息唬涧,這個(gè)和xcode文件目錄是對(duì)應(yīng)的,每一層的文件目錄有唯一的UUID,同一層group下的子group會(huì)和上一層的group的UUID有很高的重合度(基本只有1-2位不同)盛撑,這個(gè)PBXGroup section中碎节,子group沒(méi)有用樹(shù)的方式,而是采用類(lèi)似列表的方式呈現(xiàn)了所有的group目錄抵卫,可以腦補(bǔ):打開(kāi)xcode左側(cè)目錄狮荔,然后讓所有目錄和文件"左對(duì)齊",然后就會(huì)生成如下的結(jié)構(gòu)` */
/* Begin PBXGroup section */
/* End PBXGroup section */
/* 每個(gè)Target的BuildSettings和BuildPhases(Sources/Frameworks/Resources等)的信息 */
/* Begin PBXNativeTarget section */
/* End PBXNativeTarget section */
/* 整個(gè)項(xiàng)目工程Project的信息介粘,包括項(xiàng)目路徑殖氏、Config信息,相關(guān)版本號(hào)姻采,所有的Target等信息 */
/* Begin PBXProject section */
4B74E19C1AB185A200A5A377 /* Project object */ = {
isa = PBXProject;
attributes = { ... };
...
targets = (
4B74E1A31AB185A200A5A377 /* xxxxPolenTestxxxx */,
...
);
};
/* End PBXProject section */
/* 列舉了項(xiàng)目中每個(gè)Resources的信息, 包括Build Phase下`Copy Bundle Resources`文件雅采、Assets.xcassets等資源文件 */
/* Begin PBXResourcesBuildPhase section */
/* End PBXResourcesBuildPhase section */
/* 對(duì)應(yīng)Xcode中Build Phases下的腳本文件,包括:Embed Pods Frameworks慨亲,Check Pods Manifest.lock以及其他本地或者第三方的腳本文件信息 */
/* Begin PBXShellScriptBuildPhase section */
/* End PBXShellScriptBuildPhase section */
/* 對(duì)應(yīng)Xcode中Build Phases的Complie Sources的代碼文件 */
/* Begin PBXSourcesBuildPhase section */
/* End PBXSourcesBuildPhase section */
/* 記錄了每個(gè)Target的targetProxy婚瓜,每個(gè)targetProxy都是一個(gè)PBXContainerItemProxy類(lèi)型,暫時(shí)沒(méi)找到Xcode中的對(duì)應(yīng)項(xiàng) */
/* Begin PBXTargetDependency section */
/* End PBXTargetDependency section */
/* 不同地區(qū)的資源文件的引用信息刑棵,如果你項(xiàng)目使用了國(guó)際化巴刻,相關(guān)的xxx.string就在這個(gè)section中 */
/* Begin PBXVariantGroup section */
/* End PBXVariantGroup section */
/* 對(duì)應(yīng)Xcode中 Build Settings中的配置信息 */
/* Begin XCBuildConfiguration section */
/* End XCBuildConfiguration section */
/* XCBuildConfiguration只是列舉了所有Target的所有Setting項(xiàng),下面這個(gè)文件區(qū)分蛉签,不同Target在Debug時(shí)使用哪個(gè)Setting項(xiàng)胡陪,在Release時(shí)使用哪個(gè)Setting項(xiàng) */
/* Begin XCConfigurationList section */
/* End XCConfigurationList section */
};
rootObject = 4B74E19C1AB185A200A5A377 /* Project object */;
}
2.3 pbxproj結(jié)構(gòu)
2.3.1 結(jié)構(gòu)圖
首先可以去看一下Xcode Project File Format , 很詳細(xì)的介紹了pbxproj中每個(gè)類(lèi)和及其屬性字段的含義和引用關(guān)系。
從Plist的角度看碍舍,我們可以將PBXProject看成一個(gè)個(gè)節(jié)點(diǎn)和子節(jié)點(diǎn)的樹(shù)形結(jié)構(gòu)督弓,但從面向?qū)ο蟮慕嵌龋鋵?shí)就是一個(gè)個(gè)類(lèi)和子類(lèi)乒验。
自己簡(jiǎn)單整理了一下pbxproj的結(jié)構(gòu)圖 (原創(chuàng)):
2.3.2 Class Hierarchy
下面具體說(shuō)一下每個(gè)節(jié)點(diǎn)/類(lèi) 模塊包含的內(nèi)容以及在Xcode中對(duì)應(yīng)哪些文件或者目錄:
- PBXBuildFile: 構(gòu)建所需的代碼文件,資源文件蒂阱,庫(kù)文件等
- PBXBuildPhase: 對(duì)應(yīng)Xcode中Build Phases
- PBXAppleScriptBuildPhase
- PBXCopyFilesBuildPhase: 主要記錄每個(gè)target的BuildPhase中的
Embed App Extensions
的部分 - PBXFrameworksBuildPhase: 工程中所依賴(lài)的Frameworks的信息锻全,對(duì)應(yīng)Build Phases中的
Link Binary With Libraries
- PBXHeadersBuildPhase
- PBXResourcesBuildPhase: 列舉了項(xiàng)目中每個(gè)Resources的信息, 包括Build Phase下
Copy Bundle Resources
文件、Assets.xcassets
等資源文件 - PBXShellScriptBuildPhase : 對(duì)應(yīng)Xcode中Build Phases下的腳本文件录煤,包括:Embed Pods Frameworks鳄厌,Check Pods Manifest.lock以及其他本地或者第三方的腳本文件信息
- PBXSourcesBuildPhase: 對(duì)應(yīng)Xcode中Build Phases的
Complie Sources
的代碼文件
- PBXContainerItemProxy: 這里記錄了每個(gè)target的targetProxy,與
PBXTargetDependency
相對(duì)應(yīng) - PBXFileElement
- PBXFileReference: 記錄了每個(gè)代碼文件的文件類(lèi)型妈踊、路徑path了嚎、sourceTree, 不論引入文件的時(shí)候是create group還是create reference,都會(huì)在這里添加一條記錄
- PBXGroup:工程中所有文件的group信息,這個(gè)和xcode文件目錄是對(duì)應(yīng)的歪泳,每一層的文件目錄有唯一的UUID,同一層group下的子group會(huì)和上一層的group的UUID有很高的重合度(基本只有1-2位不同)萝勤,這個(gè)
PBXGroup section
中,子group沒(méi)有用樹(shù)的方式呐伞,而是采用類(lèi)似列表的方式呈現(xiàn)了所有的group目錄敌卓,可以腦補(bǔ):打開(kāi)xcode左側(cè)目錄,然后讓所有目錄和文件"左對(duì)齊"伶氢,然后就會(huì)生成如下的結(jié)構(gòu)` - PBXVariantGroup: 不同地區(qū)的資源文件的引用信息趟径,如果你項(xiàng)目使用了國(guó)際化,相關(guān)的xxx.string就在這個(gè)section中
- PBXTarget: 每個(gè)Target的BuildSettings和BuildPhases(Sources/Frameworks/Resources等)的信息
- PBXAggregateTarget: TODO: 暫未找到相關(guān)介紹癣防,自己的項(xiàng)目里也沒(méi)出現(xiàn)這類(lèi)Target
- PBXLegacyTarget:TODO: 暫未找到相關(guān)介紹蜗巧,自己的項(xiàng)目里也沒(méi)出現(xiàn)這類(lèi)Target
- PBXNativeTarget: 正常建立的Target都是這種類(lèi)型的
- PBXProject:整個(gè)項(xiàng)目工程Project的信息,包括項(xiàng)目路徑蕾盯、Config信息幕屹,相關(guān)版本號(hào),所有的Target等信息
- PBXTargetDependency: 記錄了每個(gè)Target的targetProxy刑枝,每個(gè)targetProxy都是一個(gè)
PBXContainerItemProxy
類(lèi)型香嗓,暫時(shí)沒(méi)找到Xcode中的對(duì)應(yīng)項(xiàng) - XCBuildConfiguration: 對(duì)應(yīng)Xcode中 Build Settings中的配置信息
- XCConfigurationList: XCBuildConfiguration只是列舉了所有Target的所有Setting項(xiàng),下面這個(gè)文件區(qū)分装畅,不同Target在Debug時(shí)使用哪個(gè)Setting項(xiàng)靠娱,在Release時(shí)使用哪個(gè)Setting項(xiàng)
分享一點(diǎn)其他人的總結(jié):
PBXProject 為根節(jié)點(diǎn),代表著整個(gè)工程
PBXProject 可以有多個(gè)PBXNativeTarget掠兄,代表著工程中的target
PBXNativeTarget 維護(hù)著各自資源文件(PBXResourcesBuildPhase)像云,源文件(PBXSourcesBuildPhase),以及依賴(lài)庫(kù)(PBXFrameworksBuildPhase)等等
PBXProject 和 PBXNativeTarget 都有配置管理蚂夕,通過(guò)XCConfigurationList和XCBuildConfiguration維護(hù)
每個(gè)導(dǎo)入工程的文件都會(huì)有相應(yīng)的PBXFileReference記錄迅诬,如果該文件在導(dǎo)入時(shí),選擇了create groups 婿牍,會(huì)在相應(yīng)的PBXGroup中有記錄
每個(gè)在編譯打包過(guò)程中被包含到可執(zhí)行文件中的文件侈贷,都會(huì)有PBXBuildFile記錄,根據(jù)類(lèi)別分別在PBXResourcesBuildPhase等脂,PBXSourcesBuildPhase等中有記錄
--- From ehyubewb, 2018
2.3.4 Reference Hierarchy
XcodeProj本身所有的引用是基于每個(gè)對(duì)象的UUID的俏蛮, pbxplorer 這個(gè)庫(kù)實(shí)現(xiàn)了對(duì)xcodeproj的解析,他在實(shí)現(xiàn)過(guò)程中上遥,Reference Hierarchy如下:
PBXProject
build_configuration_list: XCConfigurationList
main_group: PBXGroup
targets: [PBXNativeTarget]
XCConfigurationList
build_configurations: [XCBuildConfiguration]
PBXGroup
children: [PBXGroup|PBXFileReference]
subgroups: [PBXGroup]
file_refs: [PBXFileReference]
variant_groups: [PBXVariantGroup]
PBXNativeTarget
build_configuration_list: XCBuildConfigurationList
build_phases: [PBXBuildPhase]
product_file_ref: PBXFileReference
PBXBuildPhase
build_files: [PBXBuildFile]
PBXBuildFile
file_ref: PBXFileReference
如果想自己開(kāi)發(fā)一套XcodeProj的框架或者處理腳本搏屑,可以參照這個(gè)Reference Hierarchy,對(duì)于類(lèi)之間的彼此關(guān)聯(lián)會(huì)更加清楚粉楚。
3. 總結(jié)
XcodeProj大體來(lái)說(shuō)就是配置了項(xiàng)目的文件路徑信息PBXBuildFile辣恋、項(xiàng)目中的Target及其依賴(lài)信息亮垫、編譯中的Config信息(PBXBuildPhase、XCBuildConfiguration等)伟骨。大致了解了他的結(jié)構(gòu)后饮潦,就會(huì)覺(jué)得雖然各方面井然有序,基于UUID實(shí)現(xiàn)關(guān)聯(lián)底靠,但整體還是顯得過(guò)于龐大害晦。尤其當(dāng)項(xiàng)目越來(lái)越大的時(shí)候,XcodeProj打開(kāi)就是一場(chǎng)噩夢(mèng)暑中。
對(duì)于我個(gè)人而言壹瘟,我更喜歡簡(jiǎn)單輕量級(jí)的IDE模式,類(lèi)似Sublime/VSCode鳄逾。假設(shè)作以下改變:
對(duì)于其中的文件信息稻轨,如果Xcode不考慮支持各種Group模式,完全物理實(shí)體目錄一一對(duì)應(yīng)的話(huà)雕凹,那就只剩下一些Target和依賴(lài)庫(kù)信息和相關(guān)的Config信息了殴俱。那這些信息本質(zhì)上就是一些Config信息。那這些Config再按照Build枚抵、Info线欲、Res等分類(lèi)為不同的Config,每個(gè)Config用json實(shí)現(xiàn)具體的內(nèi)容汽摹。
那么這樣一簇Config信息+源代碼文件組成的一個(gè)Project李丰,就可以不束縛于唯一的IDE了,可以在一些常用的IDE中快速實(shí)現(xiàn)開(kāi)發(fā)功能逼泣。
當(dāng)然要考慮Debug趴泌,得再加上Clang編譯器的能力,加上快捷提示的功能拉庶,加上...
那進(jìn)一步想一想嗜憔,如果我們自己寫(xiě)一個(gè)IDE, 我們需要做哪些準(zhǔn)備呢?
參考資料