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
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.
Select the model file then choose Editor menu and Create NSManagedObject Subclass... There will be two files created:
OperatorLog+CoreDataClass.swift
OperatorLog+CoreDataProperties.swift
@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.
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"
Add the following method declaration to the MyCalculatorStore class above.
The NSFetchRequest object contains search criteria to filter the result.
Run the project then there will be the output in the console as following:
- 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
Reference
https://developer.apple.com/documentation/coredata/making_core_data_your_model_layer
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/KeyConcepts.html
https://www.youtube.com/watch?v=o_UnIG6oYjI
https://developer.apple.com/videos/play/wwdc2016/242/
https://developer.apple.com/library/archive/qa/qa1952/_index.html
https://developer.apple.com/documentation/coredata/making_core_data_your_model_layer
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/KeyConcepts.html
https://www.youtube.com/watch?v=o_UnIG6oYjI
https://developer.apple.com/videos/play/wwdc2016/242/
https://developer.apple.com/library/archive/qa/qa1952/_index.html
No comments:
Post a Comment