Thursday, January 31, 2019

Swift 3's Core Data: Using A Separate Database File For Unit Tests

I'm developing a pet project and I have to populate data when launching the app in both Simulator and real devices. I also want to keep the data inputted while I'm manually testing the app on the Simulator. Then, it's a bit difficult for unit test. I can't let the unit tests modify or delete the pre-populated data and the inputted ones. I have to input the data differently for the unit tests and delete it back when the tests are finished. If I want to count the number of records of an entity in the store, I have to give the unit-test data negative identifiers to separate them from the pre-populated and the manually inputted ones within the app. I think it's better to have a separate database file for the unit tests alone.

In Xcode 9, when creating a Single View Application, the datastore is configured by default. The persistentContainer property (an instance of NSPersistentContainer class) is initialized in AppDelegate class. The persistentContainer encapsulates the Core Data stack in the app. It handles creation of the managed object, the persistent store coordinator, and the managed object context.

The persistentContainer object has a function called defaultDirectoryURL(), which defines the location of the datastore file. I'm going to subclass NSPersistentContainer and override this function to change the datastore's file location for unit tests.

1. Creating main.swift file


Before that, we have to comment out the @UIApplicationMain attribute on the AppDelegate class.


import Foundation
import UIKit

let isRunningTest = NSClassFromString("XCTestCase") != nil
let appDelegateClass = isRunningTest ? NSStringFromClass(UnitTestAppDelegate.self) : NSStringFromClass(AppDelegate.self)

UIApplicationMain(
    CommandLine.argc,
    UnsafeMutableRawPointer(CommandLine.unsafeArgv)
        .bindMemory(
            to: UnsafeMutablePointer.self,
            capacity: Int(CommandLine.argc)),
    nil,
    appDelegateClass
)
 

