Reputation: 425
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
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
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