Good day, good day!
Today I want to start writing a series of lectures with practical tasks on the book by Michael Privat and Robert Warner "Pro Core Data for iOS", which you can buy from
this link. Each chapter will contain a theoretical and practical part.

Content:
- Chapter number 1. Getting Started ( Practical Part )
- Chapter number 2. Digest Core Data
- Chapter number 3. Data storage: SQLite and other options
- Chapter 4 Creating a data model
- Chapter number 5. We work with data objects
- Chapter number 6. Processing Result Sets
- Chapter number 7. Performance tuning and memory usage
- Chapter number 8. Version Control and Migration
- Chapter number 9. Managing tables using NSFetchedResultsController
- Chapter number 10. Using Core Data in Advanced Applications
')
Getting started
What is Core Data?
Using computers to perform their tasks, people expect that the changes they have made will be saved. Saving changes plays an important role in office software packages, text editors, games, browsers, and so on. Most software needs the ability to store user-entered data for later recovery of work status, but of course there is software that doesn’t need it - calculators, news feeds, alarm clocks, weather widgets.
Understanding how you can store data on an iDevice is critical when developing advanced applications.
Apple provides a flexible framework for working with data stored on the device - Core Data. Of course, Core Data is not a panacea, and there are other data storage options that may be better suited for solving certain problems, but Core Data fits very well and nicely into Cocoa Touch. Most of the details of working with data storage Core Data hides, allowing you to concentrate on what really makes your application fun, unique and easy to use.
Despite the fact that Core Data can store data in a relational database like SQLite, Core Data is not a DBMS. In truth, Core Data may not use relational databases at all as storage. Core Data is not something like Hibernate, although it does provide some of the capabilities of ORM. Core Data is rather a wrapper / framework for working with data, which allows you to work with entities and their relationships (relationships to other objects), attributes, in a form that resembles working with an object graph in ordinary object-oriented programming.
Did you know?Core Data was introduced only since Mac OS X 10.4 Tiger and iPhone SDK 3.0
IOS Storage History
As for the cartoons released by Pixar, we should thank NeXT, and for Core Data we have to thank her. Core Data was born and evolved from a technology called the Enterprise Objects Framework (EOF).
The framework debuts in 2005 with the launch of Mac OS X 10.4 (Tiger), but in the IPhone it appears only from version 3.0.
Before Core Data arrived on the IPhone, the developers had a hard time and the following options could be used to store data:
1) Lists of properties that contained key-value pairs of various data types.
2) Serialization of data and storing them in files (using NSCoding protocol)
3) SQLite relational database
4) Data storage in the cloud
These methods still continue to be used, although none of the four methods compare in terms of convenience with the use of Core Data. Despite the birth of such a framework as FMDatabase and ActiveRecord, to solve the data storage problem before the advent of Core Data, the developers gladly switched to Core Data after it appeared.
Although I repeat that Core Data is not a cure for all diseases, sometimes you will of course turn to solutions using, for example, a property list, but more often you will be faced with the need and desire to use Core Data in your applications, as a tool that best way to solve your problem.
The more often and the more you will program and use Core Data, the more often you will not have the question "Should I use Core Data?", And "Is there any reason not to use Core Data?".
Creating a simple Core Data application
In this section, we will create a simple application based on Core Data from one of the Xcode templates, the main parts of which will be analyzed. At the end of this part, you will understand how the application interacts with Core Data to store and retrieve data.
Understanding the Core Components of Core Data
Before you dive into the code and parse the test application, you must have an idea of ​​the components of Core Data. The figure below shows the main elements that we will use in the test application.

As a Core Data user, you should never work directly with a data store. Abstract from storage, from storage type, think only about data! A feature of this approach is the ability to safely change the type of storage (there was an XML file, and SQLite became) without changing a large number of code written by you.
Objects that are under the control of the Core Data framework must inherit the methods / properties of the NSManagedObject class.
As well as people, objects need an environment in which they can exist, there is such an environment, and it is called a managed object context (managed object environment) or simply a context. The environment in which the object is located monitors not only the state in which the object with which you work, but also the states of the associated objects (objects that are dependent on this one and on which it depends).
An instance of the NSManagedObjectContext class provides the same environment for objects; an object of this type must always be available in your application. Typically, an instance of the NSManagedObjectContext class is a property of the delegate of your application. Without the environment, without an instance of the NSManagedObjectContext class, you simply will not be able to work with Core Data.
Creating a new project
Launch XCode and create a new project from the Master-Detail Application template:

