iOS 兩個Target中資源文件的對比

1. 目標與背景

當前公司有兩個產(chǎn)品徙硅,除了部分資源外兩者差異不大,故放在同一個工程中焰情,用多Target的方式來管理陌凳。在維護過程中,會遇到新增的文件或圖片等資源在其中一個Target缺失的情況烙样,如果每次都把兩個產(chǎn)品都驗證一次冯遂,工作量較大。

于是想到谒获,所有的配置都在.project文件中記錄,包括每個Target各包含有那些資源壁却,可以通過解析.project 的方式來對兩者包含的資源進行檢查批狱,并在每次編譯前進行檢查,以及時發(fā)現(xiàn)問題展东。

經(jīng)過1周時間對這個思路進行了實踐赔硫。目前已經(jīng)能夠檢查包括編譯文件、frameworks和copy resources(下圖工程中的配置)以及.xcasset中資源的檢查盐肃,并可以設置忽略的資源列表爪膊,下面記錄一下自己的實踐歷程权悟。

image.png

2. 實現(xiàn)方法

2.1 .pbxproj 文件介紹

.prxproj 文件本質上是一種舊風格的 Property List 文件。
其結構用我們熟悉的JSON格式可以把整個文件的表示為下面形式推盛。

{
"archiveVersion" : "1",
"classes" : {
},
"objects" : {
    "0C3E934A20280D7E00C7CF6B" : {
    "fileRef" : "0C3E934920280D7E00C7CF6B",
    "isa" : "PBXBuildFile"
    },
    ...
    ...
},
"objectVersion" : "46",
"rootObject" : "8C7D6FB81A709259009D5B46"
}

其中最重要的是objects字段峦阁,里面包含了所有的配置。下面以Compile Sources中的main.m文件來管中窺豹看一下.pbxproj的組織方式耘成。

首先找到rootObject 的ID榔昔,其在字典的最外層定義,其代表的是該工程的根節(jié)點瘪菌。
在objects中搜索該ID撒会,找到其定義。接著找到其所包含的Target师妙,這里指向的是Target的ID.

image.png

這里我們以第一個Target為例诵肛,以同樣的方式,搜索其ID默穴,找到其定義:


image.png

找到其BuildPhase配置項:


image.png

在其定義中找到 main.m文件


image.png

找到其定義:


image.png

其文件定義:


image.png

可以看出怔檩,其所有的資源都會有一個ID值來標識,這個ID值在整個文件中是唯一的壁顶,以此來組織起整個邏輯珠洗。

.pbxproj的的詳細解釋可以參考下面兩篇文章:
xcode project file format
Let's talk about project pbxproj

2.2 .pbxproj 文件的解析

因為自己對python比較熟悉,所以剛開始就想找一個用python解析的庫若专,于是找到了mod-pbxproj许蓖,但看了文檔之后,發(fā)現(xiàn)提供的API太少调衰,無法獲取編譯文件列表等數(shù)據(jù)膊爪,故無法使用。

后面找到xcodeproj嚎莉,其是CocoaPods 寫的 一個Ruby 解析庫米酬,可以滿足需求,但這意味著自己也要用ruby來完成腳本趋箩。好在ruby和python一樣是腳本語言赃额,有很多相通的地方,學習起來難度也不大叫确。

腳本中使用到的基本方法如下:

# 解析.project文件
project = Xcodeproj::Project.open(project_path)
# 獲取到target跳芳,其中target_name_first是要取得的target的名稱
target_first = project.targets.select { |a_target| a_target.name.eql?(target_name_first)}
# 獲取Compile Sources
phase = target.source_build_phase
# 獲取Link Binary With Libraries
phase = target.frameworks_build_phase
# 獲取Copy Bundle Resources
phase = target.resources_build_phase
2.3 腳本實現(xiàn)

基本思路是,通過2.2中的方法竹勉,獲取到對應的文件列表飞盆,然后對列表進行對比,找出其中的不同孽水,并設置相應的忽略文件列表女气,來應對不同Target可能有的差異主卫。
主要具體實現(xiàn)如下:
從Target中取得文件路徑:

def file_arr_for_target(target, class_obj)
  if class_obj == $pbx_sources_class
    phase = target.source_build_phase
  elsif class_obj == $pbx_frameworks_class
    phase = target.frameworks_build_phase
  elsif class_obj == $pbx_resources_class
    phase = target.resources_build_phase
  else
    raise "unknow recognize class"
  end
  # puts phase
  file_arr = Array.new
  phase.files.to_a.each do |pbx_build_file|
    begin
      if pbx_build_file.file_ref.is_a?(Xcodeproj::Project::Object::PBXVariantGroup)
        pbx_build_file.file_ref.children.each do |item|
          file_arr << item.real_path.to_s
        end
      else
        file_arr << pbx_build_file.file_ref.real_path.to_s
      end
    rescue
      # 部分值不是PBXVariantGroup類,也不是PBXFileReference 類瘩将,會處理失敗走到這里姿现,對比源文件為空值备典,暫不處理意述。
      next
    end
  end
  return file_arr
end

