Use `self =` in convenience initializers to delegate to JSONDecoder or factory methods in Swift to avoid `Cannot assign to value: 'self' is immutable`
First, note that this limitation exists only for class
es, so the example initializer will work for as-is for struct
s and enum
s, but not all situations allow changing a class
to one of these types.
This limitation on class
initializers is a frequent pain-point that shows up often on this site (for example). There is a thread on the Swift forums discussing this issue, and work has started to add the necessary language features to make the example code above compile, but this is not complete as of Swift 5.4.
From the thread:
Swift's own standard library and Foundation overlay hack around this missing functionality by making classes conform to dummy protocols and using protocol extension initializers where necessary to implement this functionality.
Using this idea to fix the example code yields
final class Test: Codable {
let foo: Int
init(foo: Int) {
self.foo = foo
}
func jsonData() throws -> Data {
try JSONEncoder().encode(self)
}
}
protocol TestProtocol: Decodable {}
extension Test: TestProtocol {}
extension TestProtocol {
init(fromJSON data: Data) throws {
self = try JSONDecoder().decode(Self.self, from: data)
}
}
let test = Test(foo: 42)
let data = try test.jsonData()
let decodedTest = try Test(fromJSON: data)
print(decodedTest.foo)
which works fine. If Test
is the only type conforming to TestProtocol
, then only Test
will get this initializer.
An alternative is to simply extend Decodable
or another protocol to which your class conforms, but this may be undesirable if you do not want other types conforming to that protocol to also get your initializer.
Create a generic Data initializer for Decodable types using Swift
You can't overwrite class itself, but you can init it, init object from json and then assign values/ If take your code - it'll be something like this:
public class MyResponse: Codable {
public var id: String?
public var context: String?
public var results: [MyResult]?
}
public extension MyResponse {
convenience init(data: Data) throws {
self.init()
let object = try JSONDecoder().decode(MyResponse.self, from: data)
self.id = object.id
self.context = object.context
self.results = object.results
}
}
If you really don't need a class it's better to use struct instead of it, and it can be like this:
public struct MyResponse: Codable {
public let id: String?
public let context: String?
public let results: [String]?
}
public extension MyResponse {
init(data: Data) throws {
self = try JSONDecoder().decode(MyResponse.self, from: data)
}
}
Delegate a non-failable initializer to a failable initializer
While you can’t delegate to a failable initializer, since enums are value types, you can just try creating another value of the same enum using the failable initializer, and then substitute a default in case of nil
, and assign that to self
:
enum Result: Int {
case Success
case Failure
case Unknown = -1
init(value: Int) {
self = Result(rawValue: value) ?? .Unknown
}
}
Result(value: 100) == .Unknown // returns true
Convenience initializer in Swift
Change your dict
declaration to:
dict: Dictionary<String, Any>
It's because you're using a mix of types including non-class types.
More information in Swift docs here:
https://developer.apple.com/library/prerelease/mac/documentation/Swift/Conceptual/Swift_Programming_Language/TypeCasting.html#//apple_ref/doc/uid/TP40014097-CH22-XID_505
That "Extra argument" error message is a red-herring. I've found that it's usually complaining about the type of an argument not matching.
Can not use realm with object mapper swift 3.0
I use this pattern:
I have a BaseObject
that all my Realm objects inherit from
open class BaseObject: Object, StaticMappable {
public class func objectForMapping(map: Map) -> BaseMappable? {
return self.init()
}
public func mapping(map: Map) {
//for subclasses
}
}
Then your class would look like this:
import ObjectMapper
import RealmSwift
class QuestionSet: BaseObject {
//MARK:- Properties
dynamic var id:Int = 0
dynamic var title:String?
dynamic var shortTitle:String?
dynamic var desc:String?
dynamic var isOriginalExam:Bool = false
dynamic var isMCQ:Bool = false
dynamic var url:String?
//mapping the json keys with properties
public func mapping(map: Map) {
id <- map["id"]
title <- map["title"]
shortTitle <- map["short_title"]
desc <- map["description"]
isMCQ <- map["mc"]
url <- map["url"]
isOriginalExam <- map["original_pruefung"]
}
}
How to decode an array of inherited classes in Swift
I think a major part of the problem is that you are using an array of mixed types, [Any], and then you are decoding it as one type Parent because it is quite possible to get the child objects to be properly encoded as Child.
One solution is to create a new Codable struct that holds the array and that with the use of a type property keeps track on how to decode the objects in the array
enum ObjectType: String, Codable {
case parent
case child
}
struct ParentAndChild: Codable {
let objects: [Parent]
enum CodingKeys: CodingKey {
case objects
}
enum ObjectTypeKey: CodingKey {
case type
}
init(with objects: [Parent]) {
self.objects = objects
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
var objectsArray = try container.nestedUnkeyedContainer(forKey: CodingKeys.objects)
var items = [Parent]()
var array = objectsArray
while !objectsArray.isAtEnd {
let object = try objectsArray.nestedContainer(keyedBy: ObjectTypeKey.self)
let type = try object.decode(ObjectType.self, forKey: ObjectTypeKey.type)
switch type {
case .parent:
items.append(try array.decode(Parent.self))
case .child:
items.append(try array.decode(Child.self))
}
}
self.objects = items
}
}
I have also made some changes to the classes as well, the Parent class is hugely simplified and the Child class has modified functionality for encoding/decoding where the main change is that init(from:)
calls supers init(from:)
class Parent: Codable, CustomDebugStringConvertible {
var debugDescription: String {
return "[\(name)]"
}
var name: String
init(name: String) {
self.name = name
}
}
class Child: Parent {
override var debugDescription: String {
return "[\(name) - \(age)]"
}
var age: Int
init(name: String, age: Int) {
self.age = age
super.init(name: name)
}
enum CodingKeys: CodingKey {
case age
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
age = try container.decode(Int.self, forKey: .age)
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(age, forKey: .age)
}
}
The given data did not contain a top-level value error when attempting to decode json into NSManagedObject
I have finally resolved the issue. Instead using "Codegen: Class Definition", I should have used "Codegen: Manual" and add the subclass like this:
- Open the data model file
- Under Entities, select the relevant entity
- From the upper menu, choose Editor -> Create NSManagedObject subclass...
Related Topics
What Does the Underscore in a Function Declaration Do
Accessing Actor Properties Synchronously from Task-Less Context
Error While Using Property 'Cgrectgetwidth', It Says It Was Replaced with 'Cgrect.Width'
How to Set Cadisplaylink in Swift with Weak Reference Between Target and Cadisplaylink Instance
Get Element from Array of Dictionaries According to Key
My Button Is Centered for iPhone 6 and 6 Plus, But Not for iPhone 5
"Unexpectedly Found Nil While Unwrapping an Optional Value" When Retriveing Pffile from Parse.Com
Swift Function Compiler Error 'Missing Return'
Aws - Unauthenticated Access Is Not Supported for This Identity Pool in Swift
Swiftui Tabview Gives an Error Message During Add/Delete the Element of Coredata
How to Make Struct Lazylist in Swiftui
Can't Use 'Shape' as Type in Swiftui
Cannot Preview This File, App May Have Crashed -- Occurs When Inputting Specific Line of Code
Self. in Trailing Swift Closures, Meaning and Purpose
Empty Class in Swift Playground Gives _Lldb_Expr_ Error