Fill in the fields as follows:

And after the creation is complete, we will see something like this:

We start our project
Before you begin to understand what is under the hood of this application, let's run and see what the application does.
We press on the “Run” button and this will appear in front of us:

Press several times on the button with "+" and several entries will appear in the list with time.

Now we finish (stop) the application and, if the application did not use Core Data to store data, then at the next launch we would see an empty list again, but after restarting we see the same picture:

Parse application components
The structure of the application is relatively simple. In the presence of a data model that describes the entity stored in the database (storage); a controller that facilitates interactions between the screen (table) and the data store; application delegate, which helps to initialize and run the application.
The image below shows the classes used in the application and how they relate to each other:

Notice that the MasterViewController class contains a property that references an instance of the NSManagedObjectContext class for interacting with Core Data. Walking through the code you can see that the MasterViewController obtains the managed object context from the application delegate property.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if(self){ self.title = NSLocalizedString(@"Master", @"Master"); id delegate = [[UIApplication sharedApplication] delegate]; self.managedObjectContext = [delegate managedObjectContext]; } return self; }
BasicApplication.xcdatamodel is a directory in the file system that contains information about the structure of the data model. The data model is the foundation of every application using Core Data.
The data model of this application describes only one entity - Event. Event entity contains only one property (field, attribute) - timeStamp of type Date.

Entity Event type NSManagedObject, which is considered the main one for all entities that is managed by Core Data. In the second chapter, we will look at the NSManagedObject type in more detail.
Extract / Fetch Data
The next class that interests us is MasterViewController. In its header file, two properties are described which we note:
@property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController; @property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
NSFetchedResultsController is a controller provided by the Core Data framework for managing requests to the repository.
NSManagedObjectContext is already known to us as the medium of existence of objects of type NSManagedObject.
The implementation of the MasterViewController class, which can be found in the MasterViewController.m file, shows how to interact with Core Data to receive and store data. In the implementation of the MasterVIewController class there is an explicit getter fetchedResultsController, which pre-sets the query for fetching data from the repository.
The first step to implementing a data query is to create a query:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"Event" inManagedObjectContext:self.managedObjectContext]; [fetchRequest setEntity:entity];
Query results can be sorted using NSSortDescriptor. NSSortDescriptor defines the field for sorting and the type of sorting (ascending or descending).
In our example, we sort in descending order creation times:
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timeStamp" ascending:NO]; NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil]; [fetchRequest setSortDescriptors:sortDescriptors];
After the request is defined, we can start creating the NSFetchedResultsController.
Using NSFetchedResultsController MasterVIewController as a delegate, we can monitor the status of storage data (delete, add, move, etc.) and seamlessly integrate this solution with UITableView. Of course, we can get the same results by calling the executeFetchRequest method in the managed object context, but in this case we won’t get and can’t take full advantage of the NSFetchedResultsController.
Creating and setting an instance variable of the NSFetchedResultsController class:
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:@"Master"]; aFetchedResultsController.delegate = self; self.fetchedResultsController = aFetchedResultsController;
Did you know?You probably noticed that the initWithFetchRequest method used earlier has the cacheName parameter. When you pass nil as an argument, you exclude the possibility of caching the result of the query, but by specifying the cache name, you allow Core Data to check for the presence of the same previously implemented query and return the result from the cache. Otherwise, if there is no request with the same cache name, a request will be made to the storage and the necessary data will be returned, which will be cached later.
In conclusion, we just have to execute the query to get the data:
NSError *error = nil; if(![self.fetchedResultsController performFetch:&error]){ NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); }
Below you can see the full getter fetchedResultsController:
- (NSFetchedResultsController *)fetchedResultsController { if (_fetchedResultsController != nil) return _fetchedResultsController; NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"Event" inManagedObjectContext:self.managedObjectContext]; [fetchRequest setEntity:entity]; [fetchRequest setFetchBatchSize:20]; NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timeStamp" ascending:NO]; NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil]; [fetchRequest setSortDescriptors:sortDescriptors]; NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:@"Master"]; aFetchedResultsController.delegate = self; self.fetchedResultsController = aFetchedResultsController; NSError *error = nil; if(![self.fetchedResultsController performFetch:&error]){ NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } return __fetchedResultsController; }
NSFetchedResultsController is something like a collection of objects of type NSManagedObject; for this, it even has the properties of fetchedObjects of type NSArray to get access to the results of the query.
The MasterVIewController class, which also extends the capabilities of the UITableViewController, shows us how convenient it is to use NSFetchedResultsController to manage the table contents.
Insert a new object
Quickly taking a look at the insertNewObject: it becomes clear how new events are created and added to the repository. NSManagedObject are defined by the description of the entity from the data model and can only exist in a specific context (environment). The first step to create a new object is to get the context in which this object will be created:
NSManagedObjectContext *managedObjectContext = [self.fetchedResultsController managedObjectContext]; NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
Create an NSManagedObject:
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntity:[entity name] inManagedObjectContext:context]; [newManagedObject setValue:[NSDate date] forKey:@"timeStamp"];
The final step that needs to be done is to preserve the context in which the new object was created. Note that when you save the context, all previously unsaved changes will be saved.
NSError *error = nil; if(![context save:&error]){ NSLog(@"Unresolved error: %@, %@", error, [error userInfo]); abort(); }
The full method of adding a new object is presented below:
- (void)insertNewObject { NSManagedObjectContext *managedObjectContext = [self.fetchedResultsController managedObjectContext]; NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity]; NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context]; [newManagedObject setValue:[NSDate date] forKey:@"timeStamp"]; NSError *error = nil; if(![context save:&error]){ NSLog(@"Unresolved error: %@, %@", error, [error userInfo]); abort(); } }
Context initialization
It is obvious that everything that we previously did cannot be achieved without creating the context object, without the very environment in which the object exists and lives. The application delegate is responsible for creating this very environment. Three properties that should be required in any application using Core Data:
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext; @property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel; @property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
Please note that all three properties are readonly, this is done so that they cannot be set from the outside. Studying BasicApplicationAppDelegate.m shows that all three properties have explicit getters.
Managed Object Model:
- (NSManagedObjectModel *)managedObjectModel { if(_managedObjectModel != nil){ return _managedObjectModel; } NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"BasicApplication" withExtension:@"momd"]; _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modeURL]; return _managedObjectModel; }
Then a storage is created that supports the created data models. In our case, in fact, as in most of the others using Core Data, the data warehouse is based on SQLite.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator { if(_persistentStoreCoordinator != nil){ return _persistentStoreCoordinator; } NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"BasicApplication.sqlite"]; NSError* error = nil; _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; if(![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]){ NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } return _persistentStoreCoordinator; }
Create a context (environment):
- (NSManagedObjectContext *)managedObjectContext { if(_managedObjectContext != nil){ return _managedObjectContext; } NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; if(coordinator != nil){ _managedObjectContext = [[NSManagedObjectContext alloc] init]; [_managedObjectContext setPersistentStoreCoordinator:coordinator]; } return _managedObjectContext; }
The context is used throughout the application as an interface for interacting with Core Data and persistent storage.
Core Data Initialization Sequence:

The mechanism is started by calling the application: didFinishLaunchingWithOptions method:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; MasterViewController *controller = [[MasterViewController alloc] initWithNibName:@"MasterViewController" bundle:nil]; self.navigationController = [[UINavigationController alloc] initWithRootViewController:controller]; self.window.rootViewController = self.navigationController; [self.window makeKeyAndVisible]; return YES; }
Calling the getter property managedObjectContext of the application delegate for execution starts a chain of actions:
- (NSManagedObjectContext *) managedObjectContext calls
- (NSPersistentStoreCoordinator *) persistentStoreCoordinator, which in turn makes the call
- (NSManagedObjectModel *) managedObjectModel.
Thus, the call to the getter-method, a managedObjectContext, initializes the entire stack of Core Data objects and brings Core Data to readiness.
Adding Core Data to an existing project
It is carried out in three steps:
- Adding the Core Data Framework
- Creating a data model
- Context initialization
Adding the Core Data Framework
In the Objective-C world, libraries are called frameworks.
Standard frameworks that are already connected in “clean” projects and which you will most often see:
- Uikit
- Foundation
- Core graphics
Where new frameworks are added:

Choose a framework for connection:

Creating a data model
No Core Data application can be considered complete without a data model. The data model describes all the entities that will be administered by Core Data.
To create a new data model: File -> New -> New File

Let's call our model
MyModel.xcdatamodeld :