When launching the app, the system looks for @UIApplicationMain attribute on a class. Xcode adds the attribute on the AppDelegate class by default. The @UIApplicationMain acts as a main function, which initializes the UIApplication object (represents the current app instance) and its delegate (responds to important events in the app's life cycle) at run time.

According to Apple's documentation, if we don't use @UIApplicationMain, we must create a main.swift file at the top level that calls the NSApplicationMain(_:_:_:_) function. The third parameter is the name of UIApplication class or subclass. In the code snippets above, it's set to nil so UIApplication is assumed. The fourth parameter is the name of AppDelegate class or subclass. If the unit test is running (if the XCTestCase class exists in the bundle or not nil), UnitTestPersistentContainer (my custom class) is assigned. Otherwise, the default AppDelegate class is assigned.

2. Creating UnitTestAppDelegate class


import Foundation
import CoreData

class UnitTestAppDelegate : AppDelegate {
    
    override var persistentContainer: NSPersistentContainer {
        get {
            let container = UnitTestPersistentContainer(name: "MyCalculator")
            container.loadPersistentStores(completionHandler: { (storeDescription, error) in
                if let error = error as NSError? {

                    fatalError("Unresolved error \(error), \(error.userInfo)")
                }
            })
            return container
        }
        set {
            super.persistentContainer = newValue
        }
    }
}
 

3. Creating UnitTestPersistentContainer class

    
import Foundation
import CoreData

class UnitTestPersistentContainer : NSPersistentContainer {
    
    open class override func defaultDirectoryURL() -> URL {
        return URL(fileURLWithPath: NSHomeDirectory() + "/Documents")
    }
}
 

Note that the datastore file couldn't be created when I specified "~/Documents" or "/var/TMP" for the fileURLWithPath parameter. I don't know why.



References
https://developer.apple.com/documentation/coredata/nspersistentcontainer



Tuesday, January 29, 2019

Persisting an Array of Custom Objects with Core Data

This blog is about persisting an entity, which its attribute is an array of custom objects. Unlike the example I wrote here, the custom object does not has its own entity representation in the store. I'm using Xcode 9 and Swift 3.

1. Creating Entity


Open the Xcode model file, MyCalculator.xcdatamodeld in my case and create the entity as shown in the image below.



After that, select the model file again and choose Editor menu then Create NSManagedObject Subclass.... Xcode will then generate two files below.

HistoryMO-CoreDataClass.swift

import Foundation
import CoreData

public class HistoryMO: NSManagedObject {

}
 

HistoryMO-CoreDataProperties.swift

import Foundation
import CoreData

extension HistoryMO {

    @nonobjc public class func fetchRequest() -> NSFetchRequest {
        return NSFetchRequest(entityName: "HistoryMO")
    }

    @NSManaged public var operations: [AbstractExpressionMO]
    @NSManaged public var result: Double
}
 

2. Create the Custom Classes


Core Data converts an object into a type that it can store when saving and convert it back to the original object when restoring from the file system. A transformable attribute requires a NSValueTransformer subclass. The default transformer is NSKeyedUnarchiveFromDataTransfer class, which converts the object to and from NSData but the transformed object must conform to NSCoding protocol. If it does not conform to NSCoding protocol, we should write a custom NSValueTransformer subclass.

In my example, the custom classes I created conform to NSCoding protocol.


import Foundation
import CoreData

public class AbstractExpressionMO : NSObject, NSCoding {

    public override init() {
        super.init()
    }
    
    public required init?(coder aDecoder: NSCoder) {
        self.init()
    }
    
    public func encode(with aCoder: NSCoder) {
    }
}
 

The AbstractExpressionMO, as its name implies, is not intended for directly persisted. Since Swift does not support abstract class like in Java, I declared it as a class with empty methods. It extends from NSObject because Core Data requires the persisted object to be of type NSObject.

I added an initializer without parameter for the subclasses to call because Swift requires that all designated initializers of a subclass must call a designated initializer of its superclass.


public class NumberMO : AbstractExpressionMO {    
    fileprivate static var attrValue:String = "value"
    
    var value:Double?
    
    public init(_ value:Double?) {
        super.init()
        self.value = value
    }
    
    public convenience required init?(coder aDecoder: NSCoder) {
        let obj = aDecoder.decodeObject(forKey: NumberMO.attrValue)
        if obj != nil {
            self.init(obj as! Double)
        } else {
            // A void error when a column value is null
            self.init(nil)
        }
    }
    
    public func decode(coder aDecoder: NSCoder) {
        let obj = aDecoder.decodeObject(forKey: NumberMO.attrValue)
        if obj != nil {
            value = obj as! Double
        }
    }
    
    public override func encode(with aCoder: NSCoder) {
        aCoder.encode(value, forKey: NumberMO.attrValue)
    }
}
 

In NumberMO subclass, the convenience keyword was added for the initializer init(coder:) to force it to call one of the NumberMO's designated initializers. In my case, I want it to call init(_:Double?) initializer because I don't have to duplicate the codes to assign the value to a property.

I used NSCoder.decodeObject(forKey:) instead NSCoder.decodeDouble(forKey:) to avoid the app crash if the column value in the store is null.

    
public class OperatorMO : AbstractExpressionMO {
    fileprivate static var attrTag:String = "tag"
    
    var tag:Int?
    
    public init(_ tag:Int?) {
        super.init()
        self.tag = tag
    }

    public convenience required init?(coder aDecoder: NSCoder) {
        let tag = aDecoder.decodeObject(forKey: OperatorMO.attrTag)
        if tag != nil {
            self.init(tag as! Int)
        } else {
            // A void error when a column value is null
            return nil
        }
    }
    
    public override func encode(with aCoder: NSCoder) {
        aCoder.encode(tag, forKey: OperatorMO.attrTag)
    } 
}
 

Then, I just created the instances of NumberMO and OperatorMO classes and added them to the HistoryMO's operations properties and persisted them along with the HistoryMO object.

For how to write the object to the store, read this post.



Friday, January 25, 2019

Core Data with Swift's Generics

It requires a lot of codes to do CRUD (Create, Read, Update, and Delete) operations on an entity. If there are a lot of entities, we would end up writing hundreds of lines of duplicate codes. With Swift's Generics, we only write the CRUD methods once and are reused by with any entities.

GenericDao.swift
    
import Foundation
import CoreData

public class GenericDao {
    private var context: NSManagedObjectContext!
    
    public init(_ context: NSManagedObjectContext) {
        self.context = context
    }
    
    public func newManagedObject() throws -> T {
        var savedEntity:T!
        context.performAndWait {
            savedEntity = NSEntityDescription.insertNewObject(forEntityName: T.entity().name!, into: context!) as! T
        }
        return savedEntity
    }
    
    public func saveChanges() throws {
        var error: Error?
        if (context?.hasChanges)! {
            do {
                try context?.save()
            } catch let saveError {
                error = saveError
            }
        }
        if let e = error {
            throw e
        }
    }
    
    public func getAll(includePendingChanges:Bool = false) throws -> [T] {
        var result:[T] = []
        
        var error: Error?
        context?.performAndWait {
            let req:NSFetchRequest = T.fetchRequest() as! NSFetchRequest
            req.includesPendingChanges = includePendingChanges
            do {
                result = (try context?.fetch(req) as? [T])!
            } catch let saveError {
                error = saveError
            }
        }
        if let e = error {
            throw e
        }
        
        return result
    }
    
    public func get(orderBy attrName:String? = nil, ascending:Bool = true, offset:UInt? = 0, limit:UInt? = nil, includePendingChanges:Bool = false) throws -> [T] {
        var result:[T] = []
        
        var error:Error?
        context?.performAndWait {
            let req:NSFetchRequest = T.fetchRequest() as! NSFetchRequest
            req.includesPendingChanges = includePendingChanges
            req.fetchOffset = Int(offset!)
            
            if limit != nil {
                req.fetchLimit = Int(limit!)
            }
            
            let sortDescriptor = NSSortDescriptor(key: attrName, ascending: ascending)
            req.sortDescriptors = [sortDescriptor]
            
            do {
                result = (try context?.fetch(req) as? [T])!
            } catch let saveError {
                error = saveError
            }
        }
        if let e = error {
            throw e
        }
        return result
    }
    
    public func deleteAll() throws {
        var error:Error?
        context?.performAndWait {
            let req = NSBatchDeleteRequest(fetchRequest: T.fetchRequest())
            do {
                try context?.execute(req)
            } catch let saveError {
                error = saveError
            }
        }
        if let e = error {
            throw e
        }
    }
    
    public func delete(by attrName:String, _ attrValue:[Int]) throws {
        var error:Error?
        context?.performAndWait {
            let fetchReq = T.fetchRequest()
            fetchReq.predicate = getPredicate(attrName, attrValue)
            let req = NSBatchDeleteRequest(fetchRequest: fetchReq)
            
            do {
                try context?.execute(req)
            } catch let saveError {
                error = saveError
            }
        }
        if let e = error {
            throw e
        }
    }
    
    private func getPredicate(_ attrName:String, _ attrValue:[Int]) -> NSPredicate {
        return NSPredicate(format: "\(attrName) IN %@", attrValue)
    }
}
 

Wednesday, January 23, 2019

Browsing SQLite's data managed by Swift's Core Data framework

To see the location of the SQLite's database file, go to Product > Scheme > Edit Scheme... then select Run and add the following arguments.
    
-com.apple.CoreData.SQLDebug 1
-com.apple.CoreData.Logging.stderr 1
 



There is a free app to browse the database here https://sqlitebrowser.org/. I uploaded the app to my server here.

Or we can use the sqlite command which is built-in in OSX to enter SQLite's command shell by running this command:

sqlite3 ~/Library/Developer/CoreSimulator/Devices/8BDECFA8-1DD0-4EA8-8F3B-75B7EF61A351/data/Containers/Data/Application/8AF0221B-152B-4A61-B8AA-D3F9D0CBED6E/Library/Application Support/CoreDataTest.sqlite
 

SQLite's Commands


Query table's names

sqlite> .tables
ZEQUATION     ZOPERATORLOG  Z_METADATA    Z_MODELCACHE  Z_PRIMARYKEY
 

Show table's schema

sqlite> .schema zoperatorlog
CREATE TABLE ZOPERATORLOG ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZOPERATORTAG INTEGER, ZOPERAND FLOAT );
 

