3
\$\begingroup\$

I have been using SwiftyJSON to create my models of Object in Swift. But recently, I moved to CoreData for saving the objects locally. I am new to CoreData and want to know the best practices I should follow for creating the model.

Here's what I have created to update and get User's data (after getting the JSON from webservice)

Core Data Model

import Foundation
import CoreData
import SwiftyJSON

@objc(User)
public class User: NSManagedObject {

var authToken : String!

class func addUser(fromJSON json: JSON?, inManagedObjectContext managedObjectContext: NSManagedObjectContext?) -> User? {

    if var json = json { //unwrapping
        let entityDescription = NSEntityDescription.entity(forEntityName: "User", in: managedObjectContext!)
        let newUser = NSManagedObject(entity: entityDescription!, insertInto: managedObjectContext) as! User

        json = json["user"]
        newUser.authToken = json["auth_token"].stringValue
        newUser.email = json["email"].stringValue

        managedObjectContext?.perform({
            _ = try? newUser.managedObjectContext!.save()
        })
        return newUser
    }
    return nil
}

class func getUser(inManagedObjectContext managedObjectContext: NSManagedObjectContext) -> User? {
    // Initialize Fetch Request
    let fetchRequest = NSFetchRequest<NSFetchRequestResult>()

    // Create Entity Description
    let entityDescription = NSEntityDescription.entity(forEntityName: "User", in: managedObjectContext)

    // Configure Fetch Request
    fetchRequest.entity = entityDescription


    if let result = try? managedObjectContext.fetch(fetchRequest) {
        return result.first as? User
    } else {
        return nil
    }
}

class func updateUser(fromJson json : JSON?, inManagedObjectContext managedObjectContext: NSManagedObjectContext) {
    let user = getUser(inManagedObjectContext: managedObjectContext)

    if var json = json {
        json = json["user"]
        user?.name = json["name"].stringValue
        user?.email = json["email"].stringValue
    }

    managedObjectContext.perform({
        _ = try? managedObjectContext.save()
    })
}

}

Here's how I used to create model before I was using Core Data:

Previous Model

import Foundation
import SwiftyJSON

class User : NSObject, NSCoding{

var email : String!
var name : String!


/**
 * Instantiate the instance using the passed json values to set the properties values
 */
init(fromJson json: JSON!){
    if json == nil{
        return
    }
    email = json["email"].stringValue
    name = json["name"].stringValue
}
}

Although the code is working, I am not sure if this is the right way to do it. How should I actually create the model with CoreData? And how should I call them from ViewControllers? I tried reading tutorials, but they help on getting started but not on how to use them in bigger projects.

\$\endgroup\$
  • \$\begingroup\$ Can you please give suggestions to improve this question, as this has been downvoted \$\endgroup\$ – sasquatch Jan 10 '17 at 19:27
  • \$\begingroup\$ Seems you're asking about (completely) rewriting your current code, adding some none yet existing code respectively, instead of improvement of what you have already. Such is considered off-topic at Code Review. \$\endgroup\$ – πάντα ῥεῖ Jan 10 '17 at 19:36
  • \$\begingroup\$ No I am not asking for rewriting the current code. Just want to know if this well-practiced way, or needs improvements. \$\endgroup\$ – sasquatch Jan 10 '17 at 19:39
1
+50
\$\begingroup\$

Based on the code that you've supplied, here's my take on some small changes in responsibility.

First of all, the NSManagedObject subclass.

You can write this by hand (as I think you have done) but I prefer to have Xcode generate this subclass automatically from the xcdatamodeld file (this option is found in the 'Editor' menu).

Note that in the latest version of Xcode, this file isn't added to your project, but actually added to the 'DerivedData' folder so unless you go looking for it you'll probably never see it.

If you find it and open it up, it will probably look something like this this:

import Foundation
import CoreData

@objc(User)
public class User: NSManagedObject {

    @NSManaged public var name: String?
    @NSManaged public var email: String?
    @NSManaged public var authToken: String?
}

Second, an extension that contains some methods that are specific to working with JSON objects.

Instead of a class function to create a User object, I'd use a failiable initializer init?() that returns nil if an instance could not be created.

I'd also create some way to reference your string keys so that you're not typing them by hand each time. This could be a struct, or just a bunch of static let statements. I prefer the struct because it puts all the keys in a similar namespace that helps organize your code, but also helps with auto completion when you're typing out the keys.

import SwiftyJSON

extension User {

    // prevent accidental typos when working with string keys
    struct Keys {
        static let name = "name"
        static let email = "email"
        static let authToken = "auth_token"
        static let user = "user"
    }

    // convenience initalizer for making `User` from a JSON object
    convenience init?(in context: NSManagedObjectContext, with json: JSON?) {
        guard let json = json else { return nil }

        email = json[Keys.email].stringValue
        authToken = json[Keys.authToken].stringValue
    }

    // updating existing instance with data from a JSON object
    func update(from json: JSON?) {
        guard let json = json[Keys.user] else { return }

        name = json[Keys.name].stringValue
        email = json[Keys.email].stringValue

    }
}

Finally, I'd remove any code that calls save() on your context from within the User object, and have this in the controller. I'd probably also remove the code that fetches a User and also have this moved into the view controller.

Personally, I'd also not call save() on the object when it's newly created, and instead defer that until some later time (such as when the view controller is exiting).

import UIKit

class ViewController: UIViewController {

    var context: NSManagedObjectContext?

    func someMethodThatCreatesUser() {

        guard let context = context else { return }

        let json: JSON?  // ... grab json
        guard let user = User(in: context, with: json) else { return }

        // do something with the user...
    }

    func someMethodThatGetsExistingUser() -> User? {

        guard let context = context else { return nil }

        let request: NSFetchRequest<User> = User.fetchRequest() 
        // `fetchRequest()` is auto generated by Xcode, if you create
        // your own managed object subclasses you'll need to do some
        // additional work here.

        let users = try? context.fetch(request)
        return users?.first

    }

    deinit {
        guard let context = context else { return }
        context.perform {
            try? context.save()
        }
    }

}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.