After creating the model, you will see the window for editing (creating) entities.

You can create a new entity by clicking on the "+" button in the bottom left of Xcode, and you can add a new entity attribute by clicking on the "+" button in the "Attributes" section and then select its type.

Context initialization
The final step is to initialize the context, the repository, and the object model. Most often you can use code that is automatically generated by XCode in “empty” projects using Core Data.
DemoAppAppDelegate.h #import <UIKit/UIKit.h> #import <CoreData/CoreData.h> @interface DemoAppDelegate : UIResponder <UIApplicationDelegate> { } @property (strong, nonatomic) UIWindow *window; @property (strong, nonatomic) UINavigationController *navigationController; @property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel; @property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext; @property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator; - (void)saveContext; - (NSURL *)applicationsDocumentsDirectory; @end
Switch to the * .m file DemoAppAppDelegate and write the following lines:
@synthesize managedObjectContext = _managedObjectContext; @synthesize managedObjectModel = _managedObjectModel; @synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
Next comes the code that initializes the data model, the repository, and the context.
- (NSManagedObjectModel *)managedObjectModel { if (_managedObjectModel != nil){ return _managedObjectModel; } NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"MyModel" withExtension:@"momd"]; _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modeURL]; return _managedObjectModel; }
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator { if(_persistentStoreCoordinator != nil){ return _persistentStoreCoordinator; } NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"DemoApp.sqlite"]; NSError *error = nil; _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]]; if(![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error&error]){ NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } return _persistentStoreCoordinator; }
- (NSManagedObjectContext *)managedObjectContext { if(_managedObjectContext != nil){ return _managedObjectContext; } NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; if(coordinator != nil){ _managedObjectContext = [[NSManagedObjectContext alloc] init]; [_managedObjectContext setPersistentStoreCoordinator:coordinator]; } return _managedObjectContext; }
Methods that we have never implemented before:
- (NSURL *)applicationDocumentsDirectory{ return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; }
- (void)saveContext { NSError *error = nil; NSManagedObjectContext *managedObjectContext = [self managedObjectContext]; if(managedObjectContext != nil) { if([managedObjectContext hasChanges] && ![managedObjectContext save:&error]){ NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } } }
Now that we have connected Core Data, our application can use it to store data.
Let's implement a simple example that would allow us to make sure that everything works as it should and that the data is actually saved.
In the test example, we will determine the number of times that our application has been launched.
Let's make small changes to the application: didFinishLaunchingWithOptions: application delegate method in the form of getting the count of times that the application has started before and adding a new launch event.
The code to get the previous launches of the application:
NSManagedObjectContext *context = [self managedObjectContext]; NSFetchRequest *request = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"MyData" inManagedObjectContext:context]; [request setEntity:entity]; NSArray *results = [context executeFetchRequest:request error:nil];
Then we can go through the whole array as follows:
for(NSManagedObject *object in results){ NSLog(@"Found %@", [object valueForKey:@"myAttribute"]; }
Add a new entry and save:
NSString *launchTitle = [NSString stringWithFormat:@"launch %d", [results count]]; NSManagedObject *object = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context]; [object setValue:launchTitle forKey:@"myAttribute"]; [self saveContext]; NSLog(@"Added : %@", launchTitle);
After the first launch, the application will display the following line in the console:
2011–02-25 05:13:23.783 DemoApp[2299:207] Added: launch 0
After the second launch:
2011–02-25 05:15:48.883 DemoApp[2372:207] Found launch 0 2011–02-25 05:15:48.889 DemoApp[2372:207] Added: launch 1
The full method is as follows:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSManagedObjectContext *context = [self managedObjectContext]; NSFetchRequest *request = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entytyForName:@"MyData" inManagedObjectContext:context]; [request setEntity:entity]; NSArray *results = [context executeFetchRequest:request error:nil]; for(NSManagedObject *object in results){ NSLog(@"Found %@", [object valueForKey:@"myAttribute"]); } NSString *launchTitle = [NSString stringWithFormat:@"launch %d", [results count]]; NSManagedObject *object = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context]; [object setValue:launchTitle forKey:@"myAttribute"]; [self saveContext]; NSLog(@"Added : %@", launchTitle); [self.window makeKeyAndVisible]; return YES; }
In conclusion
I ask for errors not to write in the comments, it is better to immediately in personal messages.
Do translations continue? Is there any interest in this topic?
Thank you for attention!