簡體   English   中英

將屬性觀察器添加到 Swift 4 中的可解碼協議

[英]Adding property observer to Decodable protocol in Swift 4

我正在從 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: 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
}

但這does not conform to protocol 'Decodable'

還有其他人有解決方案嗎?

下面我將列舉一些處理這種情況的方法,但正確的答案是您不應該在解析 JSON 期間檢索圖像。 而且您絕對不應該同步執行此操作(這就是Data(contentsOf:)所做的)。

相反,您應該只檢索 UI 需要的圖像。 並且您希望將圖像檢索到可以在低內存情況下清除的內容,以響應.UIApplicationDidReceiveMemoryWarning系統通知。 最重要的是,如果您不小心,並且您幾乎總是想將圖像與模型對象本身分離,圖像可能會占用驚人的內存量。


但是,如果您絕對決定將圖像合並到Book對象中(我再次建議不要這樣做),您可以:

  1. 您可以將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 }

    在這種情況下,這是一種可怕的模式(同樣,因為我們永遠不應該進行同步網絡調用),但它說明了lazy變量的想法。 您有時也會使用計算屬性來執行這種模式。

  2. 同樣糟糕(出於同樣的原因,同步網絡調用是邪惡的),您還可以實現自定義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屬性時。

    但有關此通用模式的更多信息,請參閱編碼和解碼自定義類型中的手動編碼和解碼

我提供了上面這兩個模式,作為示例,說明如何擁有一些不是 JSON 的一部分,而是以其他方式初始化的屬性。 但是因為您使用的是Data(contentsOf:) ,所以這些都不是真正可取的。 但是如果有問題的屬性不需要一些耗時的同步任務,就像你在這里做的那樣,它們是很好的模式,需要注意。

在這種情況下,我認為最簡單的方法是提供一種在需要時異步檢索圖像的方法:

  1. 完全消除同步網絡調用,只提供一個異步方法來檢索圖像:

     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 } }

這些是您可以采用的幾種方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM