在Swift 4中向可解码协议添加属性观测器
我正在从JSON解析中检索URL,在这样做之后,我希望将该URL转换为UIImage。这是我当前的数据结构:在Swift 4中向可解码协议添加属性观测器,swift,decodable,Swift,Decodable,我正在从JSON解析中检索URL,在这样做之后,我希望将该URL转换为UIImage。这是我当前的数据结构: struct Book: Decodable { let author: String let artworkURL: URL let genres: [Genre] let name: String let releaseDate: String } 我试着做: struct Book: Decodable { let author:
struct Book: Decodable {
let author: String
let artworkURL: URL
let genres: [Genre]
let name: String
let releaseDate: String
}
我试着做:
struct Book: Decodable {
let author: String
var bookArtwork: UIImage
let artworkURL: URL {
didSet {
do {
if let imageData: Data = try Data(contentsOf: artworkURL) {
bookArtwork = UIImage(data: imageData)!
}
} catch {
print(error)
}
}
let genres: [Genre]
let name: String
let releaseDate: String
}
但该不符合协议“可解码”
还有其他人有解决方案吗?下面我将列举几种处理这种情况的方法,但正确的答案是,在解析JSON的过程中,您不应该检索图像。而且您绝对不应该同步执行此操作(这就是
数据(contentsOf:)
所做的)
相反,您应该只检索UI需要的图像。并且您希望将图像检索到一些可以在内存不足的情况下清除的内容中,以响应.uiapplicationidreceivememorywarning
系统通知。总之,如果您不小心,并且几乎总是希望将图像与模型对象本身解耦,那么图像可能会占用大量内存
但是,如果您下定决心要将图像合并到
书籍
对象中(我还是建议您不要这样做),您可以:
bookArtwork
设置为lazy
变量:
struct Book: Decodable {
let author: String
lazy var bookArtwork: UIImage = {
let data = try! Data(contentsOf: artworkURL)
return UIImage(data: data)!
}()
let artworkURL: URL
let genres: [Genre]
let name: String
let releaseDate: String
}
在这种情况下,这是一种可怕的模式(同样,因为我们永远不应该进行同步网络调用),但它说明了惰性
变量的概念。有时也会使用计算属性执行这种模式init
方法:
struct Book: Decodable {
let author: String
let bookArtwork: UIImage
let artworkURL: URL
let genres: [Genre]
let name: String
let releaseDate: String
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
author = try values.decode(String.self, forKey: .author)
artworkURL = try values.decode(URL.self, forKey: .artworkURL)
genres = try values.decode([Genre].self, forKey: .genres)
name = try values.decode(String.self, forKey: .name)
releaseDate = try values.decode(String.self, forKey: .releaseDate)
// special processing for artworkURL
let data = try Data(contentsOf: artworkURL)
bookArtwork = UIImage(data: data)!
}
enum CodingKeys: String, CodingKey {
case author, artworkURL, genres, name, releaseDate
}
}
事实上,这比之前的模式更糟糕,因为至少在第一个选项中,同步网络调用被推迟到您引用UIImage
属性时
但有关此常规模式的更多信息,请参阅中的手动编码和解码数据(contentsOf:)
,所以这两种方法都不可取。但是,如果所讨论的属性不需要像您在这里所做的那样执行一些耗时的同步任务,那么它们是需要注意的好模式
在这种情况下,我认为最简单的方法是在需要时提供一种异步检索图像的方法:
struct Book: Decodable {
let author: String
let artworkURL: URL
let genres: [Genre]
let name: String
let releaseDate: String
func retrieveImage(completionHandler: @escaping (UIImage?, Error?) -> Void) {
let task = URLSession.shared.dataTask(with: artworkURL) { data, _, error in
guard let data = data, error == nil else {
completionHandler(nil, error)
return
}
completionHandler(UIImage(data: data), nil)
}
task.resume()
}
}
然后,当您的UI需要图像时,您可以惰性地检索它:
book.retrieveImage { image, error in
DispatchQueue.main.async { image, error in
cell.imageView.image = image
}
}
这些是您可以采用的几种方法。代码中有几个问题:(1)当在
init/init(从解码器:)
中设置属性时,不会触发属性观察器。(2)一个let
属性是一个常量,因此didSet
无效,你让我摆脱了一个巨大的疑问:你根本不应该在解析JSON的过程中检索图像。我觉得很脏,因为我必须为图像使用单独的类,但你完全正确,这应该是一个按需操作,而不是串行操作。干杯