Core Data – manage your data

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.

Core Data example

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:

  1. Let's create an application using the motion command:
    $motion create CoreDataExample
    
  2. Add the CoreData framework in the rake.rb file:
    app.frameworks += [‘CoreData']
  3. This will be an MVC application, so let's create a model named 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.

  4. Let's create a folder named 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.

  5. Next, create a file named 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.

  6. Now, add the following code to the 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.

  7. Let's fire up the terminal and run our application using the following command:
    $rake
    
    Core Data example
  8. You will see a blank screen as we have not yet created the controller and view. We will create them in the next section, but before that, let's first update the 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

Creating an employee

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:

  1. Create a file named 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
  2. Next, let's fetch specific objects using the 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)
  3. Next, we will create the view that will be called when the + button is clicked on. Let's create a file named 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.

  4. Let's fire up the terminal and run our application using the following command:
    $ rake
    

    The output is as follows:

    Creating an employee
  5. Now, let's add data to the Employee form using the view:
    Creating an employee

Deleting the employee

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:

  1. Update the 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.

  2. Once we fetch the row that we want to delete, we use NSManagedObjectContext to delete that object:
       @managed_object_context.deleteObject(employee)

    Tip

    Remember that we have to always call save to persist it to our database.

  3. Let's fire up the terminal and run the application using the following command:
    $rake
    

    The output is as follows:

    Deleting the employee

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.229.85