這里在實際測試的時候遇到兩個問題:
1)在取文件路徑的時候荤崇,部分配置的fileRef為空术荤,導致最終的路徑也是空值瓣戚,最終發(fā)現(xiàn)其在源文件中也是空的子库,原因暫時還不清楚刚照,如下圖无畔。這里就先用rescue進行保護浑彰,不做進一步處理郭变。


image.png

2)本地化過的文件周伦,取值方式與其他不同专挪,因為其相對于其他文件寨腔,又多了一層迫卢,需要通過遍歷的方式去取得相應真正的資源文件乾蛤,處理如下:

if pbx_build_file.file_ref.is_a?(Xcodeproj::Project::Object::PBXVariantGroup)
        pbx_build_file.file_ref.children.each do |item|
          file_arr << item.real_path.to_s
        end

3).xcasset中的具體資源幻捏,未在.pbxproj中配置篡九。但其中的圖片也是檢查的重點榛臼∨嫔疲看了相關的介紹金刁,其本質上是文件夾的集合尤蛮。故最終通過文件遍歷的方式來進行檢查产捞。

def get_items_arr_in_folder(folder_path)
  items_arr = Array.new
  Dir.foreach(folder_path) do |file|
    if file == "." or file == ".." or file == ".DS_Store"
      next
    end
    path = File.join folder_path, file
    items_arr << path
    if File.directory? path
      items_arr += get_items_arr_in_folder path
    end
  end
  items_arr
end

def get_relative_paths_arr_in_folder(folder_path)
  paths_arr = get_items_arr_in_folder folder_path
  paths_arr.map do |path|
    path.slice! folder_path
    path
  end
end

def verify_assets(first_asset, last_asset)
  first_asset_list = get_relative_paths_arr_in_folder first_asset
  last_asset_list = get_relative_paths_arr_in_folder last_asset
  puts "\n--------\ncount:#{first_asset_list.length}, #{last_asset_list.length}\n--------\n"
  abnormal_list = first_asset_list - last_asset_list - $asset_ignore_keys
  reverse_abnormal_list = last_asset_list - first_asset_list - $asset_reverse_ignore_keys
  return abnormal_list, reverse_abnormal_list
end

Asset Catalog Format Reference

2.4 工程集成

為了方便能及時發(fā)現(xiàn)問題,故將這些檢查項集成到工程中赶促,每次編譯前先進行檢查芳杏,方法如下:
1)新建一個run script,重命名為TargetVerify泊脐。
注意:一般創(chuàng)建的run script 會被放在最后秕铛,這里的執(zhí)行順序是按Build Phase中的排列來的,我們希望它在編譯前執(zhí)行谨湘,所以需要把它拖動到Compile Sources前面.


image.png

image.png

2)在其中填入執(zhí)行ruby腳本的shell命令


image
#!/bin/sh
# 將此文件里面的命令放到 Build Phases -> Run Script 腳本中
echo "start verify target..."
pwd
declare -a cmd_list=("ruby ./script/target_verify/target_verify.rb ./xxx.xcodeproj <#target name first#> <#target name last#>"
"ruby ./script/target_verify/asset_verify.rb ./xxxx/xxxx.xcassets ./xxxx/xxx.xcassets")
for cmd in "${cmd_list[@]}"
do
    eval "$cmd"
    if [ $? -ne 0 ]
    then
    echo "FAILED"
    exit 1
    fi
done
echo "finished target verify and no issue found"

這樣就配置好了,在運行或編譯時乖仇,如果腳本運行不通過这敬,就會直接報編譯失敗崔涂。

具體實現(xiàn)的腳本已上傳到github冷蚂,希望對大家有所幫助。
target_verify

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子愧口,更是在濱河造成了極大的恐慌耍属,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡劳跃,警方通過查閱死者的電腦和手機郑诺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門轻抱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事梁呈。” “怎么了寻咒?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵粘舟,是天一觀的道長霞揉。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上痪宰,老公的妹妹穿的比我還像新娘叼架。我一直安慰自己,他們只是感情好酵镜,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布碉碉。 她就那樣靜靜地躺著,像睡著了一般淮韭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上贴届,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天靠粪,我揣著相機與錄音,去河邊找鬼毫蚓。 笑死占键,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的元潘。 我是一名探鬼主播畔乙,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼翩概!你這毒婦竟也來了牲距?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤钥庇,失蹤者是張志新(化名)和其女友劉穎牍鞠,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體评姨,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡难述,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吐句。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胁后。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖嗦枢,靈堂內(nèi)的尸體忽然破棺而出攀芯,到底是詐尸還是另有隱情,我是刑警寧澤净宵,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布敲才,位于F島的核電站裹纳,受9級特大地震影響,放射性物質發(fā)生泄漏紧武。R本人自食惡果不足惜剃氧,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望阻星。 院中可真熱鬧朋鞍,春花似錦、人聲如沸妥箕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽畦幢。三九已至坎吻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宇葱,已是汗流浹背瘦真。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留黍瞧,地道東北人诸尽。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像印颤,于是被迫代替她去往敵國和親您机。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354