在应用中本地存储大量图像的正确方法

时间:2018-08-14 11:41:15

标签: ios arrays swift

我正在尝试找到最方便,最正确的方法,将大量图像本地存储在设备上,并在需要时在屏幕上显示它们。

到目前为止,我所有的图像和图标都放置在Assets文件夹中,并且我还创建了一个单独的swift文件,在其中创建了数组,根据图像类型将它们分为适当的数组。大约30个数组。如下所示:

let instrumentImages = [UIImage(named: "accordion")!, UIImage(named: "drums")!, UIImage(named: "electricGuitar")!, UIImage(named: "flute")!, UIImage(named: "harp")!, UIImage(named: "maracas")!, UIImage(named: "piano")!, UIImage(named: "guitar")!, UIImage(named: "saxophone")!, UIImage(named: "trumpet")!, UIImage(named: "violin")!, UIImage(named: "tuba")!]

选择图像图标后,我会将实际图像传递到“细节”视图控制器,在该控制器中屏幕上显示图像,如下所示:

let mainStoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
        let destVC = mainStoryboard.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
        destVC.image = ImageModel().instrumentImages[indexPath.row]

        self.navigationController?.pushViewController(destVC, animated: true)

一切都非常简单并且可以完美运行,但是当我说我拥有大量图像时,说100多个应用程序开始运行非常缓慢。

所以我的问题是,在我的情况下,存储如此大量的图像并在需要时将它们显示在屏幕上的最佳方法是什么?

2 个答案:

答案 0 :(得分:1)

我会说问题出在这里:

destVC.image = ImageModel().instrumentImages[indexPath.row]

每次要加载单个图像时,都会创建一个ImageModel实例,该实例(我想是)将加载所有图像,并仅返回一个,即您需要的一个。这是非常低效的,加载所有图像,仅获得一个图像并丢弃所有其余图像是没有意义的。

如果您坚持要预加载图像,则只需加载一次并将其存储在ImageModel的静态实例中,就像这样

class ImageModel
{
    static let sharedInstance = ImageModel()

    init()
    {
        // load images here
    }
}

然后像这样检索图像

destVC.image = ImageModel.sharedInstance.instrumentImages[indexPath.row]

这样,您将在共享ImageModel实例的构造函数中仅加载一次所有图像。

但是,我认为将所有图像加载到内存中没有意义-在您的ImageModel类中,您应该仅保留图像文件名,并且仅在以下情况下加载它们:必要

class ImageModel 
{
    let instrumentImages = ["accordion", "drums", ... ]

    static let imageWithIndex(_ index : Int) -> UIImage?
    {
        return UIImage(named: instrumentImages[index])
    }
}

并这样称呼

destVC.image = ImageModel.imageWithIndex(indexPath.row)

在预加载图像中毫无用处,甚至可能没有使用,因为大量的高清图像可能会耗尽您的应用程序的内存。

答案 1 :(得分:0)

使用图像加载器可以取消存档所有图像,只是为了将一个图像传递给另一个视图控制器。正如其他答案所言,这是低效的。

我在这些情况下使用的解决方案是使用以下方法将资产的定义与实际资产分开:

/// A wrapper around an image asset in the catalogue that can be return an image using `UIImage(named:in:compatibleWith)` with `self`
public struct ImageAsset: Equatable {
    private let named: String
    private let bundle: Bundle
    private let compatibleWith: UITraitCollection?


    /// Initialise the wrapper with the properties required to call UIImage(named:in:compatibleWith)`
    ///
    /// - Parameters:
    ///   - named: The name of the asset
    ///   - bundle: The bundle for the asset catalogue - defaults to `Bundle.main`
    ///   - compatibleWith: The UITraitCollection - defaults to `nil`
    public init(named: String, bundle: Bundle = Bundle.main, compatibleWith: UITraitCollection? = nil) {
        self.named = named
        self.bundle = bundle
        self.compatibleWith = compatibleWith
    }

    // the result of calling `UIImage(named:in:compatibleWith)` with `self`
    public func image() -> UIImage? {
        return UIImage(named: named, in: bundle, compatibleWith: compatibleWith)
    }
}

extension ImageAsset: ExpressibleByStringLiteral {
    // Convenient extension for creating the most common values from string literals
    public init(stringLiteral value: String) {
        self.init(named: value)
    }
}

这只是一个结构,您可以根据需要从中提取图像,该图像使用的内存比实际图像数组少得多。现在,您可以定义一个ImageAssets数组,而不是图像数组(通过ExpressibleByStringLiteral的魔力):

let instrumentImages: [ImageAsset] = ["accordion", "drums", "electricGuitar, "flute"]

您可以使用以下方法将其传递给视图控制器:

destVC.image = ImageModel().instrumentImages[indexPath.row].image()!

为方便起见,您想为图像命名空间,可以将ImageModel()重写为枚举,而没有返回图像列表的情况:

enum ImageModel {
    static var instrumentImages: [ImageAssets] = ["accordion", "drums", "electricGuitar, "flute"]
}

然后按顺序将其传递给ViewConroller,如下所示:

destVC.image = ImageModel.instrumentImages[indexPath.row].image()!

更高效的是,您的视图控制器使用UIImage,可以使其使用ImageAsset:

destVC.imageAsset = ImageModel.instrumentImages[indexpath.row]

然后destVC可以在需要实际获取图像时在此.image()上调用imageAsset