Wednesday, October 11, 2017

Swift Core Data Stack Programmatically

Core Data is Apple framework that provides a layer to work with database management that support CRUD (Create, Read, Update, Delete), and  support objects relationship (ORM). There are 3 elements: NSManagedObjectModel, NSManagedObjectContext and NSPersistenceStoreCoordinator.

NSManagedObjectModel manages your model schema structure which includes your entities definition and entities relationship.

NSPersistenceStoreCoordinator ensures the consistence of object model schema and persistence store.

NSManagedObjectContext manages the task of CRUD.

They are called Core Data Stack. However, since iOS 10, Apple introduces NSPersistentContainer which simplify the complexity of core data stack and provide ease of use for developers.
However, if you want to understand the how core data stack work, let's see the following code.


1. Create a class called CoreDataManager
import Foundation
import CoreData
final class CoreDataManager {
}
*Note: It is not important to use "final"

2. Create a property named "model" and init function to that class
private let modelName : String
init(modelName:String) {
        self.modelName = modelName
}

3. Create a function named "createModel"
func createModel() -> NSManagedObjectModel{
        let model = NSManagedObjectModel()
    
        // Entity
        let entity = NSEntityDescription()
        entity.name = "Person"
        entity.managedObjectClassName = "Person"
        
        // Attribute
        var properties : [NSAttributeDescription] = []
        
        let nameAttr = NSAttributeDescription()
        nameAttr.name = "name"
        nameAttr.attributeType = .stringAttributeType
        nameAttr.isOptional = false
        properties.append(nameAttr)
        
        entity.properties = properties
        model.entities = [entity]

        return model
}

4. Create a lazy property called "managedObjectModel"
private lazy var managedObjectModel: NSManagedObjectModel = {
        var objectModel : NSManagedObjectModel?
        var modelURL : URL?
        if let url = Bundle.main.url(forResource: self.modelName, withExtension: "momd"){
            modelURL = url
            return NSManagedObjectModel(contentsOf: modelURL!)!
        }else {
            let fileManager = FileManager.default
            do {
                let documentDirectory = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
                let fileURL = documentDirectory.appendingPathComponent("\(self.modelName).momd")
                let model = self.createModel()
                let modelData = NSKeyedArchiver.archivedData(withRootObject: model)
                try modelData.write(to: fileURL)
                objectModel = model
            }catch let error as NSError {
                print("error")
            }
        }
        
     return objectModel!
}()

5. Create a lazy property called "persistentStoreCoodinator"
private lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
        let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
        
        let fileManager = FileManager.default
        let storeName = "\(self.modelName).sqlite"
        
        let documentsDirectoryURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
        
        let persistentStoreURL = documentsDirectoryURL.appendingPathComponent(storeName)
        
        do {
            try persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType,
                                                              configurationName: nil,
                                                              at: persistentStoreURL,
                                                              options: nil)
        } catch {
            fatalError("Unable to Load Persistent Store")
        }
        
        return persistentStoreCoordinator
}()

6. Finally Create a lazy property called "managedObjectContext"
private(set) lazy var managedObjectContext: NSManagedObjectContext = {
         let managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
         managedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator
        return managedObjectContext
}()

Now let's see how it works:

  1. First we create a simple class named "CoreDataManager" for creating Core Data, actually.
  2. We create a string property to store the name of Core Data, the name will be used for both database and file inside the project. One name to rule them all, maybe.
  3. And we create a function to define a model for our core data. Model contain all entities and relationship. 
  4.  We then call the model from the file path in project in case we already have a .momd file. However, in this case, we don't have any file .momd yet.  So in "Else" block, we create a file that contain model content from "createModel" function. Then save it to project path and return it. 
  5. So persistentStoreCoordinator just fetch a database file which the table schema based on the managed object model we just created in (4).
  6. Lastly, "persistentStoreCoordinator" will give model schema and table schema to "managedObjectContext" for data manipulation (Insert, Delete, Update...).


Now let's see how we use this "CoreDataManager" class. 
  1. In AppDelegate, create a property named "coreDataManager"
    var coreDataManager : CoreDataManager?
  2. Add the following code to this function
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
            self.coreDataManager = CoreDataManager(modelName: "MyCoreData")
            return true
        }
  3. Create a view controller class and add this code to "viewDidLoad"
    class ViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
            
            let appDelegate = UIApplication.shared.delegate as! AppDelegate
            let core = appDelegate.coreDataManager
        
            let e = NSEntityDescription.entity(forEntityName: "Person", in: (core?.managedObjectContext)!)
            let eObj = NSManagedObject(entity: e!, insertInto: core?.managedObjectContext)
            
            eObj.setValue("TestName", forKey: "name")
            
            try! core?.managedObjectContext.save()
            
            let fet = NSFetchRequest<NSManagedObject>(entityName: "Person")
            let q = try! core?.managedObjectContext.fetch(fet)
            
            for i in q! {
                let v = i.value(forKey: "name")
                print("q \(String(describing: v)))")
            }
        }
    }
I don't need to explain the above points since we focus on only Core Data Stack. That's all for Core Data Stack. Here is the full file source code.

1 comment:

  1. If you have any question regarding to this topic or any errors with my content, please give me feedbacks.

    ReplyDelete