我們的 App 最近要處理一個(gè)事情, 就是往里面添加 unity, 但是 unity 項(xiàng)目和 主項(xiàng)目又是分開的, 所以當(dāng)每次 unity 開發(fā)人員把導(dǎo)出的文件拋給我們的時(shí)候, 我們需要手動(dòng)的修改代碼層級(jí), 這是我自己不能忍受的, 所以我決定使用腳本來幫助我完成這件事情.
但是一直以來, 不依靠 XCode 來修改或者創(chuàng)建 iOS 工程一直是一個(gè)大問題, 一些解決方案是創(chuàng)建一個(gè)模板工程,然后在使用的時(shí)候再去下載這個(gè)工程, 之后只需要修改某幾個(gè)字段就好了.
但是,我又想到了 cocoapods, 想到了大神寫的 pbxprojHelper, 認(rèn)為肯定已經(jīng)存在輪子來幫助我完成這個(gè)任務(wù), 于是, 我最終找到了 Xcodeproj, 一個(gè) CocoaPods 官方提供的用于修改 xcode 工程文件的 ruby 類庫(kù)
那還費(fèi)什么話, 開搞 ! !
我做的腳本
由于之前發(fā)現(xiàn)了一篇博客, 解決的事情與我的類似, 所以我大規(guī)模的借鑒了他的代碼,然后根據(jù)自己的需求進(jìn)行了修改, 原文請(qǐng)查看參考資料的鏈接
首先, 如果我要更新 group, 那么我要做的第一件事情當(dāng)然是找到這個(gè) group
require 'xcodeproj'
project_path = File.join(File.dirname(__FILE__), "./GiftAR.xcodeproj")
project = Xcodeproj::Project.open(project_path)
target = project.targets.first
unityClassGroup = project.main_group.find_subpath(File.join('GiftAR', 'Unity', 'Classes'), true)
unityClassGroup.set_source_tree('<group>')
unityClassGroup.set_path('../../unity-ios-build/Classes')
- 通過路徑
project_path
獲取到了解析好的對(duì)象 project - 然后 通過這個(gè) project 獲取到相應(yīng)的 target 和 unityClassGroup, 這里的 target 為了修改之后的工程配置而預(yù)備的, unityClassGroup 就是我們要操作的文件夾
- 之后我們給 unityClassGroup 設(shè)置兩個(gè)屬性, 其中 source_tree 代表
The directory to which the path is relative.
文件夾與路徑的關(guān)系, 而 path 則代表文件夾的具體路徑, 這里我們的 unity 代碼不在工程內(nèi)部所以我們引用了一個(gè)其他位置的路徑
這里是不同 source_tree 的一些具體含義
Note: The accepted values are:
<absolute>
for absolute paths
<group>
for paths relative to the group
SOURCE_ROOT
for paths relative to the project
DEVELOPER_DIR
for paths relative to the developer directory.
BUILT_PRODUCTS_DIR
for paths relative to the build products directory.
SDKROOT
for paths relative to the SDK directory.
~~~
其實(shí)對(duì)應(yīng)到 Xcode 中, 就是下面這個(gè)位置
那么之后我要做的事情就是清除之前的引用, 下面這段代碼是從別人那里直接拿過來用的,
主要視為了介紹的連貫性, 不得不如此, 請(qǐng)見諒.
...
if !unityClassGroup.empty? then
removeBuildPhaseFilesRecursively(target, unityClassGroup)
unityClassGroup.clear()
end
...
def removeBuildPhaseFilesRecursively(aTarget, aGroup)
aGroup.files.each do |file|
if file.real_path.to_s.end_with?(".m", ".mm", ".cpp") then
aTarget.source_build_phase.remove_file_reference(file)
elsif file.real_path.to_s.end_with?(".plist") then
aTarget.resources_build_phase.remove_file_reference(file)
end
end
aGroup.groups.each do |group|
removeBuildPhaseFilesRecursively(aTarget, group)
end
end
這一段的意思是, 去清空原有 group 的所有引用, group 提供的清除單個(gè)引用的方法, 但是每次只能清除一條, 還好 group 也提供了 clear 方法.
但是只是清除 group 的引用還是不夠的 我們還需要去掉 target 對(duì) group 內(nèi)原有資源的引用, 這個(gè)就對(duì)應(yīng)到了 removeBuildPhaseFilesRecursively 方法上
...
addFilesToGroup(project, target, unityClassGroup)
project.save
...
def addFilesToGroup(project, aTarget, aGroup)
Dir.foreach(aGroup.real_path) do |entry|
filePath = File.join(aGroup.real_path, entry)
# 過濾目錄和.DS_Store文件
if !File.directory?(filePath) && entry != ".DS_Store" then
# 特殊邏輯
...
# 向group中增加文件引用
fileReference = aGroup.new_reference(filePath)
# 如果不是頭文件則繼續(xù)增加到Build Phase中,PB文件需要加編譯標(biāo)志
if filePath.to_s.end_with?("pbobjc.m", "pbobjc.mm") then
aTarget.add_file_references([fileReference], '-fno-objc-arc')
elsif filePath.to_s.end_with?(".m", ".mm", ".cpp") then
aTarget.source_build_phase.add_file_reference(fileReference, true)
elsif filePath.to_s.end_with?(".plist") then
aTarget.resources_build_phase.add_file_reference(fileReference, true)
end
# 目錄情況下, 遞歸添加
elsif File.directory?(filePath) && entry != '.' && entry != '..' then
hierarchy_path = aGroup.hierarchy_path[1, aGroup.hierarchy_path.length]
subGroup = project.main_group.find_subpath(hierarchy_path + '/' + entry, true)
subGroup.set_source_tree(aGroup.source_tree)
subGroup.set_path(aGroup.real_path + entry)
addFilesToGroup(project, aTarget, subGroup)
end
end
end
之后就我們要做的最后一步, 把之前的操作反過來再做一遍, 然后調(diào)用 save 方法保存 target
這里值得注意的是在處理多文件夾層級(jí)遞歸的時(shí)候, 我們需要生成新的對(duì)應(yīng)的 group ,這里用到了 group 對(duì)象的幾種不同的 path, 需要選擇合適的屬性加以利用.
到這里,我的所有腳本就全都說完了,很少,但是幫助了我很多.
xcodeproj 工程文件原理
但是我們不能僅僅限于使用別人的輪子, 也要學(xué)習(xí)原理, 我看了 使用代碼為 Xcode 工程添加文件, 學(xué)習(xí)很多
我打開了我們工程的配置文件, 就是一個(gè)超級(jí)大的 hash 字典, 根節(jié)點(diǎn)有五個(gè) archiveVersion
, classes
, objectVersion
, objects
, rootobject
其中 objects 是最重要的一個(gè), 里面的每個(gè)子節(jié)點(diǎn)對(duì)應(yīng)的就是一個(gè)個(gè)不同的配置文件, 他們每一個(gè)配置所對(duì)應(yīng)的 key, 都是一個(gè)唯一的 UUID, 而 rootobjects 指向的就是這么一個(gè) UUID
其中配置之前都有一段注釋來解釋配置的作用, 在配置內(nèi)部也同樣擁有一個(gè) isa 字段來表示這個(gè)配置的類型, 那么我就直接無恥的搬運(yùn)一下前輩們已經(jīng)總結(jié)好的類型, 不完全統(tǒng)計(jì),大概有如下一些類型
PBXBuildFile
PBXBuildPhase
PBXAppleScriptBuildPhase
PBXCopyFilesBuildPhase
PBXFrameworksBuildPhase
PBXHeadersBuildPhase
PBXResourcesBuildPhase
PBXShellScriptBuildPhase
PBXSourcesBuildPhase
PBXContainerItemProxy
PBXFileElement
PBXFileReference
PBXGroup
PBXVariantGroup
PBXTarget
PBXAggregateTarget
PBXLegacyTarget
PBXNativeTarget
PBXProject
PBXTargetDependency
XCBuildConfiguration
XCConfigurationList