Как найти неиспользуемые изображения в проекте Xcode?


97

Есть ли у кого-нибудь однострочный поиск неиспользуемых изображений в проекте Xcode? (Предполагая, что все файлы упоминаются по имени в коде или файлах проекта - имена файлов не генерируются кодом.)

Эти файлы, как правило, накапливаются в течение жизни проекта, и может быть трудно определить, безопасно ли удалить любой конкретный png.


4
Это работает и для XCode4? Cmd-Opt-A в XCode4, кажется, открывает диалоговое окно «Добавить файлы».
Rajavanya Subramaniyan

Ответы:


61

Для файлов, которые не включены в проект, а просто находятся в папке, вы можете нажать

cmd ⌘+ alt ⌥+A

и они не будут выделены серым цветом.

Для файлов, на которые нет ссылок ни в xib, ни в коде, может работать что-то вроде этого:

#!/bin/sh
PROJ=`find . -name '*.xib' -o -name '*.[mh]'`

find . -iname '*.png' | while read png
do
    name=`basename $png`
    if ! grep -qhs "$name" "$PROJ"; then
        echo "$png is not referenced"
    fi
done

6
Если вы столкнулись с ошибкой: нет такого файла или каталога, вероятно, это связано с пробелами в пути к файлу. Кавычки нужно добавить в строку grep, так что если! grep -qhs "$ name" "$ PROJ";
Lukasz

8
Один сценарий, при котором это не сработает, - это когда мы можем загружать изображения программно после создания их имен. Например, arm1.png, arm2.png .... arm22.png. Я мог бы построить их имена в цикле for и load. Eg Games
Раджаванья Субраманиян

Если у вас есть изображения для дисплея Retina с именем @ 2x, они будут указаны как неиспользуемые. Вы можете избавиться от этого, добавив дополнительный оператор if: if [["$ name"! = @ 2x ]]; затем
Sten

3
Cmd + Opt + a, похоже, больше не работает в XCode 5. Что должно срабатывать?
powtac

