I have a model which looks like this and contains NSManagedObject properties, namely the blendsWith property which is a type of [Tag]:
extension Oil {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Oil> {
return NSFetchRequest<Oil>(entityName: "Oil")
}
#NSManaged public var blendsWith: [Tag]?
#NSManaged public var color: String?
#NSManaged public var commentsCount: Int64
#NSManaged public var id: Int64
#NSManaged public var imageURL: String?
#NSManaged public var latinName: String?
#NSManaged public var name: String?
#NSManaged public var properties: NSObject?
#NSManaged public var research: String?
#NSManaged public var resourceType: String?
#NSManaged public var viewsCount: Int64
}
public class Oil: NSManagedObject, Codable {
enum CodingKeys: String, CodingKey {
case resourceType = "resource_type"
case id, name
case imageURL = "image_url"
case color
case latinName = "latin_name"
case emotions
case safetyInformation = "safety_information"
case fact, research
case viewsCount = "views_count"
case commentsCount = "comments_count"
case blendsWith = "blends_with"
case foundInBlends = "found_in_blends"
case properties
case sourcingMethods = "sourcing_methods"
case usages
}
required convenience public init(from decoder: Decoder) throws {
let context = CoreDataHelper.sharedInstance.persistentContainer.viewContext
guard let entity = NSEntityDescription.entity(forEntityName: "Oil", in: context) else { fatalError() }
self.init(entity: entity, insertInto: context)
let container = try decoder.container(keyedBy: CodingKeys.self)
self.resourceType = try! container.decodeIfPresent(String.self, forKey: .resourceType)!
self.id = try! container.decodeIfPresent(Int64.self, forKey: .id)!
self.name = try! container.decodeIfPresent(String.self, forKey: .name)!
self.imageURL = try! container.decodeIfPresent(String.self, forKey: .imageURL)!
self.color = try! container.decodeIfPresent(String.self, forKey: .color)!
self.viewsCount = try! container.decodeIfPresent(Int64.self, forKey: .viewsCount)!
self.viewsCount = try! container.decodeIfPresent(Int64.self, forKey: .viewsCount)!
self.commentsCount = try! container.decodeIfPresent(Int64.self, forKey: .commentsCount)!
self.latinName = try! container.decodeIfPresent(String.self, forKey: .latinName)!
if let blendsWith = try container.decodeIfPresent([Tag].self, forKey: CodingKeys.blendsWith) {
self.blendsWith = blendsWith
}
}
public func encode(to encoder: Encoder) throws {
}
}
Tag looks like this:
extension Tag {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Tag> {
return NSFetchRequest<Tag>(entityName: "Tag")
}
#NSManaged public var id: Int64
#NSManaged public var name: String?
#NSManaged public var resourceType: String?
#NSManaged public var tagType: String?
#NSManaged public var viewsCount: Int64
}
public class Tag: NSManagedObject, Codable {
enum CodingKeys: String, CodingKey {
case resourceType = "resource_type"
case id, name
case viewsCount = "views_count"
case tagType = "tag_type"
}
required convenience public init(from decoder: Decoder) throws {
let context = CoreDataHelper.sharedInstance.persistentContainer.viewContext
guard let entity = NSEntityDescription.entity(forEntityName: "Tag", in: context) else { fatalError() }
self.init(entity: entity, insertInto: context)
let container = try decoder.container(keyedBy: CodingKeys.self)
self.resourceType = try! container.decodeIfPresent(String.self, forKey: .resourceType)!
self.id = try! container.decodeIfPresent(Int64.self, forKey: .id)!
self.name = try! container.decodeIfPresent(String.self, forKey: .name)!
if let viewsCount = try container.decodeIfPresent(Int64.self, forKey: .viewsCount) {
self.viewsCount = viewsCount
} else {
self.viewsCount = 0
}
if let tagType = try container.decodeIfPresent(String.self, forKey: .tagType) {
self.tagType = tagType
} else {
self.tagType = "lol"
}
}
public func encode(to encoder: Encoder) throws {
}
}
When I go to fetch the Oil data stored locally, I get this crash:
2018-08-29 20:31:30.602764+0100 EL[27994:14799374] -[EL.Tag initWithCoder:]: unrecognized selector sent to instance 0x60c000679980
2018-08-29 20:31:30.603905+0100 EL[27994:14799374] [error] error: exception handling request: <NSSQLFetchRequestContext: 0x608000181ee0> , -[EL.Tag initWithCoder:]: unrecognized selector sent to instance 0x60c000679980 with userInfo of (null)
CoreData: error: exception handling request: <NSSQLFetchRequestContext: 0x608000181ee0> , -[EL.Tag initWithCoder:]: unrecognized selector sent to instance 0x60c000679980 with userInfo of (null)
2018-08-29 20:31:30.612185+0100 EL[27994:14799374] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[EL initWithCoder:]: unrecognized selector sent to instance 0x60c000679980'
What could be causing this crash?
For reference, my fetching method looks like this:
func getItems<T : NSManagedObject>(predicate : NSPredicate? = nil) -> [T]{
do {
let reqest = T.fetchRequest()
reqest.predicate = predicate
if let items = try persistentContainer.viewContext.fetch(reqest) as? [T] {
return items
} else {
return [T]()
}
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
return [T]()
}
}
And works like this:
let arrat : [Oil] = CoreDataHelper.sharedInstance.getItems()
If you debug this code does it ever step into the Tag init function? It appears the compiler is not seeing your Tag.init function which is most likely due to your Data Model for the Oil object not correctly setting the class type of the blendsWith property.
Check your data model for Oil and make sure that blendsWith is set to the correct type of Tag.
EDIT:
For Core Data to pick up your class setting you might need to add the objc flag before your class definition:
#objc(Tag)
public class Tag ...
A Transformable is usually coded by CoreData to Data (stored just as that), and coding an NSManagedObject may do things you do not expect. Decoding it could make things „more“ unexpected. (Coding done by NSKeyedArchiver and NSKeyedUnarchiver automatically on assignment/use.)
If you want to use Transformable then making it an NSManagedObject is pointless due to the argument above. (I at least have no experience with it, and have not heard what use it would have.)
So its usually Transformable OR CoreData-relationships(with NSManagedObjects) to model one relationship in CoreData, but not both to model a single one.
Related
// Service.swift
let decoder = JSONDecoder()
decoder.userInfo[CodingUserInfoKey.managedObjectContext] = self.coreDataManager.managedObjectContext
do {
_ = try decoder.decode([POI].self, from: jsonData)
self.coreDataManager.saveContext()
print("Saved")
}
catch{
print(error.localizedDescription)
}
// POI+CoreDataClass.swift
#objc(POI)
public class POI: NSManagedObject, Codable {
...
required public convenience init(from decoder: Decoder) throws {
guard let context = decoder.userInfo[CodingUserInfoKey.managedObjectContext] as? NSManagedObjectContext else {
throw DecoderConfigurationError.missingManagedObjectContext
}
let entity = NSEntityDescription.entity(forEntityName: "POI", in: context)!
self.init(entity: entity, insertInto: context)
let encodedValues = try decoder.container(keyedBy: CodingKeys.self)
let idString = try encodedValues.decode(String.self, forKey: .id)
self.id = UUID.init(uuidString: idString)!
self.externalId = try encodedValues.decode(String.self, forKey: .externalId)
self.title = try encodedValues.decode([String:String].self, forKey: .title)
self.coordinates = try encodedValues.decode(Geometry.self, forKey: .coordinates)
self.author = try encodedValues.decode(String.self, forKey: .author)
self.imageUrl = try encodedValues.decode(String.self, forKey: .imageUrl)
self.type = try encodedValues.decode(String.self, forKey: .type)
let propertyValues = try encodedValues.decode([String:Any].self, forKey: .properties)
self.properties = propertyValues
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(externalId, forKey: .externalId)
try container.encode(title, forKey: .title)
try container.encode(coordinates, forKey: .coordinates)
try container.encode(author, forKey: .author)
try container.encode(imageUrl, forKey: .imageUrl)
try container.encode(type, forKey: .type)
try container.encode(properties, forKey: .properties)
}
private enum CodingKeys: String, CodingKey {
case id
case externalId
case title
case coordinates
case author
case imageUrl
case type
case properties
}
}
enum DecoderConfigurationError: Error {
case missingManagedObjectContext
}
// POI+CoreDataProperties.swift
extension POI {
#nonobjc public class func fetchRequest() -> NSFetchRequest<POI> {
return NSFetchRequest<POI>(entityName: "POI")
}
#NSManaged public var author: String
#NSManaged public var coordinates: Geometry
#NSManaged public var externalId: String
#NSManaged public var id: UUID
#NSManaged public var imageUrl: String
#NSManaged public var properties: [String:Any]
#NSManaged public var title: [String:String]
#NSManaged public var type: String
}
// CoreDataManager.swift
...
func saveContext() {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
// The context couldn't be saved.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
// Example.json
..
{
"id": "0c0ef61d-9507-4400-8e5e-e7dc31a34370",
"externalId": "node/1234567890",
"title": {
"de": "Küche",
"en": "kitchen"
},
"coordinates": {
"type": "Point",
"values": [
{
"lat": 52.123456,
"lon": 13.123456
}
]
},
"author": "Authorname",
"imageUrl": "null",
"type": "Type",
"properties": {
"Website": {
"de": "https://website.com/de"
},
"wheelchair": false,
"start_date": 2020
}
}
..
We use CoreData in swift to store json data and retreive them. We want to save our json data in our database, therefore we use the Example.json in Service.swift as jsonData. I provided the POI model as it was created by XCode and it uses Codeable with decode and encode. The context is passed with Dependecy Injection and it is a singleton. The model uses Transformable with “NSSecureUnarchiveFromData” as Transformer. The problem is: while decoding I can see all properties in debug mode, but when the context should be saved, all values are “nil”. How to fix this? The other models are similar so I haven’t included them. If you need any other files or parts of them feel free to ask.
It's not completely clear from the question, but it sounds similar to a common problem when using values that don't work with transformable attributes. A transformable must be something that conforms to NSCoding or NSSecureCoding. Swift types often don't do this, because you can't conform to those protocols unless you inherit from NSObject. Using Codable is not the same thing. It serves the same purpose but it isn't interoperable with NSCoding.
When this happens, decoding the JSON works, but the decoded data doesn't get saved because it can't be transformed.
If this seems like the case here, you can change the attributes to "binary", and then do your own conversion to/from Data when accessing them. Or, try to change to a type that conforms to NSCoding and stick with transformables.
I'm following this tutorial to implement CoreData with Codable. Everything seems to be going great however I cannot figure out how to encode my list of photo objects. You can see my data structure in the image below and view my current code. When I try to decode the photos objects in the Pin class as below I get the error:
Referencing instance method 'encode(_:forKey:)' on 'Optional' requires that 'NSSet' conform to 'Encodable'
Photo+CoreDataClass.swift
import Foundation
import CoreData
#objc(Photo)
public class Photo: NSManagedObject, Codable {
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
do {
try container.encode(id, forKey: .id)
try container.encode(owner, forKey: .owner)
try container.encode(server, forKey: .server)
try container.encode(secret, forKey: .secret)
try container.encode(title, forKey: .title)
try container.encode(isPublic, forKey: .isPublic)
try container.encode(isFriend, forKey: .isFriend)
try container.encode(isFamily, forKey: .isFamily)
}
}
required convenience public init(from decoder: Decoder) throws {
guard let contextUserInfoKey = CodingUserInfoKey(rawValue: "context"),
let managedObjectContext = decoder.userInfo[contextUserInfoKey] as? NSManagedObjectContext,
let entity = NSEntityDescription.entity(forEntityName: "Photo", in: managedObjectContext) else {
fatalError("Cannot decode Photo!")
}
self.init(entity: entity, insertInto: managedObjectContext)
let values = try decoder.container(keyedBy: CodingKeys.self)
do {
id = try values.decode(Int64.self, forKey: .id)
owner = try values.decode(String?.self, forKey: .owner)
server = try values.decode(String?.self, forKey: .server)
secret = try values.decode(String?.self, forKey: .secret)
title = try values.decode(String?.self, forKey: .title)
isPublic = try values.decode(Int16.self, forKey: .isPublic)
isFriend = try values.decode(Int16.self, forKey: .isFriend)
isFamily = try values.decode(Int16.self, forKey: .isFamily)
} catch {
print(error)
}
}
enum CodingKeys: String, CodingKey {
case id = "id"
case owner = "owner"
case server = "server"
case secret = "secret"
case title = "title"
case isPublic = "ispublic"
case isFriend = "isfriend"
case isFamily = "isfamily"
}
}
Photo+CoreDataProperties.swift
import Foundation
import CoreData
extension Photo {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Photo> {
return NSFetchRequest<Photo>(entityName: "Photo")
}
#NSManaged public var id: Int64
#NSManaged public var owner: String?
#NSManaged public var secret: String?
#NSManaged public var server: String?
#NSManaged public var title: String?
#NSManaged public var isPublic: Int16
#NSManaged public var isFriend: Int16
#NSManaged public var isFamily: Int16
}
Pin+CoreDataClass.swift
import Foundation
import CoreData
#objc(Pin)
public class Pin: NSManagedObject, Codable {
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(latitude, forKey: .latitude)
try container.encode(longitude, forKey: .longitude)
try container.encode(photos, forKey: .photos)
}
required convenience public init(from decoder: Decoder) throws {
guard let contextUserInfoKey = CodingUserInfoKey(rawValue: "context"),
let managedObjectContext = decoder.userInfo[contextUserInfoKey] as? NSManagedObjectContext,
let entity = NSEntityDescription.entity(forEntityName: "Pin", in: managedObjectContext) else {
fatalError("Could not decode Pin!")
}
self.init(entity: entity, insertInto: managedObjectContext)
let values = try decoder.container(keyedBy: CodingKeys.self)
do {
latitude = try values.decode(Double.self, forKey: .latitude)
longitude = try values.decode(Double.self, forKey: .longitude)
photos = NSSet(array: try values.decode([Photo].self, forKey: .photos))
} catch {
print(error)
}
}
enum CodingKeys: String, CodingKey {
case latitude = "latitude"
case longitude = "longitude"
case photos = "photos"
}
}
Pin+CoreDataProperties.swift
import Foundation
import CoreData
extension Pin {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Pin> {
return NSFetchRequest<Pin>(entityName: "Pin")
}
#NSManaged public var latitude: Double
#NSManaged public var longitude: Double
#NSManaged public var photos: NSSet?
}
// MARK: Generated accessors for photos
extension Pin {
#objc(addPhotosObject:)
#NSManaged public func addToPhotos(_ value: Photo)
#objc(removePhotosObject:)
#NSManaged public func removeFromPhotos(_ value: Photo)
#objc(addPhotos:)
#NSManaged public func addToPhotos(_ values: NSSet)
#objc(removePhotos:)
#NSManaged public func removeFromPhotos(_ values: NSSet)
}
Declare photos as a swift native type
#NSManaged var photos: Set<Photo>
In decoder
photos = try values.decode(Set<Photo>.self, forKey: .photos)
I have an application where I tried using UserDefaults to save a Codable model locally but the problem is that UserDefault does not store all the values of the Model and assigns nil to some of them and at times the proper value is assigned. So I decided to try an alternative which is realm and I am able to modify my model to work with realm but I have an issue not which is in the process of decoding an Object in an Object using realm. I was able to make it work when dealing with an Array object with List but non array Objects simply failed to map to JSON
below is a sample array that I am dealing with
{
"id": 732,
"name": "Vendor Name",
"logo": ".../thumb/missing.png",
"kitchens":
{
"id": 36,
"name": "Sandwiches"
}
}
model class
class VendorsList : Object, Decodable {
#objc dynamic var id : Int = 0
#objc dynamic var name : String?
#objc dynamic var logo : String?
// Create your Realm List.
var kitchensList = List<VendorKitchens>()
override class func primaryKey() -> String? {
return "id"
}
private enum CodingKeys: String, CodingKey {
case id
case name
case logo
// Set JSON Object Key
case kitchensList = "kitchens"
}
public required convenience init(from decoder: Decoder) throws {
self.init()
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int.self, forKey: .id)
self.name = try container.decode(String.self, forKey: .name)
self.logo = try container.decode(String.self, forKey: .logo)
// Map your JSON Array response
let kitchens = try container.decodeIfPresent([VendorKitchens].self, forKey: .kitchensList) ?? [VendorKitchens()]
kitchensList.append(objectsIn: kitchens)
}
}
class VendorKitchens : Object, Decodable {
#objc dynamic var id : Int = 0
#objc dynamic var name : String?
override class func primaryKey() -> String? {
return "id"
}
private enum CodingKeys: String, CodingKey {
case id
case name
}
}
this returns an error
Failed to map data to JSON
Here, try this.
class VendorsList : Object, Decodable {
#objc dynamic var id : Int = 0
#objc dynamic var name : String?
#objc dynamic var logo : String?
#objc dynamic var kitchens: VendorKitchens? = nil
override class func primaryKey() -> String? {
return "id"
}
private enum CodingKeys: String, CodingKey {
case id
case name
case logo
case kitchens = "kitchens"
}
public required convenience init(from decoder: Decoder) throws {
self.init()
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int.self, forKey: .id)
self.name = try container.decode(String.self, forKey: .name)
self.logo = try container.decode(String.self, forKey: .logo)
kitchens = try container.decodeIfPresent(VendorKitchens.self, forKey: .kitchensList)
}
}
class VendorKitchens : Object, Decodable {
#objc dynamic var id : Int = 0
#objc dynamic var name : String?
override class func primaryKey() -> String? {
return "id"
}
private enum CodingKeys: String, CodingKey {
case id
case name
}
}
I was going through some model classes of a project(Realm is being used). This is one class…
#objcMembers class CommA: Object {
dynamic var id = 0
dynamic var recipientId = "0"
dynamic var name: String?
dynamic var picture: String?
dynamic var unreadMessageCount = 0
dynamic var lastMessage: MyMessage?
override class func primaryKey() -> String? {
return "id"
}
}
This pretty much seems easy. A class with variables and a primary key defined..
But there is another class which looks like so…
#objcMembers class CommB: Object, Codable {
dynamic var id = "0"
dynamic var name: String?
dynamic var picture: String?
dynamic var status: String?
dynamic var lastSeen: String?
dynamic var unreadMessageCount = 0
dynamic var lastMessage: MyMessage?
enum CodingKeys: String, CodingKey {
case id = "UsrID"
case name = "UserName"
case picture = "UsrPicture"
case status = "ChatStatus"
}
required init() {
super.init()
}
required init(value: Any, schema: RLMSchema) {
super.init(value: value, schema: schema)
}
required init(realm: RLMRealm, schema: RLMObjectSchema) {
super.init(realm: realm, schema: schema)
}
convenience init(id: String, name: String, picture: String, status: String) {
self.init()
self.id = id
self.name = name
self.picture = picture
self.status = status
}
convenience required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let id = try container.decode(String.self, forKey: .id)
let name = try container.decode(String.self, forKey: .name)
let picture = try container.decode(String.self, forKey: .picture)
//let status = try container.decode(String.self, forKey: .status)
self.init(id: id, name: name, picture: picture, status: "status")
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(name, forKey: .name)
try container.encode(picture, forKey: .picture)
try container.encode(status, forKey: .status)
}
override class func primaryKey() -> String? {
return "id"
}
}
What I don’t understand here is why the enum, required init, convenience required init etc. is used…?
In fact, some of the initialisers are redundant. Your code can be shortened to
#objcMembers class CommB: Object, Codable {
dynamic var id = "0"
dynamic var name: String?
dynamic var picture: String?
dynamic var status: String?
dynamic var lastSeen: String?
dynamic var unreadMessageCount = 0
dynamic var lastMessage: MyMessage?
enum CodingKeys: String, CodingKey {
case id = "UsrID"
case name = "UserName"
case picture = "UsrPicture"
case status = "ChatStatus"
}
convenience init(id: String, name: String, picture: String, status: String) {
self.init()
self.id = id
self.name = name
self.picture = picture
self.status = status
}
convenience required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let id = try container.decode(String.self, forKey: .id)
let name = try container.decode(String.self, forKey: .name)
let picture = try container.decode(String.self, forKey: .picture)
//let status = try container.decode(String.self, forKey: .status)
self.init(id: id, name: name, picture: picture, status: "status")
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(name, forKey: .name)
try container.encode(picture, forKey: .picture)
try container.encode(status, forKey: .status)
}
override class func primaryKey() -> String? {
return "id"
}
}
I have removed
required init() {
super.init()
}
required init(value: Any, schema: RLMSchema) {
super.init(value: value, schema: schema)
}
required init(realm: RLMRealm, schema: RLMObjectSchema) {
super.init(realm: realm, schema: schema)
}
Everything else is quite important.
CommB is not only a realm object, but also Codable. And the author of it wanted to customise the de/encoding behaviour so that the de/encoder only de/encodes id, name, picture and status. To do this, a CodingKey enum needs to be created, storing the coding keys. Also, convenience required init(from decoder: Decoder) and func encode(to encoder: Encoder) needs to be implemented.
The convenience init(id: String, name: String, picture: String, status: String) initialiser is there because the init(from decoder: Decoder) delegates to it.
To learn more about how Codable works, visit here.
Hy I am working on app that uses Realm and Alamofire. I am really happy in using these library in my iOS project.
But then I have to post a List of models that contains multiple lists of models. So that is too much deep thing I mean List inside List that contains models and those model contains list of several model
For demonstration lets just take an example of my models
#objcMembers public class MyModel : Object{
dynamic var Id: String = ""
dynamic var Name: String = ""
dynamic var Favorites: List<String>? = nil
dynamic var Subjects: List<UserSubject>? = nil
}
#objcMembers public class UserSubject: Object{
dynamic var Id: String = ""
dynamic var Name: String = ""
dynamic var Teachers: List<Publications>? = nil
}
#objcMembers public class Publications: Object{
dynamic var Id: String = ""
dynamic var Name: String = ""
dynamic var Editors: List<Editors>? = nil
}
So you can see these are models inside list that contains another list of model.
Due to Realm I am using List for list for creating the RelationShip.
Problem: but Now when I tries to implement Codable on Models/Struct It really unable to recognize List property.
I really do not know how to solve this problem? Do anyone have any Idea how to do it ?
UPDATE:
I am using Swift 4.0 and base sdk is 11.2
Had the same problem in a project and wrote these extensions:
import Foundation
import RealmSwift
extension RealmSwift.List: Decodable where Element: Decodable {
public convenience init(from decoder: Decoder) throws {
self.init()
let container = try decoder.singleValueContainer()
let decodedElements = try container.decode([Element].self)
self.append(objectsIn: decodedElements)
}
}
extension RealmSwift.List: Encodable where Element: Encodable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.map { $0 })
}
}
With these extension you easily can make the Realm Object Codable. Like this
#objcMembers public class MyModel: Object, Codable {
dynamic var id: String = ""
dynamic var name: String = ""
var favorites = List<String>()
var subjects = List<UserSubject>()
}
#objcMembers public class UserSubject: Object, Codable {
dynamic var id: String = ""
dynamic var name: String = ""
var teachers = List<Publications>()
}
#objcMembers public class Publications: Object, Codable {
dynamic var id: String = ""
dynamic var name: String = ""
var editors = List<Editor>()
}
#objcMembers public class Editor: Object, Codable {
}
I can suggest you use Unrealm.
It's a powerful library which enables you to save Swift native types (structs, enums, Arrays, Dictionaries) into Realm database. So you don't have to worry about Lists and Codable compatibility anymore.
An example model with Codable implementation
You can use extensions for List
Swift 4.1
extension List: Decodable where Element: Decodable {
public convenience init(from decoder: Decoder) throws {
// Initialize self here so we can get type(of: self).
self.init()
assertTypeIsDecodable(Element.self, in: type(of: self))
let metaType = (Element.self as Decodable.Type) // swiftlint:disable:this force_cast
var container = try decoder.unkeyedContainer()
while !container.isAtEnd {
let element = try metaType.init(__from: &container)
self.append(element as! Element) // swiftlint:disable:this force_cast
}
}
}
extension List: Encodable where Element: Decodable {
public func encode(to encoder: Encoder) throws {
assertTypeIsEncodable(Element.self, in: type(of: self))
var container = encoder.unkeyedContainer()
for element in self {
// superEncoder appends an empty element and wraps an Encoder around it.
// This is normally appropriate for encoding super, but this is really what we want to do.
let subencoder = container.superEncoder()
try (element as! Encodable).encode(to: subencoder) // swiftlint:disable:this force_cast
}
}
}
For these using Swift 5.x and XCode 13.x.x (i have 13.3.1) and RealmSwift (10.25.1):
Realm with Codable (Encode/Decode) (2 class for example)
import Foundation
import RealmSwift
/*
* Hold info about user profile
*/
class Profile: Object, Codable {
#Persisted(primaryKey: true) var _id: String
#Persisted var firstName: String
#Persisted var lastName: String
#Persisted var email: String
#Persisted var role: String
// Relations
#Persisted var session: Session?
#Persisted var companies: List<Company>
// MARK: Codable support
enum CodingKeys: String, CodingKey {
case email, companies
case id = "_id"
case firstName, lastName, role
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(_id, forKey: .id)
try container.encode(firstName, forKey: .firstName)
try container.encode(lastName, forKey: .lastName)
try container.encode(email, forKey: .email)
try container.encode(role, forKey: .role)
try container.encode(companies, forKey: .companies)
}
required init(from decoder: Decoder) throws {
super.init()
let container = try decoder.container(keyedBy: CodingKeys.self)
_id = try container.decode(String.self, forKey: .id)
firstName = try container.decode(String.self, forKey: .firstName)
lastName = try container.decode(String.self, forKey: .lastName)
email = try container.decode(String.self, forKey: .email)
role = try container.decode(String.self, forKey: .role)
let companiesList = try container.decode([Company].self, forKey: .companies)
companies.append(objectsIn: companiesList)
}
}
Other example:
import Foundation
import RealmSwift
/*
* Hold info about user session
*/
class Session: Object, Codable {
#Persisted(primaryKey: true) var _id: String
#Persisted(indexed: true) var accessToken: String
#Persisted var refreshToken: String
#Persisted var tokenType: String
#Persisted var expiresIn: Double
// Relations
#Persisted var profile: Profile?
// MARK: Codable support
enum CodingKeys: String, CodingKey {
case accessToken = "access_token"
case tokenType = "token_type"
case expiresIn = "expires_in"
case refreshToken = "refresh_token"
case id = "_id"
case v = "__v"
case profile
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(_id, forKey: .id)
try container.encode(accessToken, forKey: .accessToken)
try container.encode(refreshToken, forKey: .refreshToken)
try container.encode(tokenType, forKey: .tokenType)
try container.encode(expiresIn, forKey: .expiresIn)
try container.encode(profile, forKey: .profile)
}
required init(from decoder: Decoder) throws {
super.init()
let container = try decoder.container(keyedBy: CodingKeys.self)
_id = try container.decode(String.self, forKey: .id)
accessToken = try container.decode(String.self, forKey: .accessToken)
refreshToken = try container.decode(String.self, forKey: .refreshToken)
tokenType = try container.decode(String.self, forKey: .tokenType)
expiresIn = try container.decode(Double.self, forKey: .expiresIn)
profile = try container.decode(Profile.self, forKey: .profile)
}
}
You can encode List from realm with this code, for example:
try container.encode(companies, forKey: .companies)
and to decode:
let companiesList = try container.decode([Company].self, forKey: .companies)
companies.append(objectsIn: companiesList)
This is only and example, you can adapt to your need's.
And finally for example when you get data from network (i'm using Moya):
extension Session {
init(data: Data) throws {
self = try JSONDecoder().decode(Session.self, from: data)
}
}
self.xxApi.request(.login(username: "user#domain.com", password: "HiTh3r3.2022")) { result in
switch result {
case let .success(response):
guard let session = try? Session(data: response.data) else {
print("Can't parse session data: \(JSON(response.data))")
return
}
// Request parsed so work with data here
print(session)
case let .failure(error):
print(error)
}
}
Try this:
extension List: Decodable where Element: Decodable {
public convenience init(from decoder: Decoder) throws {
self.init()
var container = try decoder.unkeyedContainer()
while !container.isAtEnd {
let element = try container.decode(Element.self)
self.append(element)
}
}
}
extension List: Encodable where Element: Encodable {
public func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
for element in self {
try element.encode(to: container.superEncoder())
}
}
}
Found it here: How to use List type with Codable? (RealmSwift)