当UIImageView
加载了一张图片之后,如果再加载另外一张图片,然后立即删除第二张图片。则UIImageView
会变黑。
演示代码如下:
import UIKit
class ViewController: UIViewController {
lazy var image = { () -> UIImage? in
guard let path = Bundle.main.path(forResource: "poster_icon_mac_1024", ofType: "png") else {
return nil
}
return UIImage(contentsOfFile: path)
}()
lazy var replaceImage = { () -> UIImage? in
guard let path = Bundle.main.path(forResource: "Miss Devil 恶魔人事·椿真子.Miss.Devil.Jinji.no.Akuma.Tsubaki.Mako.Ep05.Chi_Jap.HDTVrip.1280X720-0001", ofType: "png") else {
return nil
}
return UIImage(contentsOfFile: path)
}()
lazy var saveImageURL = { () -> URL in
let baseURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first
let fileName = UUID().uuidString + ".jpg"
return URL(fileURLWithPath: fileName, isDirectory: false, relativeTo: baseURL)
}()
var latestImage:UIImage? = nil
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
addImage()
DispatchQueue.main.asyncAfter(wallDeadline: .now() + .seconds(2)) { [unowned self] in
self.createImage()
self.loadImage()
self.changeImageViewToLatestImage()
self.removeLatestImage()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
@IBOutlet weak var imageView: UIImageView!
func addImage() {
imageView.image = image
}
func changeImage() {
imageView.image = replaceImage
}
func createImage() {
let imageData = UIImageJPEGRepresentation(replaceImage!, 1.0)
FileManager.default.createFile(atPath: saveImageURL.path, contents: imageData!, attributes: nil)
}
func loadImage() {
latestImage = UIImage(contentsOfFile: saveImageURL.path)
}
func changeImageViewToLatestImage() {
imageView.image = latestImage
}
func removeLatestImage() {
try! FileManager.default.removeItem(at: saveImageURL)
}
}
分析与解决
这个问题的造成,是因为为了性能,UIImageView
其实是异步加载的。在它加载完成之前,如果图片被删除了,就会加载不到,变黑。
有人提出,可以将图片通过Data
类型,加载到内存中,然后将内存中的图片加载到UIImageView
的方式,来绕过这个问题。这个思路的确能解决这个问题,但是毕竟多占用了内存。不算是最好的方式。
其实,我们知道了原理,就等UIImageView
加载完成之后,再删除图片就好了。虽然苹果并没有告诉我们图片何时加载好。但是我们知道,类似更新UI的这种操作,必然是在UI线程完成的,即main线程
。那么我们只需要在main队列
排队一下就可以了。
将代码
DispatchQueue.main.asyncAfter(wallDeadline: .now() + .seconds(2)) { [unowned self] in
self.createImage()
self.loadImage()
self.changeImageViewToLatestImage()
self.removeLatestImage()
}
改成
DispatchQueue.main.asyncAfter(wallDeadline: .now() + .seconds(2)) { [unowned self] in
self.createImage()
self.loadImage()
self.changeImageViewToLatestImage()
DispatchQueue.main.async { [unowned self] in
self.removeLatestImage()
}
}
思考
为什么上面的代码会成功呢?这是因为第一段代码的运行顺序是
DispatchQueue.main.asyncAfter(wallDeadline: .now() + .seconds(2)) { [unowned self] in
self.createImage()
self.loadImage()
self.changeImageViewToLatestImage() // 异步操作,押后运行
self.removeLatestImage() // 先删除了图片,之后才进行的图片加载
}
而修改之后的代码是
DispatchQueue.main.asyncAfter(wallDeadline: .now() + .seconds(2)) { [unowned self] in
self.createImage()
self.loadImage()
self.changeImageViewToLatestImage() // 异步操作1,押后运行
DispatchQueue.main.async { [unowned self] in // // 异步操作2,押后运行
self.removeLatestImage()
}
}
由于main队列
是一个包执行完成之后,才会执行下一个。因此实际上修改后的代码是在图片加载完成之后才删除图片的。这就避免了这个问题。