使用python找出iOS專案中沒有使用到的圖片資源

hi_xgb發表於2016-12-13

隨著版本迭代的進行,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(即單詞匹配),有結果就說明這個關鍵字有被使用到。這個方法會有幾個小問題,下文會提到。

(二)注意點

  1. 有兩個目錄需要特殊處理,/AppIcon.appiconset 和 /LaunchImage.launchimage,這是專案配置用到的圖片,用grep並不會被匹配到,因此這兩個目錄下的圖片資源要過濾掉,不需要被新增到匹配列表裡
  2. grep是根據關鍵字匹配,因此如果一張圖的名字是"message",grep有匹配到結果,這隻能說明專案裡有某個檔案包含"message"關鍵字,不一定是使用到了圖片,但反過來可以說明,如果沒有一個檔案中包含關鍵字,那麼也不可能作為圖片被使用
  3. 圖片名字用巨集定義,但是這個巨集又不再使用,這種情況是會認為有被使用而遺漏掉;或者是原來有使用但是註釋掉了,因為有出現關鍵字也會被遺漏掉
  4. 需要搜尋的檔案可以縮小範圍,指定字尾為".h"、".m"、".mm"、".xib"、".swift"、".storyboard"
  5. 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目錄

轉載請註明出處,有任何疑問都可聯絡我,歡迎探討。


最後做個推廣,歡迎關注公眾號 MrPeakTech,我從這裡學到很多,推薦給大家,共同進步~

使用python找出iOS專案中沒有使用到的圖片資源

相關文章