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

时间:2011-05-24 15:55:54

标签: xcode assets

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

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

14 个答案:

答案 0 :(得分:80)

这是一个更强大的解决方案 - 它会检查对任何文本文件中的basename的任何引用。请注意上面没有包含故事板文件的解决方案(完全可以理解,当时它们不存在)。

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

答案 1 :(得分:58)

对于未包含在项目中但仅在文件夹中挂起的文件,您可以按

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

答案 2 :(得分: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

答案 3 :(得分:23)

请试试LSUnusedResources

它深受jeffhodnett Unused的影响,但老实说Unused非常慢,结果并不完全正确。所以我做了一些性能优化,搜索速度比Unused更快。

答案 4 :(得分:12)

可能你可以试试slender,做得不错。

更新:有了emcmanus的想法,我继续创建一个没有确认的小型工具,只是为了避免机器中的其他设置。

https://github.com/arun80/xcodeutils

答案 5 :(得分:6)

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

修改

已更新为支持swift个文件和cocoapod。默认情况下,它不包括Pods目录并仅检查项目文件。要运行以检查Pods文件夹,请使用--pod 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

答案 6 :(得分:2)

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

此处为GREPLS

的人

您可以轻松循环所有源文件,将图像保存在数组中或等同于使用

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

通过这个技巧,您可以搜索项目源代码中的所有图像!!

希望这有帮助!

答案 7 :(得分:2)

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

第一步 - 静态图片参考(简单的一点,由其他答案涵盖)

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

第二步 - 动态图片参考(有趣的一点)

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

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

边缘情况是不处理来自服务器的图像名称。为了解决这个问题,我们在此搜索中包含了服务器代码。

答案 8 :(得分:2)

我对@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脚本,所以如果在这里有改进(可能)请在评论中告诉我,我会更新它。

答案 9 :(得分:2)

您可以尝试FauxPas App for Xcode。发现丢失的图像以及与Xcode项目相关的许多其他问题/违规行为确实很好。

答案 10 :(得分:2)

我使用了这个框架: -

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

作品该死的!我看到问题的只有2个地方是图像名称来自服务器,而且图像资产名称与资产文件夹内的图像名称不同...

答案 11 :(得分:1)

使用其他答案,这个是一个很好的例子,说明如何忽略两个目录上的图像,而不是在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

答案 12 :(得分:0)

使用http://jeffhodnett.github.io/Unused/查找未使用的图像。

答案 13 :(得分:0)

我创建了一个python脚本来标识未使用的图像:'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!")
相关问题