Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/swift/19.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在Swift 4中向可解码协议添加属性观测器_Swift_Decodable - Fatal编程技术网

在Swift 4中向可解码协议添加属性观测器

在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:

我正在从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
}
但该
不符合协议“可解码”


还有其他人有解决方案吗?

下面我将列举几种处理这种情况的方法,但正确的答案是,在解析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
    属性时

    但有关此常规模式的更多信息,请参阅中的手动编码和解码

  • 我提供上述两种模式作为示例,说明如何使某些属性不是JSON的一部分,而是以其他方式初始化的。但是,因为您使用的是
    数据(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的过程中检索图像。我觉得很脏,因为我必须为图像使用单独的类,但你完全正确,这应该是一个按需操作,而不是串行操作。干杯