从磁盘加载许多 UIImages 阻塞主线程

Loading many UIImages from disk blocks main thread

我有一组本地 UIImage,当它们各自的单元格被点击时,我需要按顺序加载和显示它们。例如,我有 20 张热狗图像组合在一起形成动画。当用户点击热狗单元格时,该单元格的 UIImageView 应该会激活图像。

我知道如何使用UIImageViewanimationImages来实现动画。我的问题是从磁盘中检索所有这些图像需要大约 1.5 秒并阻塞主线程。

我可以在 application(_:didFinishLaunchingWithOptions:) 中实例化一个帮助程序 class,它在后台线程上从磁盘加载这些图像,以便它们在需要时位于内存中,但这看起来很老套。

有没有更好的方法可以从磁盘快速加载许多图像?


编辑:这些图像是插图,因此是 .png。

Edit2:假设每个图像序列的总和为1 MB。我正在测试的图像尺寸比 UIImageView 的 @3x 要求大 33-60%。我 正在 等待确认最终 UIImageView 大小,然后再从我们的设计师那里获得正确的图像集,因此使用适当大小的资产应该会大大缩短时间,但我也在测试物理 iPhone X.

class ViewModel {

    func getImages() -> [UIImage] {

        var images: [UIImage] = []

        for i in 0..<44 {
            if let image = UIImage(named: "hotDog\(i).png") {
                images.append(image)
            }
        }

        return images

    }
}

class ViewController: UIViewController {

    private var viewModel: ViewModel!

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        let cell = tableView.cellForRow(at: indexPath) as! CustomCell
        let images = viewModel.getImages()
        cell.animateImageView(withImages: images)

    }
}

class CustomCell: UITableViewCell {

    @IBOutlet weak var imageView: UIImageView!

    func animateImageView(withImages images: [UIImage]) {

        imageView.image = images.last
        imageView.animationImages = images
        imageView.animationDuration = TimeInterval(images.count / 20)
        imageView.animationRepeatCount = 1
        imageView.startAnimating()

    }
}

我建议您尝试 UIImage(contentsOfFile:) 而不是 UIImage(named:)。在我的快速测试中,发现它的速度快了一个数量级以上。它有点好理解,因为它做了更多(搜索资产、缓存资产等)。

// slow

@IBAction func didTapNamed(_ sender: Any) {
    let start = CFAbsoluteTimeGetCurrent()
    imageView.animationImages = (0 ..< 20).map {
        UIImage(named: filename(for: [=10=]))!
    }
    imageView.animationDuration = 1.0
    imageView.animationRepeatCount = 1
    imageView.startAnimating()

    print(CFAbsoluteTimeGetCurrent() - start)
}

// faster

@IBAction func didTapBundle(_ sender: Any) {
    let start = CFAbsoluteTimeGetCurrent()
    let url = Bundle.main.resourceURL!
    imageView.animationImages = (0 ..< 20).map {
        UIImage(contentsOfFile: url.appendingPathComponent(filename(for: [=10=])).path)!
    }
    imageView.animationDuration = 1.0
    imageView.animationRepeatCount = 1
    imageView.startAnimating()

    print(CFAbsoluteTimeGetCurrent() - start)
}

注意,这假定您在资源目录中有文件,您可能必须根据它们在项目中的位置相应地修改它。另请注意,我避免在循环内执行 Bundle.main.url(forResource:withExtension:),因为即使那样也会对性能产生明显的影响。