了解 Xcode 项目文件 .xcodeproj
Posted SwiftCafe
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了了解 Xcode 项目文件 .xcodeproj相关的知识,希望对你有一定的参考价值。
作为一名开发者, 肯定对 Xcode 的项目文件 .xcodeproj
不陌生了. 我们用 Xcode 创建的任何一个项目都包含它. 那么大家是否对它有过进一步的了解呢. 我们来一探究竟.
.xcodeproj
首先 .xcodeproj
不是一个文件, 你可以理解为它是一个文件夹, 通过右键点击, 选择 "显示包内容" 来打开它:
之后, 会看到这几个文件:
其中 project.pbxproj
是我们要关注的主要内容. Xcode 项目的整体文件结构,以及编译,构建的配置信息,都保存在这个文件里. 打开这个文件你会看到类似这样的内容:
是不是顿时觉得不知所云呢, 几乎不在我们任何已认知的文件格式中. 这里只截取了一部分, 整个文件的结构都和上图类似.
没关系, 让我用尽量简单的介绍, 帮助大家理解它.
版本控制工具的阻碍
如果你的 Xcode 项目使用过多人协作的版本控制工具的话, 比如 git
. 那么你可能就会遇到过类似这样的问题, 团队的几个人同时开发一个项目, 其他人将自己的改动提交到版本库, 然后你拉取他们的提交, 更新到本地.
本来一切代码应该按照预期正常合并, 结果你发现拉取之后, 整个项目打不开了. 就像这样:
合并之前:
合并之后:
如果对这块细节了解不多的开发者, 很可能就会找另外一个提交代码的开发者当面沟通, 手动处理项目文件的改动. 或许这样可以解决问题, 但是效率并不高.
如果多想一步的人, 可能想到这可能是 XCode 项目文件合并冲突导致的. 实际上确实是这个原因. 但是当他们打开这个 project.pbxproj
文件后, 多数都会被它奇怪的格式阻拦.
下面就和大家聊聊 project.pbxproj
文件的数据格式, 了解它后, 不但能帮你解决上述这个问题, 还能让你更加深入了解 Xcode 项目的细节, 在一些其他场景也会有帮助.
project.pbxproj 文件格式
虽然一眼看去, project.pbxproj
文件很凌乱, 但其实它的基本元素并不复杂, 下面是它的基础结构:
{
archiveVersion = 1;
classes = {
};
objectVersion = 45;
objects = {
...
};
rootObject = 0867D690FE84028FC02AAC07 /* Project object */;
}
其中, archiveVersion
和 objectVersion
都是文件兼容版本号, 只需要按照 Xcode 生成的即可. 最重要的是 objects
属性. 这里面包含了我们项目中引用的所有文件, 配置等条目的信息.
rootObject
是根对象的, 它的值是一串这样的内容 0867D690FE84028FC02AAC07
. 这个值是 Xcode 随机生成的, 我们自己也可以用任何算法生成它, 只需要保证它在整个文件中是唯一的. 类似这样一长串的值, 在整个项目文件中随处可见. Xcode 构建工具分析项目文件, 也是从 rootObject
开始逐级往下找的.
任何的 project.pbxproj
文件, 都在这个基础结构上生成, 你可以打开你自己的项目文件, 就可以看到这几个属性了.
了解每个条目
知道这个基础结构后, 我给大家展示一个实际的例子. 比如我们有一个项目文件, 它的 rootObject
如下:
我们知道了根对象的 ID, 那么我们就搜索这个ID - 1093A36C21F871FD00E71BC6
. 你会找到这样一段内容:
1093A36C 21F 871FD00E 71BC6 /* Project object */ = {
isa = PBXProject ;
attributes = {
LastSwiftUpdateCheck = 1010 ;
LastUpgradeCheck = 1010 ;
ORGANIZATIONNAME = swiftcafe;
TargetAttributes = {
1093A 37321F 871FD00E 71BC6 = {
CreatedOnToolsVersion = 10. 1 ;
} ;
} ;
} ;
buildConfigurationList = 1093A 36F 21F 871FD00E 71BC6 /* Build configuration list for PBXProject "test" */ ;
compatibilityVersion = "Xcode 9.3" ;
developmentRegion = en ;
hasScannedForEncodings = 0 ;
knownRegions = (
en,
Base,
) ;
mainGroup = 1093A 36B 21F 871FD00E 71BC6 ;
productRefGroup = 1093A 37521F 871FD00E 71BC6 /* Products */ ;
projectDirPath = "" ;
projectRoot = "" ;
targets = (
1093A 37321F 871FD00E 71BC6 /* test */,
) ;
} ;
/* End PBXProject section */
这段内容中, /* */
包裹起来的内容是生成的注释, 帮助我们更容易理解这段内容. 比如 /* Begin PBXProject section */
和 /* End PBXProject section */
表示整个项目描述信息的开始和结束.
如果去掉注释后, 可以帮助你更清楚的理解这个文件的格式:
isa = PBXProject ;
attributes = {
LastSwiftUpdateCheck = 1010 ;
LastUpgradeCheck = 1010 ;
ORGANIZATIONNAME = swiftcafe;
TargetAttributes = {
1093A 37321F 871FD00E 71BC6 = {
CreatedOnToolsVersion = 10. 1 ;
} ;
} ;
} ;
buildConfigurationList = 1093A 36F 21F 871FD00E 71BC6 ;
compatibilityVersion = "Xcode 9.3" ;
developmentRegion = en ;
hasScannedForEncodings = 0 ;
knownRegions = (
en,
Base,
) ;
mainGroup = 1093A 36B 21F 871FD00E 71BC6 ;
productRefGroup = 1093A 37521F 871FD00E 71BC6 ;
projectDirPath = "" ;
projectRoot = "" ;
targets = (
1093A 37321F 871FD00E 71BC6,
) ;
} ;
首先, 开头的 1093A36C21F871FD00E71BC6
是我们刚才在 rootObject
中看到的对应 ID, 是我们项目根对象. 再往下看 isa
是这个对象的类型 - PBXProject
. 表示这个节点描述的是项目的基础信息.
比如我们可以看到 ORGANIZATIONNAME
是我们创先项目时候填入的组织名, CreatedOnToolsVersion
是创建这个项目的 Xcode 版本, 等等.
有两个属性需要说明一下, 就是 buildConfigurationList
和 mainGroup
. 大家可以看到, 这两个属性的值,本身又是一个 ID. 这两个属性分别表示项目的配置列表, 以及主文件 Group. 它们本身都会包含很多子属性. 所以我们这里引用的是它们的 ID.
理解文件结构
你可以理解为 Xcode 项目文件, 所有的内容都是一个节点, 从根节点,一直向下引用的每一个子节点. 每个节点都包含一个 ID. 我们常见的 JSON 或 XML 结构也是树形结构, 但他们是比较直观的, 我们视觉上直接就能看到他们的父子节点关系. 而 .pbxproj
所有的节点都平铺在文件中, 视觉上不能一眼就看出整个的树形结构, 而他们之间是通过 ID 互相引用的. 理解这点至关重要.
举个例子, 如果我们的项目文件用 JSON 格式表示, 大概是这样:
rootObject: {
isa: "PBXProject",
mainGroup: {
isa: "PBXGroup",
children: [
{
isa : "PBXGroup",
path: "test"
children: [
...
]
}
]
}
}
}
同样的结构, 换成 .pbxproj
就是这样:
...
objects = {
1093A36C 21F 871FD00E 71BC6 /* Project object */ = {
isa = PBXProject ;
...
mainGroup = 1093A 36B 21F 871FD00E 71BC6 ;
...
} ;
1093A 36B 21F 871FD00E 71BC6 = {
isa = PBXGroup ;
children = (
1093A 37621F 871FD00E 71BC6 /* test */,
) ;
sourceTree = "<group>" ;
} ;
1093A 37621F 871FD00E 71BC6 /* test */ = {
isa = PBXGroup ;
children = (
...
) ;
path = test ;
sourceTree = "<group>" ;
} ;
}
rootObject = 1093A36C 21F 871FD00E 71BC6 /* Project object */ ;
}
可以看到, .pbxproj
中所有节点, 都平铺在 objects
属性中. 通过每个节点的 ID, 互相引用, 建立起的树形结构.
节点类型
我们前面已经了解了一种节点的类型, 就是 PBXProject
项目基础信息. .pbxproj
还包含了很多类型的节点. 下面, 继续我们前面的探索.
我们前面看到 mainGroup = 1093A36B21F871FD00E71BC6
引用了另外一个节点, 通过搜索我们找到了这个节点的定义:
isa = PBXGroup ;
children = (
1093A 37621F 871FD00E 71BC6 /* test */,
1093A 37521F 871FD00E 71BC6 /* Products */,
) ;
sourceTree = "<group>" ;
} ;
这个节点的类型是 PBXGroup
, 在它的 children
属性中, 又引用了另外两个节点. 并且 Xcode 在后面生成了注释 /* test */
和 /* Products */
, 其实就是这个 mainGroup
下级的两个子 group
. 对应我们 xcode 左边文件结构:
现在应该知道 Xcode 左边栏的文件结构是如何在项目文件中表示的了. 这里要提醒大家一点, /* test */
和 /* Products */
这样的内容只是注释. 并不是配置文件的实际内容.
比如 1093A37621F871FD00E71BC6 /* test */
, 在配置文件中, 实际有效的是前半部分的 ID, 我们搜索这个ID, 会发现这个定义:
isa = PBXGroup;
children = (
1093A37721F871FD00E71BC6 /* AppDelegate.swift */,
1093A37921F871FD00E71BC6 /* ViewController.swift */,
1093A37B21F871FD00E71BC6 /* Main.storyboard */,
1093A37E21F871FE00E71BC6 /* Assets.xcassets */,
1093A38021F871FE00E71BC6 /* LaunchScreen.storyboard */,
1093A38321F871FE00E71BC6 /* Info.plist */,
);
path = test;
sourceTree = "<group>";
};
这个节点实际的文件路径, 其实是定义在它的 path
属性中. 每个 ID 后面的注释, 其实只是帮助我们更好的阅读配置文件的. 可以看到, 这个节点本身又引用了一些其他节点.
1093A37921F871FD00E71BC6 /* ViewController.swift */,
1093A37B21F871FD00E71BC6 /* Main.storyboard */,
1093A37E21F871FE00E71BC6 /* Assets.xcassets */,
1093A38021F871FE00E71BC6 /* LaunchScreen.storyboard */,
1093A38321F871FE00E71BC6 /* Info.plist */
他们又都对应了相应的文件, 比如 1093A37721F871FD00E71BC6 /* AppDelegate.swift */
, 你搜索它的 ID , 就会找到关于这个文件的明确定义:
这种文件节点, 是树形结构的最末端阶段, 它在配置文件中直接写成了一行, 我们把它的格式整理一下, 这样就好理解了:
isa = PBXFileReference ;
lastKnownFileType = sourcecode. swift;
path = AppDelegate. swift;
sourceTree = "<group>" ;
} ;
这个节点的类型是 PBXFileReference
, 顾名思义是文件引用. 同样, 它还包含了一些其他属性, 比如文件的路径, 类型等.
结尾
通过上面咱们一起的梳理, 你应该不难发现了, .pbxproj
文件结构其实和我们常见的树形结构没什么两样, 只是他不像类似 JSON 这样的数据结构看起来那么直观. 每个节点的定义在文件中看起是来同级的, 但他们又通过 ID 的互相引用实现了逻辑上的树形结构.
只要你了解了这个概念, 你下次遇到比如合并冲突,或者其他需要操作 .pbxproj
文件的问题, 运用这个原理基本都可以解决.
另外在补充一下, 我们前面提到的节点 ID, 比如这个 1093A37721F871FD00E71BC6 /* AppDelegate.swift */
, 一长串16进制数. .pbxproj
并没有规定节点ID固定的生成算法. 我们看到的这串内容,实际上是 Xcode 用自身的算法生成的. 但其实一个 .pbxproj
文件, 它里面的节点ID 可以用任何算法生成, 只要保证他在当前文件中全局唯一不重复即可.
我之前的文章曾经给大家介绍过一个工具 就是关于 ID 生成这个事情的. 你也不妨了解一下.
以上是关于了解 Xcode 项目文件 .xcodeproj的主要内容,如果未能解决你的问题,请参考以下文章
Xcode 项目错误:xcodeproj 无法打开,因为它缺少其 project.pbxproj 文件
无法在 XCode 4.3.2 中打开较旧的 .xcodeproj 文件
无法将 ReactNativeNavigation.xcodeproj 添加到 Xcode 中的库