Use 'Self =' in Convenience Initializers to Delegate to JSONdecoder or Factory Methods in Swift to Avoid 'Cannot Assign to Value: 'Self' Is Immutable'

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 classes, so the example initializer will work for as-is for structs and enums, 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:

  1. Open the data model file
  2. Under Entities, select the relevant entity
  3. From the upper menu, choose Editor -> Create NSManagedObject subclass...


Related Topics



Leave a reply



Submit