Xcode 프로젝트에서 사용하지 않는 이미지를 찾는 방법은 무엇입니까?


97

Xcode 프로젝트에서 사용하지 않는 이미지를 찾을 수있는 한 줄이 있습니까? (모든 파일이 코드 또는 프로젝트 파일의 이름으로 참조된다고 가정하면 코드 생성 파일 이름이 없습니다.)

이 파일은 프로젝트의 수명 동안 쌓이는 경향이 있으며 주어진 png를 삭제해도 안전한지 알기 어려울 수 있습니다.


4
XCode4에서도 작동합니까? XCode4의 Cmd-Opt-A가 "파일 추가"대화 상자를 여는 것 같습니다.
Rajavanya Subramaniyan 2013 년

답변:


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 줄에 따옴표를 추가해야하므로 다음과 같이됩니다. if! grep -qhs "$ name" "$ PROJ";
Lukasz

8
이것이 해결되지 않는 한 가지 시나리오는 이름을 구성한 후 프로그래밍 방식으로 이미지를로드 할 수있는 경우입니다. arm1.png, arm2.png .... arm22.png처럼. for 루프에서 이름을 구성하고로드 할 수 있습니다. Eg Games
Rajavanya Subramaniyan 2013 년

@ 2x라는 이름의 레티 나 디스플레이 용 이미지가있는 경우 사용되지 않은 것으로 표시됩니다. 추가 if 문을 추가하여 제거 할 수 있습니다. if [[ "$ name"! = @ 2x ]]; then
Sten

3
Cmd + Opt + a가 더 이상 XCode 5에서 작동하지 않는 것 같습니다. 무엇을 트리거해야합니까?
powtac 2014

cmd + opt + a는 프로젝트의 일부 임에도 불구하고 Images.xcassets의 파일을 회색으로 표시하지 않는 것 같습니다. (
tettoffensive

80

이보다 강력한 솔루션입니다 - 그것은으로 확인 모든 텍스트 파일의 기본 이름을 참조. 스토리 보드 파일을 포함하지 않은 위의 솔루션에 유의하십시오 (완전히 이해할 수 있지만 당시에는 존재하지 않았습니다).

Ack는 이것을 매우 빠르게 만들지 만,이 스크립트가 자주 실행된다면 몇 가지 명백한 최적화가 있습니다. 예를 들어이 코드는 retina / non-retina 자산이 모두있는 경우 모든 기본 이름을 두 번 확인합니다.

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

1
감사. 이 답변은 또한 공백이있는 파일과 폴더를 올바르게 처리합니다.
djskinner

2
당신이 할 필요가 @Johnny 파일 실행 파일 ( chmod a+x FindUnusedImages.sh), 다음 떠들썩한 파티에서 다른 프로그램처럼 실행./FindUnusedImages.sh
마이크 스프

2
pbxproj 파일을 무시하도록 수정했습니다 (따라서 xcode 프로젝트에 있지만 코드 또는 펜촉 / 스토리 보드에서 사용되지 않는 파일 무시) : result=`ack --ignore-file=match:/.\.pbxproj/ -i "$file"` 이것은 ack 2.0 이상이 필요합니다
Mike Sprague

2
milanpanchal의 경우 스크립트를 어디에나 배치 할 수 있으며 이미지 검색을위한 루트로 사용하려는 디렉토리 (예 : 프로젝트 루트 폴더)에서 실행하기 만하면됩니다. 예를 들어 ~ / script /에 넣은 다음 프로젝트 루트 폴더로 이동하여 스크립트를 직접 가리켜 실행할 수 있습니다. ~ / script / unused_images.sh
Erik van der Neut

25

LSUnusedResources를 사용해보십시오 .

jeffhodnett의 Unused에 크게 영향을 받지만 솔직히 Unused는 매우 느리고 결과가 완전히 정확하지는 않습니다. 그래서 성능을 최적화했습니다. 검색 속도가 미사용보다 빠릅니다.


2
와우 훌륭한 도구입니다! 이러한 스크립트를 실행하는 것보다 훨씬 좋습니다. 사용하지 않는 모든 이미지를 시각적으로 확인하고 원하는 이미지를 삭제할 수 있습니다. 그래도 발견 한 잡았다는 PLIST에서 참조 이미지를 선택하지 않는 것입니다
RyanG

1
확실히 굉장하고 내 하루를 구하십시오! 스레드에서 최고의 솔루션. 당신은 흔들립니다.
Jakehao apr

2
실에서 최고의 것. 나는 이것이 더 높았 으면 좋겠다. 그리고 나는 두 번 이상 투표 할 수 있었다!
Yoav Schwartz

이와 비슷한 것이 있지만 데드 코드 감지를위한 것이 있는지 알고 있습니까? 예를 들어, 더 이상 호출되지 않는 메서드의 경우 (적어도 더 이상 정적으로 호출 되지 않음 ).
superpuccio

24

나는 Roman의 솔루션을 시도하고 망막 이미지를 처리하기 위해 몇 가지 조정을 추가했습니다. 잘 작동하지만 이미지 이름은 코드에서 프로그래밍 방식으로 생성 될 수 있으며이 스크립트는 이러한 이미지를 참조되지 않은 것으로 잘못 나열합니다. 예를 들어,

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

뭐라고합니까 @ 2 배 기본 이름에 접미사 스위치를합니까?
ThaDon

3
참고로 이름에 공백이있는 폴더는 스크립트에 문제를 일으 킵니다.
스티브

3
오류가 발생하는 경우 : 해당 파일 또는 디렉토리가 없습니다. 파일 경로의 공백 때문일 수 있습니다. grep 줄에 따옴표를 추가해야하므로 다음과 같이됩니다. if! grep -qhs "$ name" "$ PROJ";
Lukasz

3
모든이 스크립트 목록 내 파일
jjxtra

2
나는 그 날 위해 그 날을주는 모든 PNG 이미지를 작동하지 않는 이유를 몰라
오메르 오바 이드에게

12

날씬한 것을 시도 하고 괜찮은 일을 할 수 있습니다 .

업데이트 : emcmanus 아이디어를 사용하여 컴퓨터에서 추가 설정을 피하기 위해 ack없이 작은 유틸리티를 만들었습니다.

https://github.com/arun80/xcodeutils


1
Slender는 유료 앱입니다. 여러 가지 오 탐지가 있으며 상업용 제품에는 좋지 않습니다. emcmanus에서 제공하는 스크립트는 정말 훌륭합니다.
Arun

6

이 스크립트만이 파일 이름의 공간을 처리하는 나를 위해 작동합니다.

편집하다

지원에 업데이트 swift파일과 cocoapod. 기본적으로 Pod 디렉터리를 제외하고 프로젝트 파일 만 확인합니다. 실행하여 Pods 폴더도 확인하려면 --podattrbiute로 실행 하십시오.

/.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

이 스크립트는 너무 많은 사용 된 리소스를 미사용으로 표시했습니다 . 개선이 필요합니다.
아르 템 Shmatkov

./findunused.sh : 또한 크지 깊은 프로젝트 계층 구조처럼 않습니다 라인 28 :는 / usr / 빈 / 그렙 : 인수 목록이 너무 오래
마틴 - 질 라보

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소스 코드를 작성하고 발견 된 이미지를 프로젝트 폴더와 비교할 수있는 셸 스크립트를 만들 수 있습니다 .

여기에 대한 남자 GREPLS

쉽게 모든 소스 파일을 반복하고, 이미지를 배열로 저장하거나 동일하게 사용할 수 있습니다.

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

이 트릭을 사용하면 프로젝트 소스 코드의 모든 이미지를 검색 할 수 있습니다 !!

도움이 되었기를 바랍니다!


2

루아 스크립트를 작성했는데 직장에서했기 때문에 공유 할 수 있을지 모르겠지만 잘 작동합니다. 기본적으로 다음을 수행합니다.

1 단계-정적 이미지 참조 (다른 답변에서 다루는 쉬운 부분)

  • 이미지 디렉토리를 재귀 적으로 살펴보고 이미지 이름을 가져옵니다.
  • .png 및 @ 2x의 이미지 이름을 제거합니다 (imageNamed :에서 필수 / 사용되지 않음).
  • 소스 파일에서 각 이미지 이름을 텍스트로 검색합니다 (문자열 리터럴 내에 있어야 함).

2 단계-동적 이미지 참조 (재미있는 부분)

  • 형식 지정자를 포함하는 소스의 모든 문자열 리터럴 목록을 가져옵니다 (예 : % @).
  • 이 문자열의 형식 지정자를 정규식으로 대체합니다 (예 : "foo % dbar"는 "foo [0-9] * bar"가됩니다.)
  • 이러한 정규식 문자열을 사용하여 이미지 이름을 텍스트로 검색합니다.

그런 다음 두 검색에서 찾지 못한 모든 것을 삭제합니다.

가장 중요한 경우는 서버에서 가져온 이미지 이름이 처리되지 않는다는 것입니다. 이를 처리하기 위해이 검색에 서버 코드를 포함합니다.


산뜻한. 호기심에서 형식 지정자를 와일드 카드 정규식으로 변환하는 유틸리티가 있습니까? 모든 지정자와 플랫폼을 정확하게 수용하기 위해 처리해야 할 복잡성이 많다고 생각하면됩니다. (형식 지정자 문서)
에드 맥 마누스

2

Xcode 용 FauxPas 앱을 사용해 볼 수 있습니다 . Xcode 프로젝트와 관련된 누락 된 이미지 및 기타 많은 문제 / 위반을 찾는 데 정말 좋습니다.


Xcode 9 이후로 업데이트되지 않은 것 같습니다. Xcode 11에서 작동하지 않음을 확인할 수 있습니다.
Robin Daugherty

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/

잘 작동합니다! 내가 문제를 본 두 곳은 이미지 이름이 서버에서 왔고 이미지 자산 이름이 자산 폴더 내의 이미지 이름과 다른 경우입니다.


이것은 자산을 찾지 않고 직접 참조되지 않은 이미지 파일 만 찾습니다. 원하는대로 자산을 사용하는 경우이 도구는 안타깝게도 작동하지 않습니다.
Robin Daugherty


0

사용하지 않는 이미지를 식별하기 위해 파이썬 스크립트를 만들었습니다 : 'unused_assets.py'@ gist . 다음과 같이 사용할 수 있습니다.

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

다음은 스크립트를 사용하는 몇 가지 규칙입니다.

  • 프로젝트 폴더 경로를 첫 번째 인수로 전달하고 자산 폴더 경로를 두 번째 인수로 전달하는 것이 중요합니다.
  • 모든 이미지는 Assets.xcassets 폴더 내에서 유지되며 신속한 파일 또는 스토리 보드 내에서 사용된다고 가정합니다.

첫 번째 버전의 제한 사항 :

  • 객관적인 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!")

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.