iOS 两个Target中资源文件的对比

1. 目标与背景

当前公司有两个产品,除了部分资源外两者差异不大,故放在同一个工程中,用多Target的方式来管理。在维护过程中,会遇到新增的文件或图片等资源在其中一个Target缺失的情况,如果每次都把两个产品都验证一次,工作量较大。

于是想到,所有的配置都在.project文件中记录,包括每个Target各包含有那些资源,可以通过解析.project 的方式来对两者包含的资源进行检查,并在每次编译前进行检查,以及时发现问题。

经过1周时间对这个思路进行了实践。目前已经能够检查包括编译文件、frameworks和copy resources(下图工程中的配置)以及.xcasset中资源的检查,并可以设置忽略的资源列表,下面记录一下自己的实践历程。

image.png

2. 实现方法

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,其在字典的最外层定义,其代表的是该工程的根节点。
在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,但看了文档之后,发现提供的API太少,无法获取编译文件列表等数据,故无法使用。

后面找到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 脚本实现

基本思路是,通过2.2中的方法,获取到对应的文件列表,然后对列表进行对比,找出其中的不同,并设置相应的忽略文件列表,来应对不同Target可能有的差异。
主要具体实现如下:
从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为空,导致最终的路径也是空值,最终发现其在源文件中也是空的,原因暂时还不清楚,如下图。这里就先用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 工程集成

为了方便能及时发现问题,故将这些检查项集成到工程中,每次编译前先进行检查,方法如下:
1)新建一个run script,重命名为TargetVerify。
注意:一般创建的run script 会被放在最后,这里的执行顺序是按Build Phase中的排列来的,我们希望它在编译前执行,所以需要把它拖动到Compile Sources前面.


image.png

image.png

2)在其中填入执行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"

这样就配置好了,在运行或编译时,如果脚本运行不通过,就会直接报编译失败。

具体实现的脚本已上传到github,希望对大家有所帮助。
target_verify

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