Xcode中project.pbxproj 合并冲突的终极解决方法

引言

Xcode的工程文件是 工程名.xcodeproj,它其实是个package包,通过显示包内容,可以查看到它内部主要有project.pbxproj 和 xcuserdata,以及xcshareddata。其中,xcuserdata 一般是跟用户相关的一些设置,如断点 记录等,一般不用放到版本管理中。而project.pbxproj 是工程描述文件,描述了工程里的源码文件、scheme设置等。它的格式是文本类型的plist(Info.plist是binary plist),里面是一个一个的object。
当团队中多人同时开发或者进行项目架构调整时,首先会出现冲突的地方就是这里。尤其是已经经历很长开发周期的老项目,升级改造时,随着文件的新建、删除、以及各种移动等等。各分支merge时带来的工程文件冲突十分令人头疼。对于这种project.pbxproj冲突,目前没有什么好的解决办法,只能人工逐个识别判断,稍有不慎。可能xcode就打不开了。
那么,怎么办呢???笔者最近参与一大型项目的重构,因为项目启动开发时间较早,在开发周期长,文件数量大。分支merge时,遇到的project.pbxproj冲突,十分头疼。最正在一次解决冲突的过程中,受到一位同事启发。用此方法来解决project.pbxproj冲突,简直事半功倍。那到底是什么方法 ---- 3-6法则。在讲3-6法则前先普及下基本知识:对project.pbxproj作简要说明。

pbxproj文件简要说明

pbxproj是个plist文件,plist的格式跟json的差不多,就是一个个对象,对象是个字典,可以关联一些字段和它的值。pbxproj的总体框架如下:

// !$*UTF8*$!
{
    archiveVersion = 1;
    classes = {
    };
    objectVersion = 45;
    objects = {
            /* ... */
    };
    rootObject = 29B97313FDCFA39411CA2CEA /* Project object */;
}

其中objects是主要的字段。它本身是一个大哈希,里面包含了一个个的键值对。如下:

//1、PBXFileReference
1A36EFE51CEAC506005A5035 /* DiscoverManager.h */ = {
isa = PBXFileReference;
fileEncoding = 4; 
lastKnownFileType = sourcecode.c.h; 
path = DiscoverManager.h; 
sourceTree = "<group>"; 
};

//2、PBXBuildFile
1A1282EE1C069969000C36AA /* ScreenCaptureViewController.m in Sources */ = {
isa = PBXBuildFile;
 fileRef = 1A1282ED1C069969000C36AA /* ScreenCaptureViewController.m */; 
};

//3、PBXSourcesBuildPhase
BF3014D41C10632C0080D38E /* Sources */ = {
    isa = PBXSourcesBuildPhase;
    buildActionMask = 2147483647;
    files = (
        BF3015161C10700E0080D38E /* AAStable3ViewController.m in Sources */,
        BF3015101C106FD70080D38E /* AAStable1ViewController.m in Sources */,
        BF3015221C10707E0080D38E /* AAFileMayMoveViewController.m in Sources */,
    );
    runOnlyForDeploymentPostprocessing = 0;
};

//4、PBXResourcesBuildPhase
BF3014D61C10632C0080D38E /* Resources */ = {
    isa = PBXResourcesBuildPhase;
    buildActionMask = 2147483647;
    files = (
        BF3014EB1C10632C0080D38E /* LaunchScreen.storyboard in Resources */,
        BF3014E81C10632C0080D38E /* Assets.xcassets in Resources */,
        BF3014E61C10632C0080D38E /* Main.storyboard in Resources */,
        BF3014E61C10632C0080D38E /* coverstory_done_highlight@3x.png in Resources */,
    );
    runOnlyForDeploymentPostprocessing = 0;
};

//5、PBXGroup
BF3014CF1C10632C0080D38E = {
    isa = PBXGroup;
    children = (
        BF3014DA1C10632C0080D38E /* PBTest */,
        BF3014F41C10632C0080D38E /* PBTestTests */,
        BF3014FF1C10632D0080D38E /* PBTestUITests */,
        BF3014D91C10632C0080D38E /* Products */,
    );
    sourceTree = "<group>";
};

这里的BF3014CF1C10632C0080D38E模样的数据 是uuid,后面又是一个对象。(1)每个对象中的对象都有一个isa字段,用来表明了object的类型。(上面我们共列举了5中类型,还有其他类型,这里我们只重点介绍这五种)
(2)对象中的其他字段取决于object的类型。

objects中根据uuid和对象的关联,就可以唯一标识这个对象,方便对象的相互引用。例如:通过uuid,PBXFileReference 类型的对象可以被PBXBuildFile和PBXGroup对象引用,PBXBuildFile 对象可以被PBXSourcesBuildPhase 对象引用。

针对常用的类型做简要说明:

1、PBXFileReference 用来跟踪工程中使用的外部文件(对应到磁盘),包括源文件、头文件、资源文件、库、生成的应用文件等。(简单理解就是,工程中引用到的所有类型的文件,.h.m\storyboard\Pods-News.debugadhoc.xcconfig 等等)它会被PBXGroup、PBXBuildFile等调用。

PXBFileReference类型的objc的结构大体如下:(在看其结构时,重点关注.m和.h文件的数目,这关系到我们上面提到的3-6原则)

/* Begin PBXFileReference section */
//示例1
1A58A5351CE03FA70020DE69 /* MomoChatShareService.m */ = { (1次)
isa = PBXFileReference; 
fileEncoding = 4; 
lastKnownFileType = sourcecode.c.objc; 
path = MomoChatShareService.m; (1次)
sourceTree = "<group>"; 
};
//示例2
1A58A5391CE0407C0020DE69 /* MomoChatSDK.h */ = { (1次)
isa = PBXFileReference;
 fileEncoding = 4; 
lastKnownFileType = sourcecode.c.h; 
path = MomoChatSDK.h; (1次)
sourceTree = "<group>"; 
};
//示例3
1A58A5391CE0407C0020DE69 /* Base */ = {
    isa = PBXFileReference; 
    lastKnownFileType = file.storyboard; 
    name = Base;
    path = Base.lproj/Main.storyboard; 
    sourceTree = "<group>"; 
};
......
/* End PBXFileReference section */

以MomoChatShareService.m这个.m文件为例,它在PBXFileReference section中出现了2次。同样,MomoChatSDK.h这个.h文件也出现了2次。我们暂且先记住他的次数。

2、PBXBuildFile :参与编译的PBXFileReference会有对应的PBXBuildFile,它会被PBXSourcesBuildPhase或PBXResourcesBuildPhase调用,这里一般不会有.h文件。PBXBuildFile类型的objc的结构大体如下:

/* Begin PBXBuildFile section */

//示例1
4B17C2FB283B5E0EDF457674 /* libPods-WLRRoute_Example.a in Frameworks */ = {
isa = PBXBuildFile;
 fileRef = 345940877F636B03192F1CA8 /* libPods-WLRRoute_Example.a */;
 };

//示例2
6003F58E195388D20070C39A /* Foundation.framework in Frameworks */ = {
isa = PBXBuildFile; 
fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; 
};

//示例3
1A58A5351CE03FA70020DE69 /* MomoChatShareService.m in Sources */ = {(1次)
isa = PBXBuildFile; 
fileRef = 76B4BC811E06A18400D1E590 /* MomoChatShareService.m */; (1次)
};
/* End PBXBuildFile section */

这个对象总包含了两个key值,isa和fileRef,分别用来指明对象的类型和它调用的PBXFileReference。
我们再来看下MomoChatShareService.m出现的次数,共2次,分别在name和fileRef中各包含一次。这时注意观察PBXBuildFile中包含MomoChatShareService.m的objc的UUID,同PBXFileReference中包含MomoChatShareService.m的objc的UUID是相同的。而且一定是相同的,只有相同的UUID才能唯一标识一个文件。

3、PBXSourcesBuildPhase:列出工程中参与编译的文件(Xcode中Build Phases下的Compiles Source)。如果有多个target,则会有多个source,如UITest、UNIT-Test都会生成source,下面是主target的source :

/* Begin PBXSourcesBuildPhase section */
//示例1:主工程target
6003F586195388D20070C39A /* Sources */ = {
            isa = PBXSourcesBuildPhase;
            buildActionMask = 2147483647;
            files = (
                1A58A5351CE03FA70020DE69 /* MomoChatShareService.m in Sources */ (1次)
                6003F5A7195388D20070C39A /* ViewController.m in Sources */,
                76B4BC851E06A18E00D1E590 /* UserHandler.m in Sources */,
                76B4BC8B1E06A1AA00D1E590 /* UserViewController.m in Sources */,
                6003F59A195388D20070C39A /* main.m in Sources */,
                76B4BC881E06A1A100D1E590 /* SignViewController.m in Sources */,
                                .......
            );
            runOnlyForDeploymentPostprocessing = 0;
        };
//示例2:test target
6003F5AA195388D20070C39A /* Sources */ = {
            isa = PBXSourcesBuildPhase;
            buildActionMask = 2147483647;
            files = (
                6003F5BC195388D20070C39A /* Tests.m in Sources */,
            );
            runOnlyForDeploymentPostprocessing = 0;
        };
/* End PBXSourcesBuildPhase section */

PBXSourcesBuildPhase中主要有两个重要的key:isa 和 files。分别表明对象的类型和他所包含参与编译的文件。
这时我们看到MomoChatShareService.m文件的出现的次数为1,到目前为止MomoChatShareService.m在整个工程文件中出现的次数为5次。(这里边不会出现.h文件。)

4、PBXResourcesBuildPhase:包含了工程中编译的资源文件(如图片、storyBoard等),PBXResourcesBuildPhase的结构如下:

/* Begin PBXResourcesBuildPhase section */
        6003F588195388D20070C39A /* Resources */ = {
            isa = PBXResourcesBuildPhase;
            buildActionMask = 2147483647;
            files = (
                873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */,
                6003F5A9195388D20070C39A /* Images.xcassets in Resources */,
                6003F598195388D20070C39A /* InfoPlist.strings in Resources */,
                                06ED2FDA1B29656D007679A4 /* kr-video-player-pause@3x.png in Resources */,
            );
            runOnlyForDeploymentPostprocessing = 0;
        };
        6003F5AC195388D20070C39A /* Resources */ = {
            isa = PBXResourcesBuildPhase;
            buildActionMask = 2147483647;
            files = (
                6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */,
            );
            runOnlyForDeploymentPostprocessing = 0;
        };
/* End PBXResourcesBuildPhase section */

PBXResourcesBuildPhase 对象中同样有两个重要的key,isa 和 files ,分别表明对象的类型和工程编译用到的资源文件,例如图片、storyboard等。
显然,这里面不会包含.h和.m文件。

5、PBXGroup 对应工程中的group。也就是我们开发时划分的目录,

例如:工程中的目录结构如下图所示:

2017-05-13 23.17.16.png

对应的PBXGroup结构如下:

/* Begin PBXGroup section */
        060D3DF51EC75917001A30BE /* Classes */ = {
            isa = PBXGroup;
            children = (
                060D3DF61EC75917001A30BE /* VideoPalyerView */,
                060D3DF91EC75917001A30BE /* VideoPlayerVC */,
                060D3DFE1EC75937001A30BE /* VideoPlayerModel.h */,
                060D3DFF1EC75937001A30BE /* VideoPlayerModel.m */,
            );
            path = Classes;
            sourceTree = "<group>";
        };
                //子目录中的内容,使用单独的对象来展示
        060D3DF61EC75917001A30BE /* VideoPalyerView */ = {
            isa = PBXGroup;
            children = (
                060D3DF71EC75917001A30BE /* KRVideoPlayerControlView.h */,
                060D3DF81EC75917001A30BE /* KRVideoPlayerControlView.m */,
            );
            path = VideoPalyerView;
            sourceTree = "<group>";
        };
                //子目录中的内容,使用单独的对象来展示
        060D3DF91EC75917001A30BE /* VideoPlayerVC */ = {
            isa = PBXGroup;
            children = (
                060D3DFA1EC75917001A30BE /* KRVideoPlayerController.h */,
                060D3DFB1EC75917001A30BE /* KRVideoPlayerController.m */,
            );
            path = VideoPlayerVC;
            sourceTree = "<group>";
        };
        1A58A5351CE03FA70020DE69 /* ChatModule */ = {
            isa = PBXGroup;
            children = (
                 1A58A5351CE03FA70020DE69 /* MomoChatShareService.h */,
                            1A58A5351CE03FA70020DE69 /* MomoChatShareService.m */,(1次)
            );
            name = ChatModule;
            sourceTree = "<group>";
        };
              1A58A5391CE0407C0020DE69 /* MomoChatSDK */ = {
            isa = PBXGroup;
            children = (
                1A58A5391CE0407C0020DE69 /* MomoChatSDK.h */,(1次)
                            1A58A5391CE0407C0020DE69 /* MomoChatSDK.m */,
            );
            name = ChatModule;
            sourceTree = "<group>";
        };
    ......

/* End PBXGroup section */

PBXGroup同工程中的group是一致的,如果工程中的某个目录下包含子目录,则在其children字段中只会显示相应的子目录名称,子目录下的内容会单独创建一个object对象来展示。例如:Classes目录下,包含了VideoPalyerView和VideoPlayerVC两个子目录,在Classes objc的children中只会包含子目录的名称。对于VideoPalyerView和VideoPlayerVC这两个子目录中的内容,使用单独的PBXGroup对象来标识。
MomoChatShareService.m 在PBXGroup中共出现1次,这时我们查看对应的objc的UUID同PBXSourceFile、PBXFileReference、PBXBuildFile中包含MomoChatShareService.m的objc的UUID完全相同。也一定会相同。至此MomoChatShareService.m 在工程文件中共出现6次。包含该文件的objc每一处的UUID都是相等的。
** MomoChatSDK.h在PBXGroup中共出现1次,这时我们查看对应的objc的UUID同PBXFileReference中包含 MomoChatSDK.h的objc的UUID完全相同。至此, MomoChatSDK.h** 在工程文件中共出现3次。包含该文件的objc每一处的UUID都是相等的。

结论:

所谓的3-6法则就是:在工程文件中,某个类的.m文件一定只有6处,.h文件只有3处。该法则适用于所有的.m和.h文件。

应用

有了3-6法则后,我们怎么使用该法则呢?很简单,当出现冲突时,分别全局查找冲突文件的.m和.h文件的总数。只要是少于6或者3个的文件一般是新的工程中不存在的文件,可直接将该文件删掉。而多余6或者3个文件的则需要将多余的文件删掉。那怎么才能确定那些事多余的文件呢??这就需要用到UUID,通过比对冲突处包含.m或者.h文件的objc的UUID是否跟其他位置上对应文件的UUID相同,相同则保留,反之则删除。

自动化脚本

正在完善中,请期待...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • 引言 Xcode的工程文件是 工程名.xcodeproj,而它其实是个package目录,通过显示包内容,可以查看...
    俞子将阅读 25,635评论 8 48
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 我花了不到半天的时间看完了偶像刘同的新书《向着光亮那方》,意犹未尽。 美好的事物总是会让人有一种一口气看到最...
    暖冬zyc阅读 382评论 4 2
  • 文明小读者,请离阅览室,我选择沉默,至今后悔不已。 半年前,区图书馆成人阅览室,进来母子俩。小读者,估计小学高年级...
    陈茀茀阅读 488评论 0 0
  • 一岁 一岁孩子作品与其说是绘画,不如说是手的运动轨迹,让孩子充分涂鸦不要给孩子看形 象也不要叫孩子画形象 一...
    冕娘Kylin阅读 600评论 0 0