1

I am following the current tutorial on creating a large class to handle seeding a large database with data.

http://www.andrewcbancroft.com/2015/02/25/using-swift-to-seed-a-core-data-database/

My database is populated using JSON, though I am wanting to copy the pattern the author uses in the above article.

During the article he mentions this approach violates the single-use responsibility. I am aware that classes should take a single responsibility, but given a situation such as mine where I will need to seed quite a large dataset when the user logs in for example, is there another approach to take?

I apologise if this comes off as inciting a discussion, that isn't my intention, my question is wether this style of seeding is commonplace in production or if not, what is the best pattern to implement this kind of data seeding.

dyatesupnorth
  • 790
  • 4
  • 15
  • 45

1 Answers1

3

I don't think it's possible to really answer how everyone imports data in production as everyone could do different things.

Instead, I just want to mention that according to Apple's "Core Data Programming Guide" the most efficient way to import data is via a batch import process. This process is detailed here.

https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreData/Articles/cdImporting.html

With that said I would store your data in a JSON file that is stored either on a web service, or in the app bundle as a resource, and then use the NSJsonSerialization class to convert it to foundation objects that your code can reason with. Then I would use the principals outlined in the guide above to create a bulk import process to seed your database.

That's pretty much it, and Apple's examples are pretty straight forward. I would also state it would be best to run this process on a background thread as the OS may terminate your application if the import takes a long time to complete.

Hope this helps!

* EDIT *

Here is an example of how you can use protocols and generics to perform tasks against any type of object. You can use this pattern to perform any type of operation, so just take the concept and enter your Core Data logic.

This is merely an example of a pattern one could follow, and shouldn't be considered a plug-in-play implementation. It will need to be adapted to support the Core Data bulk import and saving. However, it does clearly show a way to take a dictionary, or array of dictionaries, and decode them to objects. Then what you do with your objects is completely up to you.

protocol JSONDecodable {

    // This is used so you can have a unified way to instantiate an instance without relying on sub-classing NSObject
    init()

    // This can be implemented so you can manually decode a single object of the type from a dictionary
    static func decodeFromJSON(json: AnyObject?) -> AnyObject?

    // This is used so that you can manually set the values to the object. This is required as Swift reflection doesn't support dynamic property setting
    func setValueForKey(value: AnyObject?, forKey: String)
}

// This class is responsible for decoding a JSON dictionary into an object
class JSONDecoder<T:JSONDecodable>: NSObject {

    //MARK: Initialization

    required override init() {
        // do any initialization here
    }

    //MARK: Public Methods

    /**
        Creates a single object from the JSON. This method should only be used if the JSON will only ever contain a single object

        :json: A dictionary of data
        :returns: An object of the given type
    */
    func toSingle(json: AnyObject?) -> T? {

        // process single object, and return an array with the one object
        if let dict = json as? [NSObject: AnyObject] {
            return self.makeObject(dict)
        }

        return nil
    }

    /**
        Creates a list of objects from the JSON. This method should only be used if the JSON will contain multiple objects

        :json: A dictionary of data
        :returns: An list of objects of the given type
    */
    func toArray(json: AnyObject?) -> [T]? {

        // process array
        if let arr = json as? [AnyObject] {
            return self.makeObjects(arr)

        } else if let dict = json as? [NSObject: AnyObject] {
            // process single object, and return an array with the one object
            var arr = [T]()
            arr.append(self.makeObject(dict))
            return arr
        }

        return nil
    }

    //MARK: The Magic

    private func makeObjects(jsonArray: [AnyObject]?) -> [T]? {

        var returnArray: [T] = [T]()

        if let jArray = jsonArray {
            for jObject in jArray {
                if let dict = jObject as? [NSObject: AnyObject] {
                    returnArray.append(self.makeObject(dict))
                }
            }
        }

        if returnArray.count > 0 {
            return returnArray
        } else {
            return nil
        }
    }

    private func makeObject(jsonDict: [NSObject: AnyObject]) -> T {

        var returnObject = T.self() // this is where the init() function in the protocol comes in handy. It allows us to use generics to create a dynamic instance of our object

        for (key, value) in jsonDict {
            if let k = key as? String {
                returnObject.setValueForKey(value, forKey: k) // this is where the setValueForKey(value: AnyObject?, forKey: String) function in the protocol comes in handy. It allows us to let the object it's self set it's own values.
            }
        }

        return returnObject
    }
}

// This is an example class that implements the protocol which means it can be sent through the decoding process
class Employee: NSManagedObject, JSONDecodable {

    //MARK: - Properties
    var employeID: Int!
    var name: Int!
    var hireDate: NSDate?
    var department: Department?

    //MARK: - Initialization
    override required init() {
        // Necessary to satisfy the JSONDecodable protocol
    }

    static func decodeFromJSON(json: AnyObject?) -> AnyObject? {

        var decoder = JSONDecoder<Employee>()
        return decoder.toSingle(json)
    }

    func setValueForKey(value: AnyObject?, forKey: String) {

        switch (forKey) {
        case "employeID":
            self.employeID = value as! Int

        case "name":
            self.name = value as! String

        case "hireDate":

            if let v = value as? String {
                let dateFormatter = NSDateFormatter()
                dateFormatter.dateFormat = "MM/dd/yyyy"
                self.hireDate = dateFormatter.dateFromString(v)
            }

        case "department":
            if let v = value as? [NSObject: AnyObject] {

                if let dept = Department.decodeFromJSON(dict) as? Department {
                    self.department = dept
                }
            }

        default:
            NSLog("[setValueForKey] Unable to find property \(forKey)")
        }
    }
}
Josh Crozier
  • 233,099
  • 56
  • 391
  • 304
miken.mkndev
  • 1,821
  • 3
  • 25
  • 39
  • thanks, yes im already doing all of that, on a background thread too. I'm just at a point where i am seeding the rest of my DB (I got a couple of entities working great, now its time to fill in the rest). I was wondering if it was ok to do all of this in one class, or if there was a better approach to take? If you need an example of what i mean exactly let me know :) – dyatesupnorth Jul 24 '15 at 13:29
  • Ok, I think I get your jist now. What I would personally do is utilize Swift's awesome protocols and generics implementation to create a single class that can decode a list of any NSManagedObject and insert into the database. Once this is done you can then call it and pass in a list of your data objects. I know this isn't a direct answer as you may have no idea what I'm talking about, but I will update my post with an example. – miken.mkndev Jul 24 '15 at 13:35
  • Thats cool, yeah i was looking for something like that. Though I am using SwiftyJson which i think handles most of this sort of thing. Awesome answer though thanks – dyatesupnorth Jul 24 '15 at 13:59