Xcode工程文件project.pbxproj小结
简介
project.pbxproj 文件被包含于 Xcode 工程文件 *.xcodeproj 之中,存储着 Xcode 工程的各项配置参数。它本质上是一种旧风格的 Property List 文件,历史可追溯到 NeXT 的 OpenStep。由于有Xcode工具的存在,我们一般不需要与pbxproj直接打交道,通过General、Build Settungs或者Info等面板,就可以完成项目工程配置信息的修改。
一般格式如下:
数组用小括号括起来并用逗号隔开元素;字典用大括号括起来并用分号隔开键值对,键值之间用等号连接;二进制数据用尖括号括起来
接下来我们就依据一个具体的project.pbxproj文件来看看文件的具体内容;
首先使用Xcode新建一个项目文件,然后找到工程文件*.xcodeproj 邮件显示包内容就可以看到project.pbxproj文件,使用熟悉的编辑器打开,可以看到一些貌似混乱却又组织严密的内容。
文件的组织规则
对于NeXTSTEP格式的Property List 文件来说,整个的project.pbxproj文件就是一个字典,里面最外层有5个键值对,key分别为:
archiveVersion
classes
objectVersion
objects
rootObject
其中重要的 Key 是 objects 和 rootObject。其他字段目前看来是固定的:
archiveVersion = 1;
classes = {
};
objectVersion = 46;
上面说到这个文件看似混乱,实质上却是组织清晰,最好的方法就是顺着 rootObject 的各个属性对应的 UUID 在 objects 中找到对应的对象,然后一层层看下去。这样整个文件的配置信息存放方式就慢慢摸清了(rootObject就好像一个入口函数),具体的配置实现是在 objects这个域里面的,里面每一项的又是一个字典,key是UUID,Value 依然是个字典。下面可以看到rootObject对应的value也可以在objects中找到具体的值。objects 中的键值对被分成了若干个 section,虽然 section 的顺序是 Xcode 私有 API 钦定的,但每个 section 内部的键值对会根据 Key 的字典序排列,以此增加可读性。
此外,这里还要补充一下:
project.pbxproj文件使用UUID 作为交叉引用的索引,保证每个配置信息对象的唯一性。因为 UUID 根据机器硬件和时间戳生成,避免了多人在同一时间段操作修改工程文件带来的问题。也就是说工程中每项配置对象都有个唯一的 UUID,然后其他配置对象想引用某个配置对象直接使用它的 UUID 即可。这就跟我们编程时使用指针指向某个对象的地址一样,其他对象的属性想引用它,只需要给属性传个指针地址就行了。
下面我就从rootObject开始简单的走一遍,大概了解一下此文件的配置方式。
rootObject
rootObject = 662A377C1DB7D8B00038B7DB /* Project object */;
从注释上就可以看出指向一个UUID为662A377C1DB7D8B00038B7DB的Project object字典
Project object
/* Begin PBXProject section */
662A377C1DB7D8B00038B7DB /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0800;
ORGANIZATIONNAME = Apress;
TargetAttributes = {
662A37831DB7D8B00038B7DB = {
CreatedOnToolsVersion = 8.0;
ProvisioningStyle = Automatic;
};
662A379C1DB7D8B00038B7DB = {
CreatedOnToolsVersion = 8.0;
ProvisioningStyle = Automatic;
TestTargetID = 662A37831DB7D8B00038B7DB;
};
662A37A71DB7D8B00038B7DB = {
CreatedOnToolsVersion = 8.0;
ProvisioningStyle = Automatic;
TestTargetID = 662A37831DB7D8B00038B7DB;
};
};
};
buildConfigurationList = 662A377F1DB7D8B00038B7DB /* Build configuration list for PBXProject "HelloWorld" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 662A377B1DB7D8B00038B7DB;
productRefGroup = 662A37851DB7D8B00038B7DB /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
662A37831DB7D8B00038B7DB /* HelloWorld */,
662A379C1DB7D8B00038B7DB /* HelloWorldTests */,
662A37A71DB7D8B00038B7DB /* HelloWorldUITests */,
);
};
/* End PBXProject section */
PBXProject顾名思义就是工程,对应构建可执行的二进制目标程序或库,里面包含了编译工程所需的全部信息。
1、isa 可以把每个value看做一个对象,可以发现每一个value都有一个isa字段,代表value的类型。
2、attributes 属性,包含一些编译器的基本信息,版本,以及项目中的target,每一个target一个UUID其中,Xcode自动创建的项目里面有三个target一个就是所要编译的APP主target,其余两个为test Target,可以看到其余两个target中有一个字段TestTargetID指向主target,可以理解为依赖相关吧。
3、buildConfigurationList 配置列表 指向一个配置字典 XCConfigurationList 类型类型(稍后讲)
4、compatibilityVersion 应该是兼容版本 目前看来是 Xcode 3.2
5、developmentRegion 语言版本,English英语
6、hasScannedForEncodings 是否已经扫描了文件编码信息
7、knownRegions 不同区域的本地资源文件列表
8、mainGroup Xcode的文件组织形式,可以理解为文件层次 PBXGroup 类型
9、productRefGroup 编译后的输出文件 PBXGroup 类型
10、projectDirPath,projectRoot 项目路径和项目的根目录 目前为空
11、targets 项目下的三个target对象 PBXNativeTarget类型
从上到下,我们来看一下所用的类型。
XCConfigurationList
/* Begin XCConfigurationList section */
662A377F1DB7D8B00038B7DB /* Build configuration list for PBXProject "HelloWorld" */ = {
isa = XCConfigurationList;
buildConfigurations = (
662A37AF1DB7D8B00038B7DB /* Debug */,
662A37B01DB7D8B00038B7DB /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
662A37B11DB7D8B00038B7DB /* Build configuration list for PBXNativeTarget "HelloWorld" */ = {
isa = XCConfigurationList;
buildConfigurations = (
662A37B21DB7D8B00038B7DB /* Debug */,
662A37B31DB7D8B00038B7DB /* Release */,
);
defaultConfigurationIsVisible = 0;
};
662A37B41DB7D8B00038B7DB /* Build configuration list for PBXNativeTarget "HelloWorldTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
662A37B51DB7D8B00038B7DB /* Debug */,
662A37B61DB7D8B00038B7DB /* Release */,
);
defaultConfigurationIsVisible = 0;
};
662A37B71DB7D8B00038B7DB /* Build configuration list for PBXNativeTarget "HelloWorldUITests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
662A37B81DB7D8B00038B7DB /* Debug */,
662A37B91DB7D8B00038B7DB /* Release */,
);
defaultConfigurationIsVisible = 0;
};
/* End XCConfigurationList section */
XCConfigurationList是一个构建配置相关元素的列表,里面有一个项目文件(HelloWorld),三个target(HelloWorld、HelloWorldTests和HelloWorldUITests)对应于你在Xcode界面中看到的这样,如图,每一个都有相对应的配置属性buildConfigurations,而每个配置属性都有两个的版本(Debug和Release)
那Debug和Release又对应什么呢?是XCBuildConfiguration:
/* Begin XCBuildConfiguration section */
662A37AF1DB7D8B00038B7DB /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVES = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
662A37B01DB7D8B00038B7DB /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
·
·
·
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
·
·
·
/* End XCBuildConfiguration section */
XCBuildConfiguration构建具体的配置元素,也就是Xcode的Build Setting面板中所涉及的选项。至于每个字段所代表的含义可再深入研究。
PBXGroup
/* Begin PBXGroup section */
662A377B1DB7D8B00038B7DB = {
isa = PBXGroup;
children = (
662A37861DB7D8B00038B7DB /* HelloWorld */,
662A37A01DB7D8B00038B7DB /* HelloWorldTests */,
662A37AB1DB7D8B00038B7DB /* HelloWorldUITests */,
662A37851DB7D8B00038B7DB /* Products */,
);
sourceTree = "";
};
662A37851DB7D8B00038B7DB /* Products */ = {
isa = PBXGroup;
children = (
662A37841DB7D8B00038B7DB /* HelloWorld.app */,
662A379D1DB7D8B00038B7DB /* HelloWorldTests.xctest */,
662A37A81DB7D8B00038B7DB /* HelloWorldUITests.xctest */,
);
name = Products;
sourceTree = "";
};
662A37861DB7D8B00038B7DB /* HelloWorld */ = {
isa = PBXGroup;
children = (
662A378A1DB7D8B00038B7DB /* AppDelegate.h */,
662A378B1DB7D8B00038B7DB /* AppDelegate.m */,
662A378D1DB7D8B00038B7DB /* ViewController.h */,
662A378E1DB7D8B00038B7DB /* ViewController.m */,
662A37901DB7D8B00038B7DB /* Main.storyboard */,
662A37931DB7D8B00038B7DB /* Assets.xcassets */,
662A37951DB7D8B00038B7DB /* LaunchScreen.storyboard */,
662A37981DB7D8B00038B7DB /* Info.plist */,
662A37871DB7D8B00038B7DB /* Supporting Files */,
);
path = HelloWorld;
sourceTree = "";
};
662A37871DB7D8B00038B7DB /* Supporting Files */ = {
isa = PBXGroup;
children = (
662A37881DB7D8B00038B7DB /* main.m */,
);
name = "Supporting Files";
sourceTree = "";
};
662A37A01DB7D8B00038B7DB /* HelloWorldTests */ = {
isa = PBXGroup;
children = (
662A37A11DB7D8B00038B7DB /* HelloWorldTests.m */,
662A37A31DB7D8B00038B7DB /* Info.plist */,
);
path = HelloWorldTests;
sourceTree = "";
};
662A37AB1DB7D8B00038B7DB /* HelloWorldUITests */ = {
isa = PBXGroup;
children = (
662A37AC1DB7D8B00038B7DB /* HelloWorldUITests.m */,
662A37AE1DB7D8B00038B7DB /* Info.plist */,
);
path = HelloWorldUITests;
sourceTree = "";
};
/* End PBXGroup section */
PBXGroup 文件组织形式文件的组织结构,可以嵌套,可以看到子文件列表放在key为children的字典中value是一个数组,如果数组中的值指向的是一个文件夹类型的,则再生成一个字典,一直到底指向最初的文件,PBXFileReference类型
PBXFileReference
/* Begin PBXFileReference section */
662A37841DB7D8B00038B7DB /* HelloWorld.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HelloWorld.app; sourceTree = BUILT_PRODUCTS_DIR; };
662A37881DB7D8B00038B7DB /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
662A378A1DB7D8B00038B7DB /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
662A378B1DB7D8B00038B7DB /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
·
·
·
662A37A81DB7D8B00038B7DB /* HelloWorldUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HelloWorldUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
662A37AC1DB7D8B00038B7DB /* HelloWorldUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HelloWorldUITests.m; sourceTree = ""; };
662A37AE1DB7D8B00038B7DB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
/* End PBXFileReference section */
PBXFileReference 项目所引用的每一个外部文件,比如源代码文件、资源文件、库文件、生成目标文件等,每一个外部文件都可以在这里找到,也就是在Xcode的文件导航栏所看到的文件组织形式,如图3。
productRefGroup
类似于mainGroup
PBXNativeTarget
/* Begin PBXNativeTarget section */
662A37831DB7D8B00038B7DB /* HelloWorld */ = {
isa = PBXNativeTarget;
buildConfigurationList = 662A37B11DB7D8B00038B7DB /* Build configuration list for PBXNativeTarget "HelloWorld" */;
buildPhases = (
662A37801DB7D8B00038B7DB /* Sources */,
662A37811DB7D8B00038B7DB /* Frameworks */,
662A37821DB7D8B00038B7DB /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = HelloWorld;
productName = HelloWorld;
productReference = 662A37841DB7D8B00038B7DB /* HelloWorld.app */;
productType = "com.apple.product-type.application";
};
662A379C1DB7D8B00038B7DB /* HelloWorldTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 662A37B41DB7D8B00038B7DB /* Build configuration list for PBXNativeTarget "HelloWorldTests" */;
buildPhases = (
662A37991DB7D8B00038B7DB /* Sources */,
662A379A1DB7D8B00038B7DB /* Frameworks */,
662A379B1DB7D8B00038B7DB /* Resources */,
);
buildRules = (
);
dependencies = (
662A379F1DB7D8B00038B7DB /* PBXTargetDependency */,
);
name = HelloWorldTests;
productName = HelloWorldTests;
productReference = 662A379D1DB7D8B00038B7DB /* HelloWorldTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
662A37A71DB7D8B00038B7DB /* HelloWorldUITests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 662A37B71DB7D8B00038B7DB /* Build configuration list for PBXNativeTarget "HelloWorldUITests" */;
buildPhases = (
662A37A41DB7D8B00038B7DB /* Sources */,
662A37A51DB7D8B00038B7DB /* Frameworks */,
662A37A61DB7D8B00038B7DB /* Resources */,
);
buildRules = (
);
dependencies = (
662A37AA1DB7D8B00038B7DB /* PBXTargetDependency */,
);
name = HelloWorldUITests;
productName = HelloWorldUITests;
productReference = 662A37A81DB7D8B00038B7DB /* HelloWorldUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
/* End PBXNativeTarget section */
PBXNativeTarget,对应于生成的可执行二进制程序或库文件的本地构建目标对象,也就是Xcode我们看到的这三个target目标对象。里面的主要内容是:
1、buildConfigurationList 这个我们在讲述PBXProject时已经介绍过,是一个配置相关元素相关的列表。
2、buildPhases 构建阶段所涉及的源文件(PBXSourcesBuildPhase类型)、框架(PBXFrameworksBuildPhase类型)和资源(PBXResourcesBuildPhase)以数组形式组织。
3、buildRules 指定了不同文件类型该如何编译
4、dependencies 列出了在Xcode build phase tab中列出的target依赖项
5、productReference 目标文件(PBXFileReference)
PBXSourcesBuildPhase
/* Begin PBXSourcesBuildPhase section */
662A37801DB7D8B00038B7DB /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
662A378F1DB7D8B00038B7DB /* ViewController.m in Sources */,
662A378C1DB7D8B00038B7DB /* AppDelegate.m in Sources */,
662A37891DB7D8B00038B7DB /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
662A37991DB7D8B00038B7DB /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
662A37A21DB7D8B00038B7DB /* HelloWorldTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
662A37A41DB7D8B00038B7DB /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
662A37AD1DB7D8B00038B7DB /* HelloWorldUITests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
PBXSourcesBuildPhase 代表构建阶段需要复制的资源文件 文件组织在key为files的value中的数组列表里,每个元素为PBXBuildFile类型。
PBXBuildFile
/* Begin PBXBuildFile section */
662A37891DB7D8B00038B7DB /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 662A37881DB7D8B00038B7DB /* main.m */; };
662A378C1DB7D8B00038B7DB /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 662A378B1DB7D8B00038B7DB /* AppDelegate.m */; };
662A378F1DB7D8B00038B7DB /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 662A378E1DB7D8B00038B7DB /* ViewController.m */; };
662A37921DB7D8B00038B7DB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 662A37901DB7D8B00038B7DB /* Main.storyboard */; };
662A37941DB7D8B00038B7DB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 662A37931DB7D8B00038B7DB /* Assets.xcassets */; };
662A37971DB7D8B00038B7DB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 662A37951DB7D8B00038B7DB /* LaunchScreen.storyboard */; };
662A37A21DB7D8B00038B7DB /* HelloWorldTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 662A37A11DB7D8B00038B7DB /* HelloWorldTests.m */; };
662A37AD1DB7D8B00038B7DB /* HelloWorldUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 662A37AC1DB7D8B00038B7DB /* HelloWorldUITests.m */; };
/* End PBXBuildFile section */
PBXBuildFile 代表文件元素,被PBXBuildPhase等作为文件包含或被引用的资源,其实里面fileRef指向最终的文件PBXFileReference。
PBXFrameworksBuildPhase
/* Begin PBXFrameworksBuildPhase section */
662A37811DB7D8B00038B7DB /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
662A379A1DB7D8B00038B7DB /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
662A37A51DB7D8B00038B7DB /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
PBXFrameworksBuildPhase用于framewrok构建的链接阶段
PBXResourcesBuildPhase
/* Begin PBXResourcesBuildPhase section */
662A37821DB7D8B00038B7DB /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
662A37971DB7D8B00038B7DB /* LaunchScreen.storyboard in Resources */,
662A37941DB7D8B00038B7DB /* Assets.xcassets in Resources */,
662A37921DB7D8B00038B7DB /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
662A379B1DB7D8B00038B7DB /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
662A37A61DB7D8B00038B7DB /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
PBXResourcesBuildPhase 构建阶段需要复制的资源文件,包括xib文件、故事板(storyboard)、图片和plist文件等其他资源文件。链接至PBXBuildFile文件
到这里,整个project.pbxproj 文件基本走了一遍,我们也对整个文件的组织结构有了一个初步的了解,里面有些字段的含义也许不是很清楚,但是并不影响我们对文件结构的理解。如果想看更深的一步了解,可以参看这里。
Cocoapods正是通过它的组件Xcodeproj操作project.pbxproj文件来对工程结构进行修改。
最后附上一张整体的流程图:
参考文献:
1、 聊聊 Xcode 项目文件中的 project.pbxproj
2、 通过Xcodeproj深入探究Xcode工程文件 一
3、 Xcode工程文件的格式说明
4、 project.pbxproj,最熟悉的“陌生人”