aios
aios

Reputation: 425

Swift - Initialise model object with init(from decoder:)

Below is my model struct

struct MovieResponse: Codable {
    
    var totalResults: Int
    var response: String
    var error: String
    var movies: [Movie]
    
    enum ConfigKeys: String, CodingKey {
        case totalResults
        case response = "Response"
        case error = "Error"
        case movies
    }
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        self.totalResults = try values.decodeIfPresent(Int.self, forKey: .totalResults)!
        self.response = try values.decodeIfPresent(String.self, forKey: .response)!
        self.error = try values.decodeIfPresent(String.self, forKey: .error) ?? ""
        self.movies = try values.decodeIfPresent([Movie].self, forKey: .movies)!
    }
}

extension MovieResponse {
    struct Movie: Codable, Identifiable {
        var id = UUID()
        var title: String
        var year: Int8
        var imdbID: String
        var type: String
        var poster: URL
        
        enum EncodingKeys: String, CodingKey {
            case title = "Title"
            case year = "Year"
            case imdmID
            case type = "Type"
            case poster = "Poster"
        }
    }
}

Now in a ViewModel, I am creating an instance of this model using the below code

@Published var movieObj = MovieResponse()

But there is a compile error saying, call init(from decoder) method. What is the proper way to create a model instance in this case?

Upvotes: 2

Views: 13501

Answers (2)

Witek Bobrowski
Witek Bobrowski

Reputation: 4239

As the Swift Language Guide reads:

Swift provides a default initializer for any structure or class that provides default values for all of its properties and doesn’t provide at least one initializer itself.

The "and doesn’t provide at least one initializer itself" part is crucial here. Since you are declaring an additional initializer you should either declare your own initialized like so:

init(
    totalResults: Int,
    response: String,
    error: String,
    movies: [Movie]
) {
    self.totalResults = totalResults
    self.response = response
    self.error = error
    self.movies = movies
}

or move Codable conformance to an extension so Swift can provide you with a default initialiser. This would be a preferred way to do it (my personal opinion, I like to move additional protocol conformances to extensions).

struct MovieResponse {
    var totalResults: Int
    var response: String
    var error: String
    var movies: [Movie]
}

extension MovieResponse: Codable {

    enum ConfigKeys: String, CodingKey {
        case totalResults
        case response = "Response"
        case error = "Error"
        case movies
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        self.totalResults = try values.decodeIfPresent(Int.self, forKey: .totalResults)!
        self.response = try values.decodeIfPresent(String.self, forKey: .response)!
        self.error = try values.decodeIfPresent(String.self, forKey: .error) ?? ""
        self.movies = try values.decodeIfPresent([Movie].self, forKey: .movies)!
    }
}

Upvotes: 4

SeaSpell
SeaSpell

Reputation: 758

You need to add another initializer if you do not want to use a decoder. Swift give you one for free if and only if you do not write your own initializer. Now that you have one you loose the free one.

add another:

init() {
    //Your initializer code here
}

If you are trying to use the decoder init you need to use a decoder to invoke it. For instance if it's Json

@Published var movieObj = try? JSONDecoder().decode(MovieResponse.self, from: <#T##Data#>)

Upvotes: 3

Related Questions