本文為學(xué)習(xí)記錄所作,內(nèi)容來源于玉令天下的博客-Let's Talk About project.pbxproj
概述
在此之前對(duì)于project.pbxproj
這個(gè)文件只停留在git出文件沖突時(shí), 使用Xcode打開它, 處理一下沖突并保存完事. 后來做項(xiàng)目混淆時(shí), 其中一步是修改文件名, 而修改文件名之后原本Xcode中的引用就變成大紅色了. 而之前的做法是, 刪除引用并重新把改名后的文件拖進(jìn)來, 因?yàn)槲募? Xcode需要加載好長時(shí)間. 為了解決這個(gè)問題, 故學(xué)習(xí)和使用到這個(gè)文件.
文件路徑
項(xiàng)目名.xcodeproj-> 右擊顯示包內(nèi)容 -> project.pbxproj
文件格式:
它本質(zhì)上是一種舊風(fēng)格的plist文件, 與如今Xcode中使用plist文件格式不同(現(xiàn)在新的plist文件采用xml格式), 它這樣表示一個(gè)數(shù)組:
( "1", "2", "3" )
它這樣表示一個(gè)字典:
{
"key" = "value";
...
}
內(nèi)容規(guī)則
project.pbxproj
使用 UUID 作為交叉引用的索引, 保證每個(gè)配置信息對(duì)象的唯一性. 因?yàn)?UUID 猜測(cè)是根據(jù)機(jī)器硬件和時(shí)間戳等要素生成, 避免了多人在同一時(shí)間段操作修改工程文件帶來的問題. 也就是說工程中每項(xiàng)配置對(duì)象(文件,目錄,配置表等等)都有個(gè)唯一的 UUID, 然后其他配置對(duì)象想引用某個(gè)配置對(duì)象直接使用它的 UUID 即可. 這就跟我們編程時(shí)使用指針指向某個(gè)對(duì)象的地址一樣, 其他對(duì)象的屬性想引用它, 只需要給屬性傳個(gè)指針地址就行了.
整體內(nèi)容格式
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
....
keys = values;(to much to show here)
....
};
rootObject = C5A75D711F5E7D2E000191E4 /* Project object */;
}
其中重要的 objects
和 rootObject
, 因?yàn)槠渌鼛讉€(gè)基本固定不變的. 所有的配置對(duì)象都放在objects
對(duì)應(yīng)的 value
中, 包括根對(duì)象rootObject
. objects
對(duì)應(yīng)的 value
也是一個(gè)字典, key
都為 UUID, value
依然是個(gè)字典. 讀懂 project.pbxproj
的最好方式就是順著 rootObject
的各個(gè)屬性對(duì)應(yīng)的 UUID 在 objects
中找到對(duì)應(yīng)的對(duì)象, 然后一層層看下去. 這樣整個(gè)文件的配置信息存放方式就慢慢摸清了.
配置信息對(duì)象的類型
objects
的鍵值對(duì)根據(jù)內(nèi)容類型被分成了若干個(gè)section
, section
的數(shù)量和種類跟工程有關(guān), 根據(jù)工程的配置多少而不同, 下面列出了一個(gè)section 列表(非完整):
PBXBuildFile
PBXBuildPhase
PBXAppleScriptBuildPhase
PBXCopyFilesBuildPhase
PBXFrameworksBuildPhase
PBXHeadersBuildPhase
PBXResourcesBuildPhase
PBXShellScriptBuildPhase
PBXSourcesBuildPhase
PBXContainerItemProxy
PBXFileElement
PBXFileReference
PBXGroup
PBXVariantGroup
PBXTarget
PBXAggregateTarget
PBXLegacyTarget
PBXNativeTarget
PBXProject
PBXTargetDependency
XCBuildConfiguration
XCConfigurationList
而每個(gè)section
/* Begin xxxx section */ (以這種注釋開始)
...
/* End xxxx section */ (以這種注釋結(jié)束)
一個(gè)section里面的內(nèi)容類型是相同的. 而類型可以從isa這個(gè)key對(duì)應(yīng)的value看出來. 比如項(xiàng)目中所有文件的引用是一種類型,
叫做 PBXFileReference
. 我新建了一個(gè)項(xiàng)目, 隨意拖入的一些文件.
而在objects
中的表現(xiàn)如下:
/* Begin PBXFileReference section */
C55BB9141F67BE1C00D17310 /* TwoPanScrollView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TwoPanScrollView.h; sourceTree = "<group>"; };
C55BB9151F67BE1C00D17310 /* TwoPanScrollView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TwoPanScrollView.m; sourceTree = "<group>"; };
C55FA2A220FDE9B7006BBB41 /* Person.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Person.h; sourceTree = "<group>"; };
C55FA2A320FDE9B7006BBB41 /* Person.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Person.m; sourceTree = "<group>"; };
C5616BC21FA858C000A9C033 /* UIView+Toast.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+Toast.h"; sourceTree = "<group>"; };
C5616BC31FA858C000A9C033 /* UIView+Toast.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+Toast.m"; sourceTree = "<group>"; };
C57DC7251F67FAF600106DF4 /* SecondViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SecondViewController.h; sourceTree = "<group>"; };
C57DC7261F67FAF600106DF4 /* SecondViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SecondViewController.m; sourceTree = "<group>"; };
C5A75D791F5E7D2E000191E4 /* 學(xué)習(xí)之路.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "學(xué)習(xí)之路.app"; sourceTree = BUILT_PRODUCTS_DIR; };
C5A75D7D1F5E7D2E000191E4 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
C5A75D7F1F5E7D2E000191E4 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
C5A75D801F5E7D2E000191E4 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
C5A75D821F5E7D2E000191E4 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
C5A75D831F5E7D2E000191E4 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
C5A75D861F5E7D2E000191E4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
C5A75D881F5E7D2E000191E4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
C5A75D8B1F5E7D2E000191E4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
C5A75D8D1F5E7D2E000191E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
C5A75D921F5E7D2E000191E4 /* 學(xué)習(xí)之路Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "學(xué)習(xí)之路Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
C5A75D961F5E7D2E000191E4 /* ____Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "____Tests.m"; sourceTree = "<group>"; };
C5A75D981F5E7D2E000191E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
C5A75D9D1F5E7D2E000191E4 /* 學(xué)習(xí)之路UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "學(xué)習(xí)之路UITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
C5A75DA11F5E7D2E000191E4 /* ____UITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "____UITests.m"; sourceTree = "<group>"; };
C5A75DA31F5E7D2E000191E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
再比如項(xiàng)目中所有文件的分組是一種類型, 叫做 PBXGroup
. 如下:
/* Begin PBXGroup section */
C5616BC11FA858C000A9C033 /* Toast */ = {
isa = PBXGroup;
children = (
C5616BC21FA858C000A9C033 /* UIView+Toast.h */,
C5616BC31FA858C000A9C033 /* UIView+Toast.m */,
);
path = Toast;
sourceTree = "<group>";
};
C57EAA11212D02A4003D38CD /* TwoPanScrollView */ = {
isa = PBXGroup;
children = (
C55BB9141F67BE1C00D17310 /* TwoPanScrollView.h */,
C55BB9151F67BE1C00D17310 /* TwoPanScrollView.m */,
);
path = TwoPanScrollView;
sourceTree = "<group>";
};
C57EAA12212D02D7003D38CD /* Class */ = {
isa = PBXGroup;
children = (
C5A75D7F1F5E7D2E000191E4 /* AppDelegate.h */,
C5A75D801F5E7D2E000191E4 /* AppDelegate.m */,
C5A75D821F5E7D2E000191E4 /* ViewController.h */,
C5A75D831F5E7D2E000191E4 /* ViewController.m */,
C57DC7251F67FAF600106DF4 /* SecondViewController.h */,
C57DC7261F67FAF600106DF4 /* SecondViewController.m */,
C55FA2A220FDE9B7006BBB41 /* Person.h */,
C55FA2A320FDE9B7006BBB41 /* Person.m */,
C5616BC11FA858C000A9C033 /* Toast */,
C57EAA11212D02A4003D38CD /* TwoPanScrollView */,
);
path = Class;
sourceTree = "<group>";
};
C5A75D701F5E7D2E000191E4 = {
isa = PBXGroup;
children = (
C5A75D7B1F5E7D2E000191E4 /* 學(xué)習(xí)之路 */,
C5A75D951F5E7D2E000191E4 /* 學(xué)習(xí)之路Tests */,
C5A75DA01F5E7D2E000191E4 /* 學(xué)習(xí)之路UITests */,
C5A75D7A1F5E7D2E000191E4 /* Products */,
);
sourceTree = "<group>";
};
C5A75D7A1F5E7D2E000191E4 /* Products */ = {
isa = PBXGroup;
children = (
C5A75D791F5E7D2E000191E4 /* 學(xué)習(xí)之路.app */,
C5A75D921F5E7D2E000191E4 /* 學(xué)習(xí)之路Tests.xctest */,
C5A75D9D1F5E7D2E000191E4 /* 學(xué)習(xí)之路UITests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
C5A75D7B1F5E7D2E000191E4 /* 學(xué)習(xí)之路 */ = {
isa = PBXGroup;
children = (
C57EAA12212D02D7003D38CD /* Class */,
C5A75D851F5E7D2E000191E4 /* Main.storyboard */,
C5A75D881F5E7D2E000191E4 /* Assets.xcassets */,
C5A75D8A1F5E7D2E000191E4 /* LaunchScreen.storyboard */,
C5A75D8D1F5E7D2E000191E4 /* Info.plist */,
C5A75D7C1F5E7D2E000191E4 /* Supporting Files */,
);
path = "學(xué)習(xí)之路";
sourceTree = "<group>";
};
C5A75D7C1F5E7D2E000191E4 /* Supporting Files */ = {
isa = PBXGroup;
children = (
C5A75D7D1F5E7D2E000191E4 /* main.m */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
C5A75D951F5E7D2E000191E4 /* 學(xué)習(xí)之路Tests */ = {
isa = PBXGroup;
children = (
C5A75D961F5E7D2E000191E4 /* ____Tests.m */,
C5A75D981F5E7D2E000191E4 /* Info.plist */,
);
path = "學(xué)習(xí)之路Tests";
sourceTree = "<group>";
};
C5A75DA01F5E7D2E000191E4 /* 學(xué)習(xí)之路UITests */ = {
isa = PBXGroup;
children = (
C5A75DA11F5E7D2E000191E4 /* ____UITests.m */,
C5A75DA31F5E7D2E000191E4 /* Info.plist */,
);
path = "學(xué)習(xí)之路UITests";
sourceTree = "<group>";
};
/* End PBXGroup section */
更多配置對(duì)象屬性和類型以及含義可以參照這篇文章提供的對(duì)照表:Xcode Project File Format英文 翻譯版:中文翻譯
運(yùn)用
- 我們?cè)谑褂?
Cocoapods
時(shí)發(fā)現(xiàn)它可以更改Xcode工程目錄和一些配置信息, 這其實(shí)就是Cocoapods
通過它的組件Xcodeproj
來對(duì)工程結(jié)構(gòu)進(jìn)行修改, 其中就包括讀取project.pbxproj
信息, 進(jìn)行相關(guān)的修改后再存入. 完成自動(dòng)化導(dǎo)入第三方庫. - 一些自動(dòng)化打包工具也會(huì)通過讀寫
project.pbxproj
中的信息完成對(duì)證書的動(dòng)態(tài)修改. - 簡單實(shí)踐一個(gè)例子. 比如我們將
Person.h
和Person.m
show in finder, 將他們的文件名稱修改為FTPerson.h
和FTPerson.m
. 回到Xcode會(huì)發(fā)現(xiàn)原來的引用已經(jīng)無效了.
Person無效引用.png
這時(shí)候我們可以直接打開project.pbxproj
, 全局搜索Person
并替換為FTPerson
, 保存后發(fā)現(xiàn)Xcode中的引用恢復(fù)了正常. 這個(gè)也正是我在編寫腳本實(shí)現(xiàn)項(xiàng)目中所有文件改名后, 再通過修改project.pbxproj
中的引用信息從而跳過了刪除引用再添加引用的這個(gè)耗時(shí)操作的原理.
參考
http://www.cocoachina.com/ios/20170110/18549.html
http://yulingtianxia.com/blog/2016/09/28/Let-s-Talk-About-project-pbxproj/