Search code examples
swiftrealmswift4

Using same class/model for networking and local apis


When I am creating network requests I use the new Swift 4

let user = try JSONDecoder().decode(UserCodable.self, from: data)

Which turns my api model into a swift model that I can work with. This works great:

struct User: Codable {
    let id: Int
    let username: String
    let email: String
}

This is great for networking, however I started using realm to create local databases. For each table in the database I have to create a class model, so if I want to create a Users table I have to create a realm class with the fields: id, username and email. So does this mean I am going to have to classes used to manage Users? I feel like there is a different way to doing things


Solution

  • Realm model classes can conform to Codable, so there's no need for two separate types.

    You just need to convert User to a class, make it inherit from Object to let Realm know that it is a Realm model class and mark all properties @objc dynamic to make them managed properties.

    class User: Object, Codable {
        @objc dynamic var id:Int = 0
        @objc dynamic var username:String = ""
        @objc dynamic var email:String = ""
    }
    

    List doesn't conform to Decodable out of the box, so to make a class Decodable even when it has a to-many relationship, you'll need to implement a custom init(from decoder:) method.

    let userJSON = """
    {
        "id":1,
        "username":"John",
        "email":"[email protected]",
        "dogs":[
            {"id":2,"name":"King"},
            {"id":3,"name":"Kong"}
        ]
    }
    """
    
    class Dog: Object,Codable {
        @objc dynamic var id:Int = 0
        @objc dynamic var name:String = ""
    }
    
    class User: Object, Decodable {
        @objc dynamic var id:Int = 0
        @objc dynamic var username:String = ""
        @objc dynamic var email:String = ""
        let dogs = List<Dog>()
    
        private enum CodingKeys: String, CodingKey {
            case id, username, email, dogs
        }
    
        required convenience init(from decoder: Decoder) throws {
            self.init()
            let container = try decoder.container(keyedBy: CodingKeys.self)
            id = try container.decode(Int.self, forKey: .id)
            username = try container.decode(String.self, forKey: .username)
            email = try container.decode(String.self, forKey: .email)
            let dogsArray = try container.decode([Dog].self, forKey: .dogs)
            dogs.append(objectsIn: dogsArray)
        }
    }
    
    let decodedUser = try? JSONDecoder().decode(User.self, from: userJSON.data(using: .utf8)!)