Exit the command shell

> .exit
 

Select statement
The column's names must be specified.

sqlite> select z_pk, zoperatortag from zoperatorlog
 



References
https://wiki.genexus.com/commwiki/servlet/wiki31044,How+To%3A+Query+SQLite+database+from+an+iOS+Offline+App
https://stackoverflow.com/questions/24133022/sqlite-file-location-core-data/47402447#

Parent-Child Relationship in Core Data

I'm writing this example project (CoreDataTest) using Xcode 9 and Swift 3. I have two classes, one is parent and another one is child. I want to add an array of child objects as a property of the parent class and then save them to the store.

1. CoreDataTest.xcdatamodeld 


Parent entity (Equation)




Child entity (OperatorLog)



2. The Model Classes


I let Xcode generate the swift files below.

Equation+CoreDataClass.swift

import Foundation
import CoreData

@objc(Equation)
public class Equation: NSManagedObject {

}
 

Equation+CoreDataProperties.swift

import Foundation
import CoreData

// MARK: Generated accessors for operatorLogs
extension Equation {
 @nonobjc public class func fetchRequest() -> NSFetchRequest {
        return NSFetchRequest(entityName: "Equation")
    }

    @NSManaged public var operatorLogs: NSSet?

    @objc(addOperatorLogsObject:)
    @NSManaged public func addToOperatorLogs(_ value: OperatorLog)
    

