Loading many UIImages from disk blocks main thread

2019-09-21 00:39发布

I have a group of local UIImages that I need to load and present in successive order when their respective cell is tapped. For example, I have 20 images of a hot dog that combine to form an animation. When the user taps the hot dog cell, the cell's UIImageView should animate the images.

I know how to use UIImageView's animationImages to achieve the animation. My problem is that retrieving all of these images from disk takes ~1.5 seconds and blocks the main thread.

I could instantiate a helper class in application(_:didFinishLaunchingWithOptions:) that loads these images from disk on a background thread so that they'll be in memory when needed, but this seems hacky.

Are there any better ways of quickly loading many images from disk?


Edit: These images are illustrations and thus are .png.

Edit2: Assume the sum of each image sequence is 1 MB. The image dimensions I'm testing with are 33-60% larger than the UIImageView's @3x requirements. I am waiting to confirm final UIImageView size before getting correct image sets from our designers, so the time should be cut significantly with properly sized assets, but I'm also testing on a physical 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()

    }
}

1条回答
神经病院院长
2楼-- · 2019-09-21 01:17

I would suggest you try UIImage(contentsOfFile:) instead of UIImage(named:). In my quick test and found it to be more than an order of magnitude faster. It's somewhat understandable because it's doing a lot more (searching for the asset, cacheing the asset, etc.).

// slow

@IBAction func didTapNamed(_ sender: Any) {
    let start = CFAbsoluteTimeGetCurrent()
    imageView.animationImages = (0 ..< 20).map {
        UIImage(named: filename(for: $0))!
    }
    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: $0)).path)!
    }
    imageView.animationDuration = 1.0
    imageView.animationRepeatCount = 1
    imageView.startAnimating()

    print(CFAbsoluteTimeGetCurrent() - start)
}

Note, this presumes that you had the files in the resource directory, and you may have to modify this accordingly depending upon where they are in your project. Also note that I avoided doing Bundle.main.url(forResource:withExtension:) within the loop, because even that had an observable impact on performance.

查看更多
登录 后发表回答