Jak znaleźć nieużywane obrazy w projekcie Xcode?

97

Czy ktoś ma jedną linię, aby znaleźć nieużywane obrazy w projekcie Xcode? (Zakładając, że wszystkie pliki są przywoływane przez nazwę w kodzie lub w plikach projektu - brak nazw plików wygenerowanych przez kod).

Te pliki mają tendencję do gromadzenia się w czasie trwania projektu i trudno jest stwierdzić, czy można bezpiecznie usunąć dany plik png.

Paul Robinson
źródło
4
Czy to działa również dla XCode4? Cmd-Opt-A w XCode4 wydaje się otwierać okno dialogowe „Dodaj pliki”.
Rajavanya Subramaniyan

Odpowiedzi:

61

W przypadku plików, które nie są zawarte w projekcie, ale po prostu kręcą się w folderze, możesz nacisnąć

cmd ⌘+ alt ⌥+A

i nie będą wyszarzone.

W przypadku plików, do których nie ma odniesienia ani w xib, ani w kodzie, coś takiego może działać:

#!/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
rzymski
źródło
6
Jeśli wystąpi błąd: Brak takiego pliku lub katalogu, prawdopodobnie jest to spowodowane spacjami w ścieżce pliku. Cudzysłowy należy dodać w wierszu grep, więc brzmi: if! grep -qhs "$ nazwa" "$ PROJ";
Łukasz
8
Jednym ze scenariuszy, w którym to nie zadziała, jest programowe ładowanie obrazów po skonstruowaniu ich nazw. Podobnie jak arm1.png, arm2.png .... arm22.png. Mogę skonstruować ich nazwy w pętli for i load. Np. Games
Rajavanya Subramaniyan
Jeśli masz obrazy do wyświetlacza Retina o nazwie @ 2x, będą one wyświetlane jako nieużywane. Możesz się tego pozbyć, dodając dodatkową instrukcję if: if [["$ name"! = @ 2x ]]; potem
Sten
3
Cmd + Opt + a nie działa już na XCode 5. Co powinno wywołać?
powtac
cmd + opt + a nie wydaje się wyszarzać plików w Images.xcassets, mimo że są one częścią projektu :(
tettoffensive Kwietnia
80

Jest to bardziej niezawodne rozwiązanie - sprawdza wszelkie odniesienia do nazwy bazowej w dowolnym pliku tekstowym. Zwróć uwagę na powyższe rozwiązania, które nie obejmowały plików scenorysu (całkowicie zrozumiałe, nie istniały w tamtym czasie).

Ack sprawia, że ​​jest to dość szybkie, ale istnieje kilka oczywistych optymalizacji, które należy wprowadzić, jeśli ten skrypt działa często. Ten kod sprawdza dwukrotnie każdą nazwę basenową, jeśli na przykład masz oba zasoby siatkówkowe / inne niż siatkówkowe.

#!/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
Ed McManus
źródło
12
Zainstaluj Homebrew, a następnie wykonaj brew install ack.
Marko
1
Dzięki. Ta odpowiedź również poprawnie obsługuje pliki i foldery ze spacjami.
djskinner
2
@Johnny musisz uczynić plik wykonywalnym ( chmod a+x FindUnusedImages.sh), a następnie uruchomić go jak każdy inny program z basha./FindUnusedImages.sh
Mike Sprague
2
Dokonałem modyfikacji, aby ignorować pliki pbxproj (ignorując w ten sposób pliki, które są w projekcie xcode, ale nie są używane w kodzie ani w końcówkach / scenorysach): result=`ack --ignore-file=match:/.\.pbxproj/ -i "$file"` To wymaga potwierdzenia w wersji 2.0 i nowszej
Mike Sprague
2
milanpanchal, możesz umieścić skrypt w dowolnym miejscu i po prostu wykonać go z dowolnego katalogu, którego chcesz używać jako katalogu głównego do wyszukiwania obrazów (np. katalogu głównego projektu). Możesz na przykład umieścić go w ~ / script /, a następnie przejść do głównego folderu projektu i uruchomić go, wskazując bezpośrednio na skrypt: ~ / script / unused_images.sh
Erik van der Neut
25

Spróbuj LSUnusedResources .

Jest pod silnym wpływem Unused Jeffhodnetta , ale szczerze mówiąc, Unused jest bardzo powolny, a wyniki nie są całkowicie poprawne. Zrobiłem więc pewną optymalizację wydajności, prędkość wyszukiwania jest szybsza niż nieużywana.

LessFun
źródło
2
Wow, to świetne narzędzie! O wiele przyjemniejsze niż próba uruchomienia tych skryptów. Możesz wizualnie zobaczyć wszystkie nieużywane obrazy i usunąć te, które chcesz. Jedną rzeczą, którą znalazłem, jest to, że nie odbiera obrazów, do których odwołuje się
plik
1
Zdecydowanie niesamowite i uratuj mój dzień! Najlepsze rozwiązanie w wątku. Rządzisz.
Jakehao,
2
Najlepszy w wątku. Chciałbym, żeby to było wyżej i nie mogę głosować w górę więcej niż raz!
Yoav Schwartz
Czy wiesz, czy jest coś podobnego, ale do wykrywania martwego kodu? Na przykład dla metod, które nie są już wywoływane (przynajmniej nie są już wywoływane statycznie ).
superpuccio
24

Wypróbowałem rozwiązanie Romana i dodałem kilka poprawek do obsługi obrazów siatkówki. Działa dobrze, ale pamiętaj, że nazwy obrazów mogą być generowane programowo w kodzie, a ten skrypt nieprawidłowo wyświetlałby te obrazy jako bez odwołań. Na przykład możesz mieć

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

Ten skrypt błędnie pomyśli, że nie image_1.pngma odwołań.

Oto zmodyfikowany skrypt:

#!/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
obrabować
źródło
co robi @ 2x w przełączniku sufiksów dla basename?
ThaDon
3
FYI, foldery ze spacjami w nazwie powodują problemy ze skryptem.
Steve
3
Jeśli wystąpi błąd: Brak takiego pliku lub katalogu, prawdopodobnie jest to spowodowane spacjami w ścieżce pliku. Cudzysłowy należy dodać w wierszu grep, więc brzmi: if! grep -qhs "$ nazwa" "$ PROJ";
Łukasz
3
Ten skrypt zawiera listę wszystkich moich plików
jjxtra
2
nie wiem, dlaczego to nie działa dla mnie, daje mi wszystkie obrazy png
Omer Obaid
12

Może możesz spróbować smukły , wykonuje dobrą robotę.

aktualizacja: Z pomysłem emcmanus, poszedłem do przodu i stworzyłem małe narzędzie bez potwierdzenia, aby uniknąć dodatkowej konfiguracji w maszynie.

https://github.com/arun80/xcodeutils

Bieg
źródło
1
Slender to płatna aplikacja. kilka fałszywych alarmów i nie jest dobre dla produktów komercyjnych. skrypt dostarczony przez emcmanus jest naprawdę świetny.
Arun
6

Działa tylko ten skrypt, który obsługuje nawet spacje w nazwach plików:

Edytować

Zaktualizowano do obsługi swiftplików i cocoapod. Domyślnie wyklucza katalog Pods i sprawdza tylko pliki projektu. Aby uruchomić, aby sprawdzić również folder Pods, uruchom z --podattrbiute:

/.finunusedimages.sh --pod

Oto rzeczywisty skrypt:

#!/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
ingaham
źródło
Ten skrypt oznaczył zbyt wiele używanych zasobów jako nieużywanych . Potrzebne ulepszenia.
Artem Shmatkov
Nie lubi też dużych, głębokich hierarchii projektów: ./findunused.sh: wiersz 28: / usr / bin / grep: Lista argumentów jest za długa
Martin-Gilles Lavoie
3

Dokonałem niewielkiej modyfikacji doskonałej odpowiedzi udzielonej przez @EdMcManus, aby obsługiwać projekty wykorzystujące katalogi zasobów.

#!/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

Naprawdę nie piszę skryptów basha, więc jeśli są jakieś ulepszenia do wprowadzenia tutaj (prawdopodobnie), daj mi znać w komentarzach, a zaktualizuję to.

Stakenborg
źródło
Mam problem ze spacjami w nazwach plików. Dowiedziałem się, że przydatne jest ustawienie `` IFS = $ '\ n' '', tuż przed kodem (ten ustawia wewnętrzny separator pól na nową linię) - nie zadziała, jeśli ponownie pliki mają nowe linie w nazwie.
Laura Calinoiu,
2

Możesz stworzyć skrypt powłoki, który grepbędzie kodem źródłowym i porównać znalezione obrazy z folderem projektu.

Tutaj mężczyzna (y) dla GREPiLS

Z łatwością możesz zapętlić cały plik źródłowy, zapisywać obrazy w tablicy lub coś równego i używać

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

Dzięki tej sztuczce możesz przeszukiwać wszystkie obrazy w kodzie źródłowym projektu !!

mam nadzieję że to pomoże!

elp
źródło
2

Napisałem skrypt lua, nie jestem pewien, czy mogę go udostępnić, ponieważ zrobiłem to w pracy, ale działa dobrze. Zasadniczo robi to:

Krok pierwszy - odniesienia do statycznego obrazu (łatwy bit, objęty innymi odpowiedziami)

  • rekurencyjnie przegląda katalogi obrazów i wyciąga nazwy obrazów
  • usuwa nazwy obrazów z .png i @ 2x (nie jest wymagane / używane w imageNamed :)
  • tekstowo wyszukuje każdą nazwę obrazu w plikach źródłowych (musi znajdować się wewnątrz literału ciągu)

Krok drugi - dynamiczne odniesienia do obrazów (zabawny bit)

  • wyciąga listę wszystkich literałów łańcuchowych w źródle zawierającym specyfikatory formatu (np.% @)
  • zastępuje specyfikatory formatu w tych ciągach wyrażeniami regularnymi (np. „foo% dbar” staje się „foo [0-9] * bar”
  • tekstowo przeszukuje nazwy obrazów, używając tych ciągów regularnych

Następnie usuwa wszystko, czego nie znalazł w żadnym wyszukiwaniu.

Problemem skrajnym jest to, że nazwy obrazów pochodzące z serwera nie są obsługiwane. Aby sobie z tym poradzić, uwzględniamy kod serwera w tym wyszukiwaniu.

Sam
źródło
Schludny. Z ciekawości, czy jest jakieś narzędzie do przekształcania specyfikatorów formatu w wyrażenia regularne z symbolami wieloznacznymi? Wystarczy pomyśleć, że jest dużo złożoności, z którą musisz sobie poradzić, aby dokładnie dostosować wszystkie specyfikatory i platformy. (Dokumentacja formatu)
Ed McManus,
2

Możesz wypróbować aplikację FauxPas dla Xcode . Jest naprawdę dobry w ustalaniu brakujących obrazów i wielu innych problemów / naruszeń związanych z projektem Xcode.

Kunal Shah
źródło
Wygląda na to, że nie było to aktualizowane od czasu Xcode 9. Potwierdza, że ​​nie działa z Xcode 11.
Robin Daugherty
2

Korzystając z innych odpowiedzi, ta jest dobrym przykładem ignorowania obrazów w dwóch katalogach i nie przeszukiwania wystąpień obrazów w plikach pbxproj lub xcassets (uważaj na ikonę aplikacji i ekrany powitalne). Zmień * w --ignore-dir = *. Xcassets, aby pasował do twojego katalogu:

#!/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
Gabriel Madruga
źródło
2

Użyłem tego frameworka: -

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

Działa cholernie dobrze! Tylko 2 miejsca, w których widziałem problemy, dotyczą sytuacji, gdy nazwy obrazów pochodzą z serwera i gdy nazwa zasobu obrazu różni się od nazwy obrazu w folderze zasobów ...

Swasidhant
źródło
Nie dotyczy to zasobów, tylko plików graficznych, do których nie ma bezpośrednich odwołań. Jeśli korzystasz z zasobów tak, jak powinieneś, to narzędzie niestety nie zadziała.
Robin Daugherty
0

Użyj http://jeffhodnett.github.io/Unused/, aby znaleźć nieużywane obrazy.

Praveen Matanam
źródło
Wydaje mi się, że ta aplikacja nie obsługuje dobrze miejsca w nazwach folderów. I to dość wolno jak na jeden z moich większych projektów.
ingaham
0

Stworzyłem skrypt w Pythonie, aby zidentyfikować nieużywane obrazy: „unused_assets.py” @ gist . Można go używać w następujący sposób:

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

Oto kilka zasad korzystania ze skryptu:

  • Ważne jest, aby jako pierwszy argument podać ścieżkę folderu projektu, a jako drugi argument ścieżkę folderu zasobów
  • Zakłada się, że wszystkie obrazy są przechowywane w folderze Assets.xcassets i są używane w plikach Swift lub w scenorysach

Ograniczenia w pierwszej wersji:

  • Nie działa dla obiektywnych plików c

Z czasem postaram się to ulepszyć, na podstawie opinii zwrotnych, jednak pierwsza wersja powinna być dobra dla większości.

Poniżej kod. Kod powinien być zrozumiały, ponieważ dodałem odpowiednie komentarze do każdego ważnego kroku .

# 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!")
Devarshi
źródło