    @objc(removeOperatorLogsObject:)
    @NSManaged public func removeFromOperatorLogs(_ value: OperatorLog)

    @objc(addOperatorLogs:)
    @NSManaged public func addToOperatorLogs(_ values: NSSet)
    
    @objc(removeOperatorLogs:)
    @NSManaged public func removeFromOperatorLogs(_ values: NSSet)
}
 

OperatorLog+CoreDataClass.swift

import Foundation
import CoreData

public class OperatorLog: NSManagedObject {
    
}
 

OperatorLog+CoreDataProperties.swift

import Foundation
import CoreData

extension OperatorLog {

    @nonobjc public class func fetchRequest() -> NSFetchRequest {
        return NSFetchRequest(entityName: "OperatorLog")
    }
    
    @NSManaged public var operatorTag: Double
    @NSManaged public var operand: Double
    @NSManaged public var result: Double
}
 

3. The Datastore's methods


I created a class named Datastore and added the following method:

    func saveEquation(_ operatorLogs: [OperatorLog]) throws -> Equation {
        var savedEquation:Equation!
        context?.performAndWait {
            savedEquation = NSEntityDescription.insertNewObject(forEntityName: "Equation", into: context!) as! Equation
            savedEquation.addToOperatorLogs(NSSet(arrayLiteral: operatorLogs))
        }
        
        var error: Error?
        if (context?.hasChanges)! {
            do {
                try context?.save()
            } catch let saveError {
                error = saveError
            }
        }
        if let e = error {
            throw e
        }
        return savedEquation
    }
 

4. Calling the method


In the ViewController's viewDidLoad() method, I retrieved the OperatorLog objects from the store I previously saved and added it to the Equation object then persisted the Equation object.

        do {
            let operatorLogs:[OperatorLog] = try datastore!.getOperatorLogs()
            try datastore?.saveEquation(operatorLogs)
        } catch let e {
            print("\(e.localizedDescription)")
        }
        
        do {
            let entities:[Equation] = try datastore!.getEquations()
            for entity in entities {
                var equation = ""
                for child in entity.operatorLogs! {
                    let opLog = child as! OperatorLog
                    equation.append(String(describing: opLog.operand))
                    equation.append(String(describing: opLog.operatorTag))
                }
                print("> Equation: \(equation)")
            }
        } catch let e {
            print("\(e.localizedDescription)")
        }
 

