Xcode 的 project 文件介绍
对应一个 iOS 项目而言,我们平时都是直接双击 xcodeproj
文件,打开项目进行代码编写,点击运行之后可以编译运行 APP,却很少思考这个文件的本质上是什么?有哪些部分组成,做用都是什么,这篇文章简单介绍下项目文件的组成。
下面的三个段落不是按顺序看的。。
一、项目内容由哪些部分组成
- Source Files
- 源文件:h/m 文件等
- 资源文件:图片、JSON、settings 等
- Infos:plist 配置文件,App 展示的配置信息或者 App 行为的一些配置
- App 信息
- 能力声明
- 默认配置
- Targets
- Schemes
- Build Phases:设置一个项目编译的顺序步骤
- Target Dependencies
- Compile Sources
- Link with Libraries
- Run Script
- Build Settings:项目的编译参数
- Compiler
- Linker
- Assets
- Packaging
Infos
Info.plist 用于向提供关于 App,或者framework的一些重要信息。它指定了比如一个应用的名称、版本号,启动方式,获取的权限,以及 Schema 的定义。
代码中可以使用以下代码拿到 info 信息:[[NSBundle mainBundle] infoDictionary]
比如在 URL types 里定义一个 scheme 为 demonow
,那么在其他 APP 或着 Safari 里使用 demonow://
便可以打开这个 APP。
Build Phases
定义了构建的过程和步骤,在 Xcode 中,这些步骤可以随意拖动调整顺序。
除了 Dependencies 不能挪动,不能删除,只能是第一个被执行的。
自定义编译前运行的脚本,比如使用 CocoaPods 进行依赖管理的项目里,会有一项 [CP] Check Pods Manifest.lock
:
1 | diff "${PODS_PODFILE_DIR_PATH}/Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null |
每次 build 前都会执行这个脚本去对 Podfile.lock
和 Pods/Manifest.lock
文件进行比对,如果依赖库的版本不一样,就会编译失败,提示执行 pod install
,来确保配置文件的版本号和本地库的版本号一致。
Build Settings
在 build settings 中,可以配置每个任务的详细内容。build 过程的每一个阶段,都有许多选项:从编译、链接一直到 code signing 和 packaging。
具体的含义和设置项可以到xcodebuildsettings查看。
Build rules
Build rules 指定了不同的文件类型该如何编译。一般来说,开发者并不需要修改这里面的内容。如果你需要对特定类型的文件添加处理方法,那么可以在此处添加一条新的规则。一条 build rule 指定了其应用于哪种类型文件,该类型文件是如何被处理的,以及输出的内容该如何处置。
二、一些实体的概念
Scheme、Target、Project 和 Workspace
下文中的 product 是一个编译产物:可能是一个 APP,也可能是一个静态库或者动态库。
Workspace
Workspace,在 Xcode 4之前,当时大多数工作流程仅限于单个 project,,一个 project 也可以引用一个或者多个其他的 project,但是这样其他的 project 不能相互引用和依赖;从 Xcode 4开始提供了 Workspace 可以管理多个 project,提供多个 project 和 target 之间的依赖关系。
Project
Xcode project 是构建一个或者多个 product 所需的源文件、资源和配置的集合,是管理资源的容器,本身是无法被编译的。包含用于 build product 的所有元素,并且维护这些资源之间的关系。project 可以独立存在,也可以被包含在一个 Workspace 中。
一个 project 包含一个或者多个 target,一个 target 编译时引用的资源是它所在 project 所有管理资源的子集。project 给所有的 target 定义了默认的 build settings,target 也可以自定义相关设置来覆盖默认的 build settings。
project 主要由以下几部分组成:
- 引用的源文件
- 代码的头文件和实现文件
- 资源文件(文本、xml 等)
- 图片资源
- 界面资源文件(xib、storyboard)
- 在文件结构的导航中,采用 group 来组织文件
- project 级别的编译配置(build configurations),比如 debug release 配置
- targets,给 target 提供默认的 build settings
- 运行或者调试的可执行环境:命令参数和环境变量,如 debug
target
一个 target 对应一个 product,target 继承 project 的 build settings,target 级别的 build settings 会覆盖 project 级别的 build settings。
同一时间只有个一个 active target,就是当前 Scheme 指定的,它定义了这个 product 怎样被 build 的所有细节。
target 常用来构建一个 APP 的不同版本,比如企业版、用户版等,Today 等扩展也是用 target 来管理的。
scheme
针对一个指定的 target,scheme 定义了 build 这个 target 时使用的 configuration,执行的任务,环境参数等等。Scheme 可以理解为一个工作流,Xcode 中预设了六个工作流:Build, Run, Test, Profile, Analyze, Archive。
一个 scheme 对应一个 target,同一个 target 可以有多个 scheme,通过灵活地配置 scheme,我们可以方便地管理不同环境下 App 的测试,调试,打包流程。
三、xcodeproj 的本质
工程文件的本质是什么?我们平时开发中很少会去看这个文件内部是如何组织的,基本上只有这个文件在合并代码时冲突了才会点进去。
1 | ├── demo |
xcodeproj
文件本质上是一个文件夹。它里面的 project.pbxproj
文件存储着 Xcode 工程的各项配置参数,本质上是一种旧风格的 Property List 文件,历史可追溯到 NeXT 的 OpenStep。其可读性不如 XML 和 JSON,为了方便阅读和理解,这个文件里有大量的注释。
语法:
1 | { // 字典 |
project.pbxproj
文件里数据层级的最外层是 rootObject
,然后其次 objects 里面是平铺存放的各种对象,相同的类型放到一起,被称之为一个 section,section 是比较虚的概念,本身没有类型,只是一段注释。
一些常见的类型,
1 | PBXBuildFile // 所有可编译的文件,比如源码、资源文件、nib 文件等 |
PBXProject 项目的一些定义,主要属性是targets,描述了这个项目下有哪些 target
PBXNativeTarget 项目中的 target,定义了有哪些 buildConfigurationList
和 buildPhases
PBXBuildFile
在 Build Phases 里出现的可编译资源都是这个类型,比如依赖的一个库,或者代码源文件,再或者一些图片资源,StoryBoard xib 等资源。
四、点击 run 按钮后都发生了什么
可以看到上面先 build 的是一些静态库,或者依赖的 target,最后再 build 当前 Scheme 对应的 target。把整个 build 日志导出来看,编译每个 target 开始前都会打印是编译哪个 project 的 target,并且使用的是哪个 configuration:
1 | Build target demo of project demo with configuration Inhouse |
编译过程:
- 编译依赖的的库和 target
- 写入辅助文件:hmap、script 等
- 执行预设脚本,如 CocoaPod 编译前脚本,checkPods Manifest.lock
- 编译文件:针对每一个文件进行编译,生成可执行文件 Mach-O
- 链接文件:将项目中的多个可执行文件合并成一个文件
- 资源的编译、优化、导入(Asset、nib 等)
- 配置文件生成(info.plist里有写参数是动态配置的)
- 签名、打包
TODO:待补全源码编译的过程:预编译-词法分析-语义分析-巴拉巴拉