隨著版本迭代的進行,App 的體積不斷膨脹,項目中未使用到的圖片資源也不斷積累,這會導致 App 的下載成本變高,特別是在使用流量的情況下,因此清理掉項目中不再使用的圖片資源是很有必要的。我用 python 實現了下,原理很簡單,就是 find + grep 命令的結合。下面說明下實現過程。
(一)分析
我們手動判斷一張圖片是否有使用到的方法是在 Xcode 中用 Shift + Command + F 全局搜索圖片名字,看頁面中是否有使用到,這一點我們可以使用 grep 命令。所以思路就有了,用 find 命令找出所有後綴是 ".png"、".jpg"、".jpeg"、".gif" 的文件名(不包括後綴,例如 a.png 我們需要取到a)存放到一個set中(用set是為了去重,因為圖片會有@2x,@3x),然後從這個 set 中一個一個取出 key_word 在項目路徑執行 grep -w(即單詞匹配),有結果就說明這個關鍵字有被使用到。這個方法會有幾個小問題,下文會提到。
(二)注意點
有兩個目錄需要特殊處理,/AppIcon.appiconset 和 /LaunchImage.launchimage,這是項目配置用到的圖片,用 grep 並不會被匹配到,因此這兩個目錄下的圖片資源要過濾掉,不需要被添加到匹配列表裡
grep 是根據關鍵字匹配,因此如果一張圖的名字是 "message",grep 有匹配到結果,這只能說明項目裡有某個文件包含 "message" 關鍵字,不一定是使用到了圖片,但反過來可以說明,如果沒有一個文件中包含關鍵字,那麼也不可能作為圖片被使用
圖片名字用宏定義,但是這個宏又不再使用,這種情況是會認為有被使用而遺漏掉;或者是原來有使用但是注釋掉了,因為有出現關鍵字也會被遺漏掉
需要搜索的文件可以縮小范圍,指定後綴為 ".h"、".m"、".mm"、".xib"、".swift"、".storyboard"
python 和 shell 交互時,路徑如果帶有空格兩邊表現不一致,解決方法是路徑要用引號包裹,例如 'path',具體看這兒
(三)源碼
我用的是 python,寫法還是 Objective-C 的風格,大家有更好的方法也可以討論,源碼如下:
#!/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()
我也放到了 github 上,可以從這兒下載
有兩種使用方法:
1、修改源碼的 project_dir 變量,然後
python find_not_use_images.py
2、路徑通過系統參數代入,打開終端,輸入
python find_not_use_images.py /your path
auto_delete 參數表示找到沒有使用的圖片是否要刪除,默認是0不刪除
auto_move 參數表示找到沒有使用的圖片是否要移動到指定的 back_not_used_dir 目錄