Error


The example above is the correct way to implement parent-child relationship in Core Data but the app crashed when Swift tried to save changes to the store. Here is the error message:
 "CoreDataTest[31184:16565250] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_TtGCs23_ContiguousArrayStorageC12CoreDataTest11OperatorLog_ _isKindOfEntity:]: unrecognized selector sent to instance"

I looked up on the net and they said that it was a bug with Core Data. Apple hasn't solved it yet up until now.

Solution


According to the book "iOS Programming - The Big Nerd Ranch Guide, 5th Edition", the author added the child property manually and used Set instead of NSSet as the type of the child object. Then, I left the Equation's operatorLogs property as NSSet but changed the type of the parameter of the generated method addToOperatorLogs(_:) to Set<OperatorLog> and it worked.
@NSManaged public func addToOperatorLogs(_ values: Set<OperatorLog>)

Of course, I also had to update the saveEquation() method too on the following line.
savedEquation.addToOperatorLogs(Set(operatorLogs))





Tuesday, January 22, 2019

Introduction to CoreData using Swift 3

Core Data framework reduces a hug amount of codes to store data on an iOS device. There are four types of datastore we can use with Core Data:
1. XML: readable and suitable for simple data
2. Binary:
3. SQLite: built-in and suitable for complex data like object graphs
4. In-memory:

To integrate Core Data into the app, we can either create traditional Core data stack or initialize NSPersistentContainer. Using Xcode 9, NSPersistentContainer is initialized by default in AppDelegate class when creating a SingleViewApplication then you can access the NSPersistentContainer object from a ViewController to persist or access the data to or from the store.

Sample App


1. Create a SingleViewApplication in Xcode (v9)


The persistentContainer property is added to the AppDelegate class as following:

    lazy var persistentContainer: NSPersistentContainer = {
        /*
         The persistent container for the application. This implementation
         creates and returns a container, having loaded the store for the
         application to it. This property is optional since there are legitimate
         error conditions that could cause the creation of the store to fail.
        */
        let container = NSPersistentContainer(name: "CoreDataTest")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                 
                /*
                 Typical reasons for an error here include:
                 * The parent directory does not exist, cannot be created, or disallows writing.
                 * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                 * The device is out of space.
                 * The store could not be migrated to the current model version.
                 Check the error message to determine what the actual problem was.
                 */
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()

    // MARK: - Core Data Saving support

    func saveContext () {
        let context = persistentContainer.viewContext
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                let nserror = error as NSError
                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
            }
        }
    }
 

Another file with the extension "xcdatamodeld" is created too. It's a configuration file for create and map your model classes to the tables in the store (the same concept as Hibernate's mapping files).

2. Configure the model file


Suppose we haven't created the model classes manually. Select the model file, CoreDataTest.xcdatamodeld in my case, and add a new entity named OperatorLog as following:


Under the Class settings in the Data Model Inspector:
- Set Module to Current Product Module - it's like a namespace for the model class. Without the namespace, I got the compile-time error "Class not found, using default NSManagedObject instead."
- Set Codegen to Manual/None - the default value is Class Definition, which means that Xcode creates and manages the model classes and extensions and hide them somewhere so that the developers can't modify them manually (any changes the developers want to make, make it in the model file then Xcode will update the classes and extensions). Manual/None tells Xcode to leave the work of creating and modifying the classes and extensions to the developers.

3. Generate the model class or entity


Select the model file then choose Editor menu and Create NSManagedObject Subclass... There will be two files created:

OperatorLog+CoreDataClass.swift

import Foundation
import CoreData

public class OperatorLog: NSManagedObject {

}
 

OperatorLog+CoreDataProperties.swift

import Foundation
import CoreData

extension OperatorLog {

    @nonobjc public class func fetchRequest() -> NSFetchRequest {
        return NSFetchRequest(entityName: "OperatorLog")
    }

    @NSManaged public var operatorTag: Double
    @NSManaged public var operand: Double

}
 