cmd + opt + a, похоже, не выделяет файлы в Images.xcassets серым, хотя они являются частью проекта :(
tettoffensive

80

Это более надежное решение - оно проверяет любые ссылку на базовое имя в любом текстовом файле. Обратите внимание на приведенные выше решения, которые не включали файлы раскадровки (вполне понятно, в то время их не существовало).

Ack делает это довольно быстро, но есть некоторые очевидные оптимизации, которые необходимо сделать, если этот сценарий запускается часто. Этот код проверяет каждое базовое имя дважды, например, если у вас есть ресурсы сетчатки / не сетчатки.

#!/bin/bash

for i in `find . -name "*.png" -o -name "*.jpg"`; do 
    file=`basename -s .jpg "$i" | xargs basename -s .png | xargs basename -s @2x`
    result=`ack -i "$file"`
    if [ -z "$result" ]; then
        echo "$i"
    fi
done

# Ex: to remove from git
# for i in `./script/unused_images.sh`; do git rm "$i"; done

12
Установите Homebrew, а затем выполните brew install ack.
Марко

1
Спасибо. Этот ответ также правильно обрабатывает файлы и папки с пробелами.
djskinner

2
@Johnny, вам нужно сделать файл исполняемым ( chmod a+x FindUnusedImages.sh), а затем запустить его, как любую другую программу из bash./FindUnusedImages.sh
Майк Спрэг,

2
Я сделал модификацию, чтобы игнорировать файлы pbxproj (таким образом игнорируя файлы, которые находятся в проекте xcode, но не используются в коде или перьях / раскадровках): для result=`ack --ignore-file=match:/.\.pbxproj/ -i "$file"` этого требуется ack 2.0 и выше
Майк Спрэг

2
milanpanchal, вы можете поместить сценарий куда угодно и просто запустить его из любого каталога, который вы хотите использовать в качестве корня для поиска изображений (например, корневая папка вашего проекта). Вы можете поместить его, например, в ~ / script /, а затем перейти в корневую папку вашего проекта и запустить его, указав непосредственно на скрипт: ~ / script / unused_images.sh
Эрик ван дер Нейт

25

Пожалуйста, попробуйте LSUnusedResources .

На него сильно влияет Unused Джеффоднета , но, честно говоря, Unused работает очень медленно, и результаты не совсем правильные. Поэтому я сделал некоторую оптимизацию производительности, скорость поиска выше, чем у Unused.


2
Вау, это отличный инструмент! Намного лучше, чем пытаться запустить эти скрипты. Вы можете визуально увидеть все неиспользуемые изображения и удалить те, которые хотите. Одна проблема, которую я обнаружил, заключается в том, что она не поддерживает изображения, указанные в
списке

1
Определенно круто и спаси мне день! Лучшее решение в ветке. Ты жжешь.
Jakehao

2
Лучшее в ветке. Я хотел бы, чтобы это было выше, и я мог голосовать больше одного раза!
Йоав Шварц

Вы знаете, есть ли что-то подобное, но для обнаружения мертвого кода? Например, для методов, которые больше не вызываются (по крайней мере, больше не вызываются статически ).
superpuccio

24

Я попробовал решение Романа и добавил несколько настроек для обработки изображений сетчатки. Он работает хорошо, но помните, что имена изображений могут быть сгенерированы программно в коде, и этот скрипт неправильно перечислит эти изображения как не имеющие ссылок. Например, у вас может быть

NSString *imageName = [NSString stringWithFormat:@"image_%d.png", 1];

Этот сценарий неправильно будет думать, что на image_1.pngнего нет ссылок.

Вот модифицированный сценарий:

#!/bin/sh
PROJ=`find . -name '*.xib' -o -name '*.[mh]' -o -name '*.storyboard' -o -name '*.mm'`

for png in `find . -name '*.png'`
do
   name=`basename -s .png $png`
   name=`basename -s @2x $name`
   if ! grep -qhs "$name" "$PROJ"; then
        echo "$png"
   fi
done

что делает @ 2x в переключателе суффикса для базового имени?
ThaDon

3
К вашему сведению, папки с пробелами в имени вызывают проблемы со скриптом.
Стив

3
Если вы столкнулись с ошибкой: нет такого файла или каталога, вероятно, это связано с пробелами в пути к файлу. Кавычки нужно добавить в строку grep, так что если! grep -qhs "$ name" "$ PROJ";
Lukasz

3
Этот скрипт перечисляет все мои файлы
jjxtra

2
Я не знаю, почему это не работает для меня, он дает мне все изображения в формате png
Омер Обэйд

12

Может быть, вы можете попробовать стройную , делает приличную работу.

Обновление: с идеей emcmanus я пошел дальше и создал небольшую утилиту без подтверждения, чтобы избежать дополнительной настройки на машине.

https://github.com/arun80/xcodeutils


1
Slender - платное приложение. несколько ложных срабатываний и не подходят для коммерческих продуктов. сценарий, предоставленный emcmanus, действительно великолепен.
Arun

6

У меня работает только этот скрипт, который даже обрабатывает пространство в именах файлов:

редактировать

Обновлено для поддержки swiftфайлов и файлов cocoapod. По умолчанию он исключает каталог Pods и проверяет только файлы проекта. Чтобы запустить и проверить папку --podPods , запустите с attrbiute:

/.finunusedimages.sh --pod

Вот собственно сценарий:

#!/bin/sh

#varables
baseCmd="find ." 
attrs="-name '*.xib' -o -name '*.[mh]' -o -name '*.storyboard' -o -name '*.mm' -o -name '*.swift'"
excudePodFiles="-not \( -path  */Pods/* -prune \)"
imgPathes="find . -iname '*.png' -print0"


#finalize commands
if [ "$1" != "--pod" ]; then
    echo "Pod files excluded"
    attrs="$excudePodFiles $attrs"
    imgPathes="find . $excudePodFiles -iname '*.png' -print0"
fi

#select project files to check
projFiles=`eval "$baseCmd $attrs"`
echo "Looking for in files: $projFiles"

#check images
eval "$imgPathes" | while read -d $'\0' png
do
   name=`basename -s .png "$png"`
   name=`basename -s @2x $name`
   name=`basename -s @3x $name`

   if grep -qhs "$name" $projFiles; then
        echo "(used - $png)"
   else
        echo "!!!UNUSED - $png"
   fi
done

Этот сценарий пометил слишком много используемых ресурсов как неиспользуемых . Требуются улучшения.
Артем Шматков

Также не любит большие и глубокие иерархии проектов: ./findunused.sh: строка 28: / usr / bin / grep: Слишком длинный список аргументов
Мартин-Жиль Лавуа,

3

Я немного изменил отличный ответ @EdMcManus для обработки проектов, использующих каталоги активов.

#!/bin/bash

for i in `find . -name "*.imageset"`; do
    file=`basename -s .imageset "$i"`
    result=`ack -i "$file" --ignore-dir="*.xcassets"`
    if [ -z "$result" ]; then
        echo "$i"
    fi
done

На самом деле я не пишу сценарии bash, поэтому, если здесь нужно внести улучшения (вероятно), дайте мне знать в комментариях, и я обновлю его.


У меня проблема с пробелами в именах файлов. Я обнаружил, что полезно установить IFS = $ '\ n' непосредственно перед кодом (он устанавливает внутренний разделитель полей на новую строку) - не будет работать, если снова файлы будут иметь новые строки в имени.
Laura Calinoiu

2

Вы можете создать сценарий оболочки с grepисходным кодом и сравнить найденные изображения с папкой вашего проекта.

Здесь мужчина (а) для GREPиLS

Вы можете легко зациклить весь исходный файл, сохранить изображения в массиве или что-то подобное и использовать

cat file.m | grep [-V] myImage.png

С помощью этой уловки вы можете искать все изображения в исходном коде вашего проекта !!

надеюсь это поможет!


2

Я написал lua-скрипт, не уверен, что смогу им поделиться, потому что делал это на работе, но он работает хорошо. В основном это делается так:

Шаг первый - ссылки на статические изображения (легкий бит, покрытый другими ответами)

  • рекурсивно просматривает каталоги изображений и вытягивает имена изображений
  • удаляет имена изображений .png и @ 2x (не требуется / используется в imageNamed :)
  • текстовый поиск каждого имени изображения в исходных файлах (должно быть внутри строкового литерала)

Шаг второй - динамические ссылки на изображения (забавный момент)

  • извлекает список всех строковых литералов в источнике, содержащий спецификаторы формата (например,% @)
  • заменяет спецификаторы формата в этих строках регулярными выражениями (например, «foo% dbar» становится «foo [0-9] * bar»
  • текстовый поиск по именам изображений, используя эти строки регулярных выражений

Затем удаляет все, что не было найдено ни в одном поиске.

Крайний случай состоит в том, что имена изображений, поступающие с сервера, не обрабатываются. Чтобы справиться с этим, мы включаем в этот поиск код сервера.


Аккуратно. Из любопытства, есть ли какая-нибудь утилита для преобразования спецификаторов формата в регулярные выражения с подстановочными знаками? Просто подумайте, есть много сложностей, с которыми вам придется справиться, чтобы точно учесть все спецификаторы и платформы. (Документы по спецификатору формата)
Эд Макманус

2

Вы можете попробовать приложение FauxPas для Xcode . Это действительно хорошо для обнаружения недостающих изображений и множества других проблем / нарушений, связанных с проектом Xcode.


Похоже, он не обновлялся с Xcode 9. Можно подтвердить, что он не работает с Xcode 11.
Робин Догерти

2

Используя другие ответы, это хороший пример того, как игнорировать изображения в двух каталогах и не искать вхождения изображений в файлах pbxproj или xcassets (будьте осторожны со значком приложения и экранами-заставками). Измените * в --ignore-dir = *. Xcassets, чтобы он соответствовал вашему каталогу:

#!/bin/bash

for i in `find . -not \( -path ./Frameworks -prune \) -not \( -path ./Carthage -prune \) -not \( -path ./Pods -prune \) -name "*.png" -o -name "*.jpg"`; do 
    file=`basename -s .jpg "$i" | xargs basename -s .png | xargs basename -s @2x | xargs basename -s @3x`
    result=`ack -i --ignore-file=ext:pbxproj --ignore-dir=*.xcassets "$file"`
    if [ -z "$result" ]; then
        echo "$i"
    fi
done

2

Я использовал этот фреймворк: -

http://jeffhodnett.github.io/Unused/

Работает чертовски хорошо! Только 2 места, где я видел проблемы, - это когда имена изображений с сервера и когда имя ресурса изображения отличается от имени изображения внутри папки ресурсов ...


Это не ищет ресурсы, а только файлы изображений, на которые нет прямых ссылок. Если вы используете Assets должным образом, этот инструмент, к сожалению, вам не подойдет.
Робин Догерти,

0

Используйте http://jeffhodnett.github.io/Unused/, чтобы найти неиспользуемые изображения.


Мне кажется, что ни одно из этих приложений не обрабатывает достаточно места в именах папок. И это довольно медленно для одного из моих больших проектов.
Ingaham 01

0

Я создал сценарий python для идентификации неиспользуемых изображений: 'unused_assets.py' @ gist . Его можно использовать так:

python3 unused_assets.py '/Users/DevK/MyProject' '/Users/DevK/MyProject/MyProject/Assets/Assets.xcassets'

Вот несколько правил использования скрипта:

  • Важно передать путь к папке проекта в качестве первого аргумента, путь к папке с ресурсами в качестве второго аргумента.
  • Предполагается, что все изображения хранятся в папке Assets.xcassets и используются либо в файлах swift, либо в раскадровках.

Ограничения в первой версии:

  • Не работает для объективных файлов c

Я постараюсь со временем улучшить его, основываясь на отзывах, однако первая версия должна подойти большинству.

Пожалуйста, найдите код ниже. Код должен быть понятным, поскольку я добавил соответствующие комментарии к каждому важному шагу .

# Usage e.g.: python3 unused_assets.py '/Users/DevK/MyProject' '/Users/DevK/MyProject/MyProject/Assets/Assets.xcassets'
# It is important to pass project folder path as first argument, assets folder path as second argument
# It is assumed that all the images are maintained within Assets.xcassets folder and are used either within swift files or within storyboards

"""
@author = "Devarshi Kulshreshtha"
@copyright = "Copyright 2020, Devarshi Kulshreshtha"
@license = "GPL"
@version = "1.0.1"
@contact = "kulshreshtha.devarshi@gmail.com"
"""

import sys
import glob
from pathlib import Path
import mmap
import os
import time

# obtain start time
start = time.time()

arguments = sys.argv

# pass project folder path as argument 1
projectFolderPath = arguments[1].replace("\\", "") # replacing backslash with space
# pass assets folder path as argument 2
assetsPath = arguments[2].replace("\\", "") # replacing backslash with space

print(f"assetsPath: {assetsPath}")
print(f"projectFolderPath: {projectFolderPath}")

# obtain all assets / images 
# obtain paths for all assets

assetsSearchablePath = assetsPath + '/**/*.imageset'  #alternate way to append: fr"{assetsPath}/**/*.imageset"
print(f"assetsSearchablePath: {assetsSearchablePath}")

imagesNameCountDict = {} # empty dict to store image name as key and occurrence count
for imagesetPath in glob.glob(assetsSearchablePath, recursive=True):
    # storing the image name as encoded so that we save some time later during string search in file 
    encodedImageName = str.encode(Path(imagesetPath).stem)
    # initializing occurrence count as 0
    imagesNameCountDict[encodedImageName] = 0

print("Names of all assets obtained")

# search images in swift files
# obtain paths for all swift files

swiftFilesSearchablePath = projectFolderPath + '/**/*.swift' #alternate way to append: fr"{projectFolderPath}/**/*.swift"
print(f"swiftFilesSearchablePath: {swiftFilesSearchablePath}")

for swiftFilePath in glob.glob(swiftFilesSearchablePath, recursive=True):
    with open(swiftFilePath, 'rb', 0) as file, \
        mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ) as s:
        # search all the assests within the swift file
        for encodedImageName in imagesNameCountDict:
            # file search
            if s.find(encodedImageName) != -1:
                # updating occurrence count, if found 
                imagesNameCountDict[encodedImageName] += 1

print("Images searched in all swift files!")

# search images in storyboards
# obtain path for all storyboards

storyboardsSearchablePath = projectFolderPath + '/**/*.storyboard' #alternate way to append: fr"{projectFolderPath}/**/*.storyboard"
print(f"storyboardsSearchablePath: {storyboardsSearchablePath}")
for storyboardPath in glob.glob(storyboardsSearchablePath, recursive=True):
    with open(storyboardPath, 'rb', 0) as file, \
        mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ) as s:
        # search all the assests within the storyboard file
        for encodedImageName in imagesNameCountDict:
            # file search
            if s.find(encodedImageName) != -1:
                # updating occurrence count, if found
                imagesNameCountDict[encodedImageName] += 1

print("Images searched in all storyboard files!")
print("Here is the list of unused assets:")

# printing all image names, for which occurrence count is 0
print('\n'.join({encodedImageName.decode("utf-8", "strict") for encodedImageName, occurrenceCount in imagesNameCountDict.items() if occurrenceCount == 0}))

print(f"Done in {time.time() - start} seconds!")
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.