美文网首页iOS备忘录
[iOS]XcodeProject的内部结构分析

[iOS]XcodeProject的内部结构分析

作者: pingpong_龘 | 来源:发表于2018-08-11 14:00 被阅读170次

    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的结构图 (原创):

    xcodeproject_2018-08-06_23.png

    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, 我们需要做哪些准备呢?


    参考资料

    1. GitHub: CocoaPods 官方源码
    2. GitHub: mjmsmith/pbxplorer
    3. Xcode Project File Format: 对.pbxproj文件每个参数的详细介绍
    4. XCode工程文件结构及Xcodeproj框架的使用( 二 )

    相关文章

      网友评论

        本文标题:[iOS]XcodeProject的内部结构分析

        本文链接:https://www.haomeiwen.com/subject/xzexbftx.html