@NSManaged annotation is used to shut up the compiler because Swift does not allow an extension to contain a stored property (hard to manage and allocate memory for a type if they're in different module). Note that I moved the properties to OperatorLog class then removed the annotation, and the record was saved in the store but all the attributes had no values.

4. Implement the method to save data



import Foundation
import CoreData

class MyCalculatorStore {
    let context:NSManagedObjectContext?
    
    init(_ context:NSManagedObjectContext) {
        self.context = context
    }
    
    func save(_ operand:Double?, _ operatorTag:Double) throws -> OperatorLog {
        var savedEntity:OperatorLog!
        context?.performAndWait {
            savedEntity = NSEntityDescription.insertNewObject(forEntityName: "OperatorLog", into: context!) as! OperatorLog
            if operand != nil {
                savedEntity.operand = operand!
                savedEntity.operatorTag = operatorTag
            }
        }
        
        var error: Error?
        if (context?.hasChanges)! {
            do {
                try context?.save()
            } catch let saveError {
                error = saveError
            }
        }
        if let e = error {
            throw e
        }
        return savedEntity
    }
}
 

The NSManagedObject object requires two elements: an entity description (NSEntityDescription object) and a managed object context (a NSManagedObjectContext object). The entity description includes the name of the entity it represents and its attributes and relationships. The managed object context tracks changes to and relationships between objects (like a session object in Java).

The performAndWait(_:) method execute the block operations synchronously. In other words, the method does not return until the block is executed. If we want the method return immediately without waiting for the block is executed, we should use perform(_:) method instead.

The NSEntityDescription.insertNewObject(forEntityName:) method creates a new managed object in the context. Without explicitly calling the save() method of the NSManagedObjectContext, the object is not persisted. When the managed object is firstly created in the context, it is assigned a temporary ID. A new ID is assigned after it's been persisted to the store. The ID is assigned to objectID property (which is of type NSManagedObjectID). I printed the ID out and it looks like this "0xd000000000080000 <x-coredata://9848B21C-A886-4A18-BA35-44AB8E4CAAF1/OperationMO/p2"


5. Implement the method to retrieve data


Add the following method declaration to the MyCalculatorStore class above.

    func getOperatorLogs() throws -> [OperatorLog] {
        var result:[OperatorLog] = []
        
        var error: Error?
        context?.performAndWait {
            let req:NSFetchRequest = OperatorLog.fetchRequest()
            do {
                result = (try context?.fetch(req) as? [OperatorLog])!
            } catch let saveError {
                error = saveError
            }
        }
        if let e = error {
            throw e
        }
        
        return result
    }
 

The NSFetchRequest object contains search criteria to filter the result.

6. Calling the methods from within ViewController



import UIKit

class ViewController: UIViewController {
    let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    
    var datastore:MyCalculatorStore?

    override func viewDidLoad() {
        super.viewDidLoad()
        
        datastore = MyCalculatorStore(context)

        do {
            try datastore?.save(21.342, 117)
            try datastore?.save(837.2, 118)
        } catch let e {
            print("\(e.localizedDescription)")
        }
        
        do {
            let operatorLogs:[OperatorLog] = try datastore!.getOperatorLogs()
            for entity in operatorLogs {
                print("> operand=\(entity.operand), tag=\(entity.operatorTag)")
            }
        } catch let e {
            print("\(e.localizedDescription)")
        }
    }
}
 

Run the project then there will be the output in the console as following:

> operand=21.342, tag=117.0
> operand=837.2, tag=118.0
 



Sunday, January 13, 2019

Dealing with Collections in Swift 3

Declaring an array:


var arr = [1, 2, 3, 4, 5]
       

Removing subrange:


var arr = ["a", "b", "c", "d", "e"]
print("> Before: arr=\(arr)")
arr.removeSubrange(3..<arr.count)
print("> After: arr=\(arr)")       
 
Result:
    
> Before: arr=["a", "b", "c", "d", "e"]
> After: arr=["a", "b", "c"]     
 
The lower bound and upper bound of the subrange operator "..<" must be positive and not greater than the array's max index, respectively.