如何在Xcode项目中查找未使用的图像?


97

有没有人一行可以在Xcode项目中查找未使用的图像?(假设所有文件均按名称在代码或项目文件中引用-没有代码生成的文件名。)

这些文件往往会在项目的整个生命周期内积累起来,很难确定删除任何给定的png是否安全。


4
这对XCode4也有效吗?XCode4中的Cmd-Opt-A似乎打开“添加文件”对话框。
Rajavanya Subramaniyan

Answers:


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”;
卢卡斯2012年

8
无法解决这种情况的一种情况是,我们可以在构造图像名称后以编程方式加载图像。就像arm1.png,arm2.png .... arm22.png。我可能会在for循环和加载中构造它们的名称。例如游戏
Rajavanya Subramaniyan

如果您有用于@ 2x的视网膜显示图像,它们将被列为未使用。您可以通过添加一个额外的if语句来消除该问题:if [[“ $ name”!= @ 2x ]]; 然后
Sten

3
Cmd + Opt + a似乎不再适用于XCode5。它将触发什么?
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
Marko

1
谢谢。此答案还可以正确处理带有空格的文件和文件夹。
djskinner 2012年

2
@Johnny,您需要将文件设置为可执行文件(chmod a+x FindUnusedImages.sh),然后像从bash中运行其他程序一样运行它./FindUnusedImages.sh
Mike Sprague 2013年

2
我已经进行了修改,以忽略pbxproj文件(从而忽略了xcode项目中但未在代码或nibs / storyboard中使用的文件): 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的“ 未使用”的严重影响,但老实说,“未使用”的速度很慢,结果也不完全正确。因此,我进行了一些性能优化,搜索速度比未使用的要快。


2
哇,这是个很棒的工具!比尝试运行这些脚本好得多。您可以直观地查看所有未使用的图像,并删除所需的图像。我发现的一个陷阱是,它没有拾取plist中引用的图像
RyanG

1
绝对棒,拯救我的一天!线程中的最佳解决方案。你摇滚
Jakehao '16

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

什么是@ 2X的后缀开关基本名称吗?
ThaDon 2011年

3
仅供参考,名称中带有空格的文件夹会导致脚本出现问题。
2012年

3
如果遇到错误:没有此类文件或目录,则可能是由于文件路径中的空格所致。引号需要在grep行中添加,所以去了:if!grep -qhs“ $ name”“ $ PROJ”;
卢卡斯2012年

3
这个脚本列出了我所有的文件
jjxtra 2013年

2
我不知道为什么它对我不起作用,它给了我所有的png图像
Omer Obaid 2014年


6

只有这个脚本对我有用,它甚至可以处理文件名中的空格:

编辑

已更新以支持swift文件和cocoapod。默认情况下,它不包含Pods目录,并且仅检查项目文件。要同时检查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

该脚本将太多的已使用资源标记为未使用。需要改进。
Artem Shmatkov

也不太喜欢大型的项目层次结构:./findunused.sh:第28行:/ usr / bin / grep:参数列表过长
Martin-Gilles Lavoie

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'`很有用(此代码将内部字段分隔符设置为新行)-如果文件名中再次包含新行,则将不起作用。
劳拉卡利诺乌

2

您可以制作一个包含grep源代码的Shell脚本,并将创建的图像与项目文件夹进行比较。

这里的人(个),GREPLS

您可以轻松地循环播放所有源文件,将图像保存在数组中或等同的形式并使用

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

使用此技巧,您可以搜索项目源代码中的所有图像!

希望这可以帮助!


2

我写了一个lua脚本,我不确定是否可以共享它,因为我在工作中就做到了,但是效果很好。基本上,它是这样做的:

步骤一-静态图像引用(简单易用,其他答案涵盖)

  • 递归地浏览图像目录并提取图像名称
  • 去除.png和@ 2x的图像名称(在imageNamed中不需要/使用):
  • 以文本方式搜索源文件中的每个图像名称(必须在字符串文字中)

第二步-动态图像引用(有趣的地方)

  • 提取包含格式说明符(例如,%@)的源中所有字符串文字的列表
  • 用正则表达式替换这些字符串中的格式说明符(例如,“ foo%dbar”变为“ foo [0-9] * bar”
  • 使用这些正则表达式字符串在图像名称中进行文本搜索

然后删除在任一搜索中未找到的内容。

最极端的情况是不处理来自服务器的图像名称。为了处理此问题,我们在此搜索中包括服务器代码。


整齐。出于好奇,是否有一些实用程序可以将格式说明符转换为通配符正则表达式?只是认为您必须处理很多复杂性才能准确适应所有说明符和平台。(格式说明文档)
Ed McManus 2013年


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/未使用/

很好该死!当图像名称来自服务器并且图像资产名称与资产文件夹中的图像名称不同时,我只看到两个问题。


这不查找资产,仅查找未直接引用的图像文件。如果您以应有的方式使用资产,那么很遗憾,此工具将无法使用。
罗宾·多尔蒂


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!")

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.