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.

Monday, October 9, 2017

Swift NSPredicate with Array

NSPredicate is a class that specify data format. It shows how data to be fetched. If you are familiar with SQL language, you will find it easy to use it. The example below show how it is used in normal array.

First create Person model to use with Array



class Person : NSObject {
    let name : String
    let age : Int
    let salary : Double
    
    init(name:String, age:Int, salary:Double){
        self.name = name
        self.age = age
        self.salary = salary
    }

}

Then create an array of person with some initialize data


let arrPeople = [
            Person(name: "Mary", age: 21, salary: 100),
            Person(name: "Tina", age: 22, salary: 200),
            Person(name: "Kin", age: 23, salary: 300),
            Person(name: "Pal", age: 24, salary: 400),
            Person(name: "Qusa", age: 25, salary: 500)

        ]

Create 3 predicate consisting of Name, Age and Salary


let namePredicate =  NSPredicate(format: "name == \"Qusa\" ", argumentArray: nil)
let agePredicate = NSPredicate(format: "age == 22", argumentArray: nil)
let salaryPredicate = NSPredicate(format: "salary == 300", argumentArray: nil)

Then fetch the result from the 3 predicates using function Filtered


let arrName = (arrPeople as NSArray).filtered(using: namePredicate) as! [Person]
let arrAge = (arrPeople as NSArray).filtered(using: agePredicate) as! [Person]
let arrSalary = (arrPeople as NSArray).filtered(using: salaryPredicate) as! [Person]

Finally print to see the result


print("Predicate name result : \(arrName.first?.name ?? "" ) ")
print("Predicate age result : \(arrAge.first?.name ?? "" )")
print("Predicate salary result : \(arrSalary.first?.name ?? "" )")









Congratulation, you just know how to use NSPredicate with Array. Here is the full source code.


Swift Core Data Create Entity Programmatically

Core Data provides a new layer to work with database in ease. We can create models(Entities) graphically without writing a single line of code. However, if you want to try with code, here is a sample one to try.


First create model to store all entities

let model = NSManagedObjectModel()

Create an entity called Person
let personEntity = NSEntityDescription()
personEntity.name = "Person"

personEntity.managedObjectClassName = "Person"

Create an attribute call Name with type of String
let nameAttribute = NSAttributeDescription()
nameAttribute.name = "name"
nameAttribute.attributeType = .stringAttributeType
nameAttribute.isOptional = true

Add "nameAttribute" to "personEntity"

personEntity.properties.append(nameAttribute)

Finally add "personEntity" to previous model
model.entities.append(personEntity)

Congratulation, you just create an entity programmatically.
Here is the full code.

let model = NSManagedObjectModel()
    
let personEntity = NSEntityDescription()
personEntity.name = "Person"
personEntity.managedObjectClassName = "Person"
        
let nameAttribute = NSAttributeDescription()
nameAttribute.name = "name"
nameAttribute.attributeType = .stringAttributeType
nameAttribute.isOptional = true
        
personEntity.properties.append(nameAttribute)

model.entities.append(personEntity)