使用 python 找出 iOS 项目中没有使用到的图片资源

gaoyuan123 8年前
   <p>随着版本迭代的进行,App 的体积不断膨胀,项目中未使用到的图片资源也不断积累,这会导致 App 的下载成本变高,特别是在使用流量的情况下,因此清理掉项目中不再使用的图片资源是很有必要的。我用 python 实现了下,原理很简单,就是 find + grep 命令的结合。下面说明下实现过程。</p>    <p>(一)分析</p>    <p>我们手动判断一张图片是否有使用到的方法是在 Xcode 中用 Shift + Command + F 全局搜索图片名字,看页面中是否有使用到,这一点我们可以使用 grep 命令。所以思路就有了,用 find 命令找出所有后缀是 ".png"、".jpg"、".jpeg"、".gif" 的文件名(不包括后缀,例如 a.png 我们需要取到a)存放到一个set中(用set是为了去重,因为图片会有@2x,@3x),然后从这个 set 中一个一个取出 key_word 在项目路径执行 grep -w(即单词匹配),有结果就说明这个关键字有被使用到。这个方法会有几个小问题,下文会提到。</p>    <p>(二)注意点</p>    <ol>     <li> <p>有两个目录需要特殊处理,/AppIcon.appiconset 和 /LaunchImage.launchimage,这是项目配置用到的图片,用 grep 并不会被匹配到,因此这两个目录下的图片资源要过滤掉,不需要被添加到匹配列表里</p> </li>     <li> <p>grep 是根据关键字匹配,因此如果一张图的名字是 "message",grep 有匹配到结果,这只能说明项目里有某个文件包含 "message" 关键字,不一定是使用到了图片,但反过来可以说明,如果没有一个文件中包含关键字,那么也不可能作为图片被使用</p> </li>     <li> <p>图片名字用宏定义,但是这个宏又不再使用,这种情况是会认为有被使用而遗漏掉;或者是原来有使用但是注释掉了,因为有出现关键字也会被遗漏掉</p> </li>     <li> <p>需要搜索的文件可以缩小范围,指定后缀为 ".h"、".m"、".mm"、".xib"、".swift"、".storyboard"</p> </li>     <li> <p>python 和 shell 交互时,路径如果带有空格两边表现不一致,解决方法是路径要用引号包裹,例如 'path',具体看 <a href="/misc/goto?guid=4959728725034323528" rel="nofollow,noindex">这儿</a></p> </li>    </ol>    <p>(三)源码</p>    <p>我用的是 python,写法还是 Objective-C 的风格,大家有更好的方法也可以讨论,源码如下:</p>    <pre>  <code class="language-python">#!/usr/bin/python  # -*- coding: utf-8 -*-  import os, sys  import subprocess  import shlex  import shutil  import time  __author__ = 'xieguobi'  exclude_AppIcon = 'AppIcon.appiconset'  exclude_LaunchImage = 'LaunchImage.launchimage'  project_dir = "/your_path"  back_not_used_dir = "/your_path"  auto_delete = 0  auto_move = 0  def find_exclude_images():      exclude_images_set = set()      command = "find '{0}' -type d -name {other}".format(project_dir, other = exclude_AppIcon)      s = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)      result = s.communicate()      if len(result) > 0:          exclude_path = result[0]          for type in support_types():              exclude_images_set = exclude_images_set | do_find_command(exclude_path,type)      command = "find '{0}' -type d -name {other}".format(project_dir, other = exclude_LaunchImage)      s = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)      result = s.communicate()      if len(result) > 0:          exclude_path = result[0]          for type in support_types():              exclude_images_set = exclude_images_set | do_find_command(exclude_path,type)      return exclude_images_set  def do_find_command(search_dir,file_type):      if len(search_dir) == 0 or len(file_type) == 0:          return set()      search_dir = search_dir.replace('\n','')      all_names_set = set()      command = "find '{}' -name '*.{other}' 2>/dev/null".format(search_dir,other = file_type)      s = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)      results = s.communicate()[0].split()      for name in results:          if not name.endswith(file_type):              continue          head, tail = os.path.split(name)          tail = os.path.splitext(tail)[0]          if "@" in tail:              all_names_set.add(tail.split('@')[0])          else:              all_names_set.add(tail)      return all_names_set  def do_grep(path,key_word):      if not is_available_file_path(path):          print ('path:%s is not available' % path)          return      command = "grep -w -q '%s' '%s'" %(key_word,path)      if subprocess.call(command, shell=True) == 0:          return 1      else:          return 0  def goal_file(path):      files = []      for dirName, subdirList, fileList in os.walk(path):                                      for fname in fileList:                                              if is_available_file_path(fname):                                                  path = '%s/%s' % (dirName,fname)                                                  files.append(path)      return files  def is_available_file_path(path):      available = 0      if path.endswith('.m'):         available = 1      if path.endswith('.h'):         available = 1      if path.endswith('.mm'):          available = 1      if path.endswith('.xib'):          available = 1      if path.endswith('.swift'):          available = 1      if path.endswith('.storyboard'):          available = 1      return available  def support_types():      types = []      types.append('png')      types.append('jpg')      types.append('jpeg')      types.append('gif')      return types  def delete_not_used_image(image):      if len(image) == 0:          return      command = "find '{}' \( -name '{other1}' -o -name '{other2}@*' \) 2>/dev/null".format(project_dir,other1 = image,other2 = image)      s = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)      results = s.communicate()[0].split()      for path in results:          valid = 0          for type in support_types():              if path.endswith(type):                  valid = 1                  break          if valid:              os.remove(path)              print ('\r\n ========%s is deleted========' % image)  def move_not_used_image(image):      if len(image) == 0:          return      command = "find '{}' \( -name '{other1}' -o -name '{other2}@*' \) 2>/dev/null".format(project_dir,other1 = image,other2 = image)      s = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)      results = s.communicate()[0].split()      for path in results:          valid = 0          for type in support_types():              if path.endswith(type):                  valid = 1                  break          if valid:              filename, file_extension = os.path.splitext(path)              des_dir = os.path.join(back_not_used_dir,"{}{}".format(image,file_extension))              shutil.move(path,des_dir)              print ('\r\n ========%s is moved========' % image)  def start_find_task():      print("\nstart finding task...\nbelows are not used images:\n")      global project_dir      if len(sys.argv) > 1:          project_dir = sys.argv[1]      if project_dir == " ":          print("error! project_dir can not be nil")      start = time.time()      i = 0      exclude_images_set = find_exclude_images()      results = set()      for type in support_types():              results = results | do_find_command(project_dir,type)      results = results - exclude_images_set      goal_files = goal_file(project_dir)      for image_name in results:          used = 0          for file_path in goal_files:              if do_grep(file_path,image_name):                  used = 1                  # print ('image %s is used' % image_name)                  break          if used == 0:              print(image_name)              i = i + 1              if auto_delete:                  delete_not_used_image(image_name)              elif auto_move:                  move_not_used_image(image_name)      c = time.time() - start      print('\nsearch finish,find %s results,total count %0.2f s'%(i,c))  start_find_task()</code></pre>    <p>有两种使用方法:</p>    <p>1、修改源码的 project_dir 变量,然后</p>    <pre>  <code class="language-python">python find_not_use_images.py</code></pre>    <p>2、路径通过系统参数代入,打开终端,输入</p>    <pre>  <code class="language-python">python find_not_use_images.py /your path</code></pre>    <p>auto_delete 参数表示找到没有使用的图片是否要删除,默认是0不删除</p>    <p>auto_move 参数表示找到没有使用的图片是否要移动到指定的 back_not_used_dir 目录</p>    <p> </p>    <p>来自:http://www.cocoachina.com/ios/20161208/18318.html</p>    <p> </p>