【问题标题】:How to find unused images in an Xcode project?如何在 Xcode 项目中找到未使用的图像?
【发布时间】:2011-09-01 01:52:27
【问题描述】:

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

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

【问题讨论】:

  • 这也适用于 XCode4 吗? XCode4 中的 Cmd-Opt-A 似乎打开了“添加文件”对话框。

标签: xcode assets


【解决方案1】:

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

GREPLS 的人在这里

您可以轻松地循环所有源文件,将图像保存在数组或其他类似的东西中并使用

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

有了这个技巧,你可以搜索项目源代码中的所有图片!!

希望这会有所帮助!

【讨论】:

    【解决方案2】:

    对于项目中没有包含的文件,只是在文件夹中闲逛,可以按

    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
    

    【讨论】:

    • 如果遇到错误:没有这样的文件或目录,可能是文件路径中的空格。引号需要在 grep 行中添加,所以它是:如果! grep -qhs "$name" "$PROJ";
    • 这种方法行不通的一种情况是,我们可能会在构建图像名称后以编程方式加载图像。像 arm1.png、arm2.png.... arm22.png。我可能会在 for 循环中构造它们的名称并加载。例如。游戏
    • 如果您有以@2x 命名的视网膜显示图像,它们将被列为未使用。你可以通过添加一个额外的 if 语句来摆脱它: if [[ "$name" != @2x ]];那么
    • Cmd+Opt+a 似乎不再适用于 XCode 5。它应该触发什么?
    • cmd+opt+a 似乎不会使 Images.xcassets 中的文件变灰,即使它们是项目的一部分:(
    【解决方案3】:

    我尝试了 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 在 basename 的后缀开关中有什么作用?
    • 仅供参考,名称中带有空格的文件夹会导致脚本出现问题。
    • 如果遇到错误:没有这样的文件或目录,可能是文件路径中的空格。引号需要在 grep 行中添加,所以它是:如果! grep -qhs "$name" "$PROJ";
    • 这个脚本列出了我所有的文件
    • 我不知道为什么它对我不起作用,它给了我所有的 png 图像
    【解决方案4】:

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

    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
    

    【讨论】:

    • 安装Homebrew,然后执行brew install ack
    • 谢谢。此答案还可以正确处理带有空格的文件和文件夹。
    • @Johnny,您需要使文件可执行 (chmod a+x FindUnusedImages.sh),然后像 bash ./FindUnusedImages.sh 中的任何其他程序一样运行它
    • 我进行了修改以忽略 pbxproj 文件(因此忽略了 xcode 项目中的文件,但未在代码或 nibs/storyboards 中使用):result=`ack --ignore-file=match:/.\.pbxproj/ -i "$file"` 这需要 ack 2.0 及更高版本
    • milanpanchal,您可以将脚本放在任何地方,然后从您想要用作搜索图像的根目录(例如您的项目根文件夹)执行它。例如,您可以将它放在 ~/script/ 中,然后转到您的项目根文件夹并通过直接指向脚本来运行它: ~/script/unused_images.sh
    【解决方案5】:

    我写了一个 lua 脚本,我不确定我是否可以分享它,因为我是在工作中完成的,但它运行良好。基本上它是这样做的:

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

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

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

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

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

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

    【讨论】:

    • 整洁。出于好奇,是否有一些实用程序可以将格式说明符转换为通配符正则表达式?只是想想你必须处理很多复杂性才能准确地适应所有说明符和平台。 (Format specifier docs)
    【解决方案6】:

    也许你可以试试slender,做得不错。

    更新:有了 emcmanus 的想法,我继续创建了一个没有 ack 的小工具,只是为了避免在机器上进行额外的设置。

    https://github.com/arun80/xcodeutils

    【讨论】:

    • Slender 是付费应用。几个误报,对商业产品不利。 emcmanus提供的脚本真的很棒。
    【解决方案7】:

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

    【讨论】:

    • 在我看来,这个应用程序都不能很好地处理文件夹名称中的空间。对于我的一个较大的项目来说,它的速度相当慢。
    【解决方案8】:

    我对@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 脚本,所以如果这里有改进(可能)让我知道在 cmets 中,我会更新它。

    【讨论】:

    • 我对文件名中的空格有疑问。我发现在代码之前设置' IFS=$'\n' ' 很有用(这个将内部字段分隔符设置为新行) - 如果再次文件名称中有新行,则将不起作用。
    【解决方案9】:

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

    编辑

    已更新以支持 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
    

    【讨论】:

    • 此脚本已将太多已使用资源标记为未使用。需要改进。
    • 也不喜欢大而深的项目层次结构:./findunused.sh: line 28: /usr/bin/grep: Argument list too long
    【解决方案10】:

    请尝试LSUnusedResources

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

    【讨论】:

    • 哇,这是一个很棒的工具!比尝试运行这些脚本要好得多。您可以直观地看到所有未使用的图像,并删除您想要的图像。我发现的一个问题是它不会拾取 plist 中引用的图像
    • 绝对棒极了,拯救我的一天!线程中的最佳解决方案。你摇滚。
    • 线程中最好的一个。我希望这是更高的,我可以投票不止一次!
    • 你知道是否有类似的东西,但用于死代码检测?例如,对于不再调用的方法(至少不再静态调用)。
    【解决方案11】:

    你可以试试FauxPas App for Xcode。发现丢失的图像以及与 Xcode 项目相关的许多其他问题/违规行为非常有用。

    【讨论】:

    • 看起来这个自 Xcode 9 以来没有更新。可以确认它不适用于 Xcode 11。
    【解决方案12】:

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

    【讨论】:

      【解决方案13】:

      我使用了这个框架:-

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

      效果很好!我看到的问题只有两个地方是图像名称来自服务器以及图像资产名称与资产文件夹中的图像名称不同时......

      【讨论】:

      • 这不查找资产,仅查找未直接引用的图像文件。如果您按照应有的方式使用资产,那么很遗憾,此工具不适合您。
      【解决方案14】:

      我创建了一个 python 脚本来识别未使用的图像:'unused_assets.py' @ gist。可以这样使用:

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

      这里有一些使用脚本的规则:

      • 将项目文件夹路径作为第一个参数传递很重要,资产 文件夹路径作为第二个参数
      • 假设所有图像都保存在 Assets.xcassets 文件夹中,并在 swift 文件或故事板中使用

      第一个版本的限制:

      • 不适用于目标 c 文件

      随着时间的推移,我会根据反馈尝试改进它,但是第一个版本应该对大多数人都有好处。

      请在下面找到代码。 代码应该是不言自明的,因为我在每个重要步骤中都添加了适当的 cmets

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-07-30
        • 2011-12-30
        相关资源
        最近更新 更多