Sometimes applications are required to save and manipulate user data. iOS SDK provides a framework for this purpose known as Core Data.
The Core Data framework provides comprehensive and automated solutions related to an object's life cycle and its searching and persistence features. It can retrieve and manipulate data purely on an object level without having to worry about the details of storage and retrieval.
With Core Data, data can be handled using higher-level objects indicating entities and their relationships. Core Data interfaces directly with SQLite, separating the developer from the underlying SQL.
So does it mean Core Data is a database? No; Core Data is not a database and the best example of this is that Core Data can be used totally in memory without any form of persistence. Then is Core Data similar to an ORM such as Active Record or Hibernate? No; Core Data is an object graph manager with life cycle, searching, and persistence features. With Core Data, an app can define a database schema, create a database file, and create and manage record data.
We will create a simple employee application that will allow us to add the name and age of an employee. This example is only used to demonstrate how Core Data works:
motion
command:$motion create CoreDataExample
CoreData
framework in the rake.rb
file:app.frameworks += [‘CoreData']
employee.rb
in the app
folder:class Employee < NSManagedObject #Attribute Name, Data Type, Default Value, Is Optional, Is Transient, Is Indexed @attributes ||= [ [‘name', NSStringAttributeType, ‘', false, false, false], [‘age', NSInteger32AttributeType, 0, false, false, false] ] end
You must have noticed that we have inherited the Employee
class from NSManagedObject
. We have created an array of arrays for attributes in the employee
table with the attributes name
and age
. You must be wondering what other parameters there are in this array. To understand this, we will have to write a few helpers in our application.
helper
and add a file named NSEntityDescription.rb
with the following code in it:class NSEntityDescription def self.newEntityDescriptionWithName(name, attributes:attributes) entity = self.alloc.init entity.name = name entity.managedObjectClassName = name attributes = attributes.each.map do |name, type, default, optional, transient, indexed| property = NSAttributeDescription.alloc.init property.name = name property.attributeType = type property.defaultValue = default if default != nil property.optional = optional property.transient = transient property.indexed = indexed property end entity.properties = attributes entity end end
The attributes that we have created in the employee model are defined through this class. For each attribute, the NSAttributeDescription
class will be used to define them. The NSAttributeDescription
class is used to describe attributes of an entity described by an instance of NSEntityDescription
. It is inherited from NSPropertyDescription
, which provides most of the basic behavior. Instances of NSAttributeDescription
are used to describe attributes, as distinct from relationships. We can define many properties for an object of NSAttributeDescription
; for example, we can put a validation on it, we can index the attribute, and much more.
NSManagedObject.rb
in the app
folder and add the following code:def self.entity @entity ||= NSEntityDescription.newEntityDescriptionWithName(name, attributes:@attributes) end def self.objects # Use if you do not want any section in your table view @objects ||= NSFetchRequest.fetchObjectsForEntityForName(name, withSortKey:@sortKey, ascending:false, inManagedObjectContext:Store.shared.context) end end class NSManagedObject def self.entity @entity ||= NSEntityDescription.newEntityDescriptionWithName(name, attributes:@attributes) end def self.objects # Use if you do not want any section in your table view @objects ||= NSFetchRequest.fetchObjectsForEntityForName(name, withSortKey:@sortKey, ascending:false, inManagedObjectContext:Store.shared.context) end end
An NSEntityDescription
object describes an entity in Core Data. An entity to a manage object is what a class is to an ID or, to use a database analogy, what tables are to rows. An NSEntityDescription
object may have NSAttributeDescription
and NSRelationshipDescription
objects that represent the properties of the entity in the schema. An entity may also have fetched properties, represented by instances of NSFetchedPropertyDescription
, and the model may have fetched request templates, represented by instances of NSFetchRequest
.
app_delegate.rb
file:class AppDelegate def application(application, didFinishLaunchingWithOptions:launchOptions) setting_core_data true end def setting_core_data # First we need to create the NSManagedObjectModel with all the entities and their relationships. managed_object_model = NSManagedObjectModel.alloc.init managed_object_model.entities = [Employee.entity] # The next object needed is the NSPersistentStoreCoordinator which will allow Core Data to persist the information. persistent_store_coordinator = NSPersistentStoreCoordinator.alloc.initWithManagedObjectModel(managed_object_model) # Now lets get a URL for where we want Core Data to create the persist file, in this case a SQLite Database File persistent_store_file_url = NSURL.fileURLWithPath(File.join(NSHomeDirectory(), ‘Documents', ‘EmployeeStore.sqlite')) error_pointer = Pointer.new(:object) # Add a new Persistent Store to our Persistent Store Coordinator which means that we are telling the Persistent Store Coordinator where to perform the save of our objects. # In this case we are stating that our objects must be stored in a SQLite database in the path we already created previously unless persistent_store_coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: persistent_store_file_url, options: nil, error: error_pointer) # In case we can't initialize the Persistance Store File raise "Cannot initialize Core Data Persistance Store Coordinator: #{error_pointer[0].description}" end # Finally our most important object, the Managed Object Context, is responsible for creating, destroying, and fetching the objects @managed_object_context = NSManagedObjectContext.alloc.init @managed_object_context.persistentStoreCoordinator = persistent_store_coordinator end end
Till now we have done some basic settings that we are required to do before actually using database operations. In this case, we are stating that our objects must be stored in a SQLite database at a location we define in our code with the filename EmployeeStore.sqlite
.
In the preceding code, we have created an object of NSManagedObjectModel
with all the entities. You can think of this object as a reference of the objects to be used by Core Data. The next object needed is the NSPersistentStoreCoordinator
object that will allow Core Data to persist the information. It is also responsible for choosing a location to save our objects.
In the last part of our code, we have used the most important class, which is the NSManagedObjectContext
class. This class is responsible for creating, destroying, and fetching the objects. An instance of NSManagedObjectContext
represents a single "object space" or scratch pad in an application. Its primary responsibility is to manage a collection of managed objects. These objects form a group of related model objects that represent an internally consistent view of one or more persistent stores. A single managed object instance exists in one and only one context, but multiple copies of an object can exist in different contexts.
$rake
app_delegate
file to accommodate the controller and view with the following code:def application(application, didFinishLaunchingWithOptions:launchOptions) setting_core_data employee_view_controller = EmployeeViewController.alloc.init # We need to pass the Managed Object Context to the next controller so we can use it later for creating, fetching or deleting objects employee_view_controller.managed_object_context = @managed_object_context @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds) @window.rootViewController = UINavigationController.alloc.initWithRootViewController(employee_view_controller) @window.makeKeyAndVisible true end
In the last part of the previous code snippet, we initialized EmployeeViewController
. Next, we will pass the managed object context to the next controller that will later be used for either creating, fetching, or deleting objects. And in the end, we will create a window and assign EmployeeViewController
as its root controller:
employee_view_controller.rb
in the app
folder with the following code in it:class EmployeeViewController < UIViewController attr_accessor :managed_object_context def loadView # Set up the title for the View Controller self.title = ‘Employee' # Create a new Table View for showing the Text Fields table_view = UITableView.alloc.initWithFrame(UIScreen.mainScreen.bounds, style:UITableViewStyleGrouped) table_view.dataSource = self self.view = table_view # Create a new Bar Button Item with the Add System Default add_new_employee_item= UIBarButtonItem.alloc.initWithBarButtonSystemItem(UIBarButtonSystemItemAdd, target: self, action: ‘add_new_employee') # Add the Bar Button Item to the Navigation Bar self.navigationItem.rightBarButtonItem = add_new_employee_item end def viewWillAppear(animated) super reload_data end
NSFetchRequest
object. We also need to tell Core Data which entity we want to retrieve. This can be done using NSEntityDescription
:def reload_data fetch_request = NSFetchRequest.alloc.init entity = NSEntityDescription.entityForName(Employee.name, inManagedObjectContext:@managed_object_context) fetch_request.setEntity(entity) # Sort the Employee by employee name fetch_sort = NSSortDescriptor.alloc.initWithKey(‘name', ascending: true) fetch_request.setSortDescriptors([fetch_sort]) # Update the fetch employee array and reload the table view update_fetched_employee_with_fetch_request(fetch_request) end def update_fetched_employee_with_fetch_request(fetch_request) # Create a new pointer for managing the errors error_pointer = Pointer.new(:object) # Using the NSManagedObjectContext execute the fetch request @fetched_employee = @managed_object_context.executeFetchRequest(fetch_request, error: error_pointer) # If the returning array of the fetch request is nil # means that a problem has occurred unless @fetched_employee raise "Error fetching employee: #{error_pointer[0].description}" end # refresh table view to reload its data self.view.reloadData end # UITableView Data Source def tableView(tableView, numberOfRowsInSection: section) @fetched_employee.count end def tableView(tableView, cellForRowAtIndexPath: indexPath) cell_identifier = ‘EmployeeCell' cell = tableView.dequeueReusableCellWithIdentifier(cell_identifier) # If we are not cells to use we need to create one if cell == nil # Lets create a new UITableViewCell with the identifier cell = UITableViewCell.alloc.initWithStyle(UITableViewCellStyleValue1, reuseIdentifier:cell_identifier) cell.selectionStyle = UITableViewCellSelectionStyleNone end employee = @fetched_employee[indexPath.row] cell.textLabel.text = employee.name cell.detailTextLabel.text = employee.age.to_s cell end def add_new_employee add_employee_view_controller = AddEmployeeViewController.alloc.init # We need to pass the Managed Object Context to the next controller so we can use it later for creating, fetching or deleting objects add_employee_view_controller.managed_object_context = @managed_object_context self.navigationController.pushViewController(add_employee_view_controller, animated:true) end end
That's a lot of code; let's try to understand it. First, we created a tableView
to create a table as it's the best way to represent this type of data. Then, we created a + button at the top of the navigation bar with the add_new_employee
action associated with it. When this button is pressed, it calls the add_new_employee
action that, in turn, calls a new view, shows a form, and adds a new employee.
Then, we created a reload_data
method that will be called to refresh the view with employee data. It will fetch the employee data using the NSFetchRequest
object. Then, we declared NSEntityDescription
for the Employee
object so we can tell Core Data which entity we want to retrieve. We also sorted the result by name using NSSortDescriptor
.
In the last part of our example, we created an update_fetched_employee_with_fetch_request
method that will fetch the employee array and update the table to show all of the data. NSManagedObjectContext
executes the fetch request that we created using the following code:
@fetched_employee = @managed_object_context.executeFetchRequest(fetch_request, error: error_pointer)
add_employee_view_controller.rb
and add the following code to it:class AddEmployeeViewController < UIViewController attr_accessor :managed_object_context def viewDidLoad self.view.backgroundColor = UIColor.whiteColor self.title = ‘Add Employee' save_bar_button_item = UIBarButtonItem.alloc.initWithTitle(‘Save', style: UIBarButtonItemStyleDone, target: self, action: ‘save_employee') self.navigationItem.rightBarButtonItem = save_bar_button_item load_form end def save_employee # Using Core Data create a new instance of the object employee employee = NSEntityDescription.insertNewObjectForEntityForName(Employee.name, inManagedObjectContext: @managed_object_context) # Assign the text of the name text field to the employee employee.name = @name.text employee.age = @age.text.intValue # Create a new pointer for managing the errors error_pointer = Pointer.new(:object) # Lets persist the new Movie object, saving the managed object context that contains it unless @managed_object_context.save(error_pointer) raise "Error saving a new Director: #{error_pointer[0].description}" end # Pop the Director View Controller self.navigationController.popViewControllerAnimated(true) end def load_form @name = UITextField.alloc.initWithFrame([[50,50],[200,30]]) @name.borderStyle = UITextBorderStyleRoundedRect @name.placeholder = "Name" self.view.addSubview(@name) @age = UITextField.alloc.initWithFrame([[50,100],[200,30]]) @age.borderStyle = UITextBorderStyleRoundedRect @age.placeholder = "Age" self.view.addSubview(@age) end end
With the preceding code, we created two text fields, one for name and the other for age and we first added a Save button on top of the view that will save the employee details by calling the save_employee
action. In the save_employee
action, we used Core Data to create a new instance of the employee
object in the following way:
employee = NSEntityDescription.insertNewObjectForEntityForName(Employee.name, inManagedObjectContext: @managed_object_context)
Then, we assigned the value of the text field to the employee
object and finally saved that object and navigated to EmployeeViewController
.
$ rake
The output is as follows:
With the completion of the last section, our Core Data application is capable of adding new employee records. But there may be instances when we'll need to delete an employee record. In this section, we'll enhance our app to delete employee records. The use case for this feature will be such that when we slide any row, the system will ask for a confirmation. And once we confirm, the record will be deleted:
employee_view_controller.rb
file with the following code:def tableView(tableView, canEditRowAtIndexPath: indexPath) true end def tableView(tableView, commitEditingStyle: editingStyle, forRowAtIndexPath: indexPath) employee = @fetched_employee[indexPath.row] # Ask the NSManagedObjectContext to delete the object @managed_object_context.deleteObject(employee) # Create a new pointer for managing the errors error_pointer = Pointer.new(:object) # Lets persist the deleted employee object, saving the managed object context that contains it unless @managed_object_context.save(error_pointer) raise "Error deleting an Employee: #{error_pointer[0].description}" end # Create a new mutable copy of the fetched_employee array mutable_fetched_employee = @fetched_employee.mutableCopy # Remove the employee from the array mutable_fetched_employee.delete(employee) # Assign the modified array to our fetched_employee property @fetched_employee = mutable_fetched_employee # Tell the table view to delete the row tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation:UITableViewRowAnimationFade) end
With the iOS tableView
, we have a direct way of creating or deleting a row. In the preceding code, we first passed the value true
to the tableView(tableView, canEditRowAtIndexPath: indexPath)
delegate. Then in order to perform a delete action, we defined the tableView(tableView, commitEditingStyle: editingStyle, forRowAtIndexPath: indexPath)
delegate.
NSManagedObjectContext
to delete that object:@managed_object_context.deleteObject(employee)
$rake
The output is as follows:
As shown in the preceding screenshot, when we slide the row, we get a system prompt to delete the row. And once we click on Delete, the row gets deleted.
3.143.229.85