1. 背景
平时开发中,经常会遇到xcodeproj冲突,就需要打开这个文件,进行处理。当然现在也有很多工具或者自动化的脚本来自动merge,比如 simonwagner/mergepbx, 但这个文件错综复杂,尤其当项目大到一定阶段后,非常可怕,所以很多情况还是需要人工来处理各种冲突。
所以决定研究下,这个庞然大物内部的结构究竟是怎样的...
2. XcodeProj的工程结构
2.1 project.pbxproj文件
Xcode每个项目的工程文件都在xxx.xcodeproj中,查看包内容,可以看到真正的内容都在project.pbxproj里面,其他有一些xcuserdata之类的,不重要,先忽略。我们主要来看pbxproj文件。
这是一个plist文件。记录了所有代码的和库文件的索引和路径信息,以及Target信息,包括Build Setting/Build Phase等等信息。
虽然xcodeproj提供了很多方便的文件管理和索引,但我自身还是更喜欢无project文件的代码,直接与物理目录对应,不要与IDE产生太多的依赖和耦合。
2.2 pbxproj预览
一个完整的pbxproj文件基本是如下这样:
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
…
};
rootObject = 4B74E19C1AB185A200A5A377 /* Project object */;
}
核心内容都在objects中,完整版如下:
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* 构建所需的代码文件,资源文件,库文件等 */
/* 平时git发生冲突也主要是在这个区域内冲突 */
/* 你每新建一个.h/.m文件,就会修改这个区域, 各个branch都在创建的时候,容易冲突 */
/* Begin PBXBuildFile section */
...
/* End PBXBuildFile section */
/* 这里记录了每个target的targetProxy,与PBXTargetDependency相对应 */
/* Begin PBXContainerItemProxy section */
...
/* End PBXContainerItemProxy section */
/* 主要记录每个target的BuildPhase中的Embed App Extensions的部分 */
/* Begin PBXCopyFilesBuildPhase section */
/* End PBXCopyFilesBuildPhase section */
/* 记录了每个代码文件的文件类型、路径path、sourceTree,不论引入文件的时候是create group还是create reference,都会在这里添加一条记录 */
/* Begin PBXFileReference section */
/* End PBXFileReference section */
/* 工程中所依赖的Frameworks的信息,对应Build Phases中的`Link Binary With Libraries` */
/* Begin PBXFrameworksBuildPhase section */
/* End PBXFrameworksBuildPhase section */
/* 工程中所有文件的group信息,这个和xcode文件目录是对应的,每一层的文件目录有唯一的UUID,同一层group下的子group会和上一层的group的UUID有很高的重合度(基本只有1-2位不同),这个PBXGroup section中,子group没有用树的方式,而是采用类似列表的方式呈现了所有的group目录,可以脑补:打开xcode左侧目录,然后让所有目录和文件"左对齐",然后就会生成如下的结构` */
/* Begin PBXGroup section */
/* End PBXGroup section */
/* 每个Target的BuildSettings和BuildPhases(Sources/Frameworks/Resources等)的信息 */
/* Begin PBXNativeTarget section */
/* End PBXNativeTarget section */
/* 整个项目工程Project的信息,包括项目路径、Config信息,相关版本号,所有的Target等信息 */
/* Begin PBXProject section */
4B74E19C1AB185A200A5A377 /* Project object */ = {
isa = PBXProject;
attributes = { ... };
...
targets = (
4B74E1A31AB185A200A5A377 /* xxxxPolenTestxxxx */,
...
);
};
/* End PBXProject section */
/* 列举了项目中每个Resources的信息, 包括Build Phase下`Copy Bundle Resources`文件、Assets.xcassets等资源文件 */
/* Begin PBXResourcesBuildPhase section */
/* End PBXResourcesBuildPhase section */
/* 对应Xcode中Build Phases下的脚本文件,包括:Embed Pods Frameworks,Check Pods Manifest.lock以及其他本地或者第三方的脚本文件信息 */
/* Begin PBXShellScriptBuildPhase section */
/* End PBXShellScriptBuildPhase section */
/* 对应Xcode中Build Phases的Complie Sources的代码文件 */
/* Begin PBXSourcesBuildPhase section */
/* End PBXSourcesBuildPhase section */
/* 记录了每个Target的targetProxy,每个targetProxy都是一个PBXContainerItemProxy类型,暂时没找到Xcode中的对应项 */
/* Begin PBXTargetDependency section */
/* End PBXTargetDependency section */
/* 不同地区的资源文件的引用信息,如果你项目使用了国际化,相关的xxx.string就在这个section中 */
/* Begin PBXVariantGroup section */
/* End PBXVariantGroup section */
/* 对应Xcode中 Build Settings中的配置信息 */
/* Begin XCBuildConfiguration section */
/* End XCBuildConfiguration section */
/* XCBuildConfiguration只是列举了所有Target的所有Setting项,下面这个文件区分,不同Target在Debug时使用哪个Setting项,在Release时使用哪个Setting项 */
/* Begin XCConfigurationList section */
/* End XCConfigurationList section */
};
rootObject = 4B74E19C1AB185A200A5A377 /* Project object */;
}
2.3 pbxproj结构
2.3.1 结构图
首先可以去看一下Xcode Project File Format , 很详细的介绍了pbxproj中每个类和及其属性字段的含义和引用关系。
从Plist的角度看,我们可以将PBXProject看成一个个节点和子节点的树形结构,但从面向对象的角度,其实就是一个个类和子类。
自己简单整理了一下pbxproj的结构图 (原创):
2.3.2 Class Hierarchy
下面具体说一下每个节点/类 模块包含的内容以及在Xcode中对应哪些文件或者目录:
- PBXBuildFile: 构建所需的代码文件,资源文件,库文件等
- PBXBuildPhase: 对应Xcode中Build Phases
- PBXAppleScriptBuildPhase
- PBXCopyFilesBuildPhase: 主要记录每个target的BuildPhase中的
Embed App Extensions
的部分 - PBXFrameworksBuildPhase: 工程中所依赖的Frameworks的信息,对应Build Phases中的
Link Binary With Libraries
- PBXHeadersBuildPhase
- PBXResourcesBuildPhase: 列举了项目中每个Resources的信息, 包括Build Phase下
Copy Bundle Resources
文件、Assets.xcassets
等资源文件 - PBXShellScriptBuildPhase : 对应Xcode中Build Phases下的脚本文件,包括:Embed Pods Frameworks,Check Pods Manifest.lock以及其他本地或者第三方的脚本文件信息
- PBXSourcesBuildPhase: 对应Xcode中Build Phases的
Complie Sources
的代码文件
- PBXContainerItemProxy: 这里记录了每个target的targetProxy,与
PBXTargetDependency
相对应 - PBXFileElement
- PBXFileReference: 记录了每个代码文件的文件类型、路径path、sourceTree, 不论引入文件的时候是create group还是create reference,都会在这里添加一条记录
- PBXGroup:工程中所有文件的group信息,这个和xcode文件目录是对应的,每一层的文件目录有唯一的UUID,同一层group下的子group会和上一层的group的UUID有很高的重合度(基本只有1-2位不同),这个
PBXGroup section
中,子group没有用树的方式,而是采用类似列表的方式呈现了所有的group目录,可以脑补:打开xcode左侧目录,然后让所有目录和文件"左对齐",然后就会生成如下的结构` - PBXVariantGroup: 不同地区的资源文件的引用信息,如果你项目使用了国际化,相关的xxx.string就在这个section中
- PBXTarget: 每个Target的BuildSettings和BuildPhases(Sources/Frameworks/Resources等)的信息
- PBXAggregateTarget: TODO: 暂未找到相关介绍,自己的项目里也没出现这类Target
- PBXLegacyTarget:TODO: 暂未找到相关介绍,自己的项目里也没出现这类Target
- PBXNativeTarget: 正常建立的Target都是这种类型的
- PBXProject:整个项目工程Project的信息,包括项目路径、Config信息,相关版本号,所有的Target等信息
- PBXTargetDependency: 记录了每个Target的targetProxy,每个targetProxy都是一个
PBXContainerItemProxy
类型,暂时没找到Xcode中的对应项 - XCBuildConfiguration: 对应Xcode中 Build Settings中的配置信息
- XCConfigurationList: XCBuildConfiguration只是列举了所有Target的所有Setting项,下面这个文件区分,不同Target在Debug时使用哪个Setting项,在Release时使用哪个Setting项
分享一点其他人的总结:
PBXProject 为根节点,代表着整个工程
PBXProject 可以有多个PBXNativeTarget,代表着工程中的target
PBXNativeTarget 维护着各自资源文件(PBXResourcesBuildPhase),源文件(PBXSourcesBuildPhase),以及依赖库(PBXFrameworksBuildPhase)等等
PBXProject 和 PBXNativeTarget 都有配置管理,通过XCConfigurationList和XCBuildConfiguration维护
每个导入工程的文件都会有相应的PBXFileReference记录,如果该文件在导入时,选择了create groups ,会在相应的PBXGroup中有记录
每个在编译打包过程中被包含到可执行文件中的文件,都会有PBXBuildFile记录,根据类别分别在PBXResourcesBuildPhase,PBXSourcesBuildPhase等中有记录
--- From ehyubewb, 2018
2.3.4 Reference Hierarchy
XcodeProj本身所有的引用是基于每个对象的UUID的, pbxplorer 这个库实现了对xcodeproj的解析,他在实现过程中,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
如果想自己开发一套XcodeProj的框架或者处理脚本,可以参照这个Reference Hierarchy,对于类之间的彼此关联会更加清楚。
3. 总结
XcodeProj大体来说就是配置了项目的文件路径信息PBXBuildFile、项目中的Target及其依赖信息、编译中的Config信息(PBXBuildPhase、XCBuildConfiguration等)。大致了解了他的结构后,就会觉得虽然各方面井然有序,基于UUID实现关联,但整体还是显得过于庞大。尤其当项目越来越大的时候,XcodeProj打开就是一场噩梦。
对于我个人而言,我更喜欢简单轻量级的IDE模式,类似Sublime/VSCode。假设作以下改变:
对于其中的文件信息,如果Xcode不考虑支持各种Group模式,完全物理实体目录一一对应的话,那就只剩下一些Target和依赖库信息和相关的Config信息了。那这些信息本质上就是一些Config信息。那这些Config再按照Build、Info、Res等分类为不同的Config,每个Config用json实现具体的内容。
那么这样一簇Config信息+源代码文件组成的一个Project,就可以不束缚于唯一的IDE了,可以在一些常用的IDE中快速实现开发功能。
当然要考虑Debug,得再加上Clang编译器的能力,加上快捷提示的功能,加上...
那进一步想一想,如果我们自己写一个IDE, 我们需要做哪些准备呢?
参考资料
- GitHub: CocoaPods 官方源码
- GitHub: mjmsmith/pbxplorer
- Xcode Project File Format: 对.pbxproj文件每个参数的详细介绍
- XCode工程文件结构及Xcodeproj框架的使用( 二 )