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閱讀 228,835評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,676評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,730評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,118評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,873評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,266評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,330評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,482評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,036評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,846評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,025評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,575評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,279評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,684評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,953評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,751評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,016評論 2 375

推薦閱讀更多精彩內容

  • 引言 Xcode的工程文件是 工程名.xcodeproj,而它其實是個package目錄,通過顯示包內容,可以查看...
    俞子將閱讀 25,699評論 8 48
  • 轉至元數據結尾創建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,753評論 0 9
  • 我花了不到半天的時間看完了偶像劉同的新書《向著光亮那方》,意猶未盡。 美好的事物總是會讓人有一種一口氣看到最...
    暖冬zyc閱讀 403評論 4 2
  • 文明小讀者,請離閱覽室,我選擇沉默,至今后悔不已。 半年前,區圖書館成人閱覽室,進來母子倆。小讀者,估計小學高年級...
    陳茀茀閱讀 519評論 0 0
  • 一歲 一歲孩子作品與其說是繪畫,不如說是手的運動軌跡,讓孩子充分涂鴉不要給孩子看形 象也不要叫孩子畫形象 一...
    冕娘Kylin閱讀 633評論 0 0