📜 ⬆️ ⬇️

Core Data for iOS. Chapter number 2. Theoretical part

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



Content:

')

Introduction


Many developers, after the first acquaintance with Core Data, consider it to be some kind of confusing set of classes, which, instead of simplifying work with data, on the contrary, prevents this. Perhaps this is Rails developers who are used to writing dynamic search methods and allowing accepted agreements to do all the dirty work for them. Perhaps this is a Java developer who annotates Enterprise JavaBeans (EJB) and works with Plain Old Java Objects (POJO). No matter who these people are, whatever they do, they don’t perceive the Core Data Framework as it is and its methods of working with data, as many developers see how they create “live” interfaces using Interface Builder and make fun of them . We assure you that Core Data is not another Ruben Goldberg machine. Classes in the Core Data Framework are rather the players of the 1980s Boston Celtics and, when you understand their approaches to the game, only then can you appreciate the beauty and completeness of their game and interaction.

Did you know?
Ruben Lucius Goldberg (born Reuben Lucius Goldberg; 1883–1970) is an American cartoonist, sculptor, writer, engineer and inventor.
Goldberg is best known for a series of caricatures featuring the so-called “Ruba Goldberg Machine” - an extremely complex, cumbersome and intricate device that performs very simple functions (for example, a huge machine that occupies an entire room, the purpose of which is to move a spoonful of food from a plate to human mouth).
In 1948, Goldberg received the Pulitzer Prize for his political cartoons, and in 1959 the Banshees' Silver Lady Award.
Goldberg was one of the founders and the first president of the National Society of Caricature. His name is the Ruben Prize, which the organization rewards as a cartoonist of the year. In the United States annually held a competition cars Ruba Goldberg.

image


In this chapter, the purposes of the Core Data Framework classes will be described, both individually and when they work together. Try not to rush to read the entire chapter. Examine the examples, rummage through them, enter the code yourself ( no copy-paste !) And run for verification.

Core Data Classes

In the first chapter, we have already reviewed and created the simplest application using Core Data. You saw code snippets, which classes were used, which methods were called, in what order the methods were called by these methods, and which parameters were passed to these methods. All this was done in order to understand how Core Data works. You blindly followed what you were told to do, maybe at some points you wondered why this was done, but you continued to tap sweet NSManagedObject on the keyboard, maybe you asked yourself the question “What happens if you substitute another value here?”. Some may even have tried to replace one value with another, it was they who got something different from the explosion;), and someone got what they expected to receive.

Edward Dijkstra, a well-known computer researcher and winner of the 1972 Turing Award for his work in the development of programming languages, spoke of elegance as the quality that determines victory or defeat, and not as optional luxury.

Did you know?
The Turing Award (English Turing Award) - the most prestigious award in computer science, awarded by the Association of computing technology for outstanding scientific and technical contributions in this field.

The award was established by the Computer Engineering Association in honor of the outstanding English scientist Alan Turing, who received the first profound results regarding computability long before the appearance of the first electronic computers.
The prize is awarded annually to one or more specialists in the field of computer science and computing, whose contribution in this area has had a strong and lasting impact on the computer community. The prize may be awarded to one person not more than once. In the field of information technology, the Turing Prize has a status similar to the Nobel Prize in academic sciences. For the first time the Turing Award was awarded in 1966 to Alan Perlis for the development of technology for creating compilers.

The prize is currently sponsored by Intel and Google and amounts to $ 250,000.

(c) Wikipedia


Core Data not only does the task of storing data, but it does it elegantly. To achieve this elegance in your code, you need to understand Core Data, and not guessing in the coffee grounds how it works and why it works at all. After reading this chapter, you will understand in detail not only the structure of the Core Data Framework itself, but also how the framework solves complex problems using a small set of classes, making solutions simple, understandable and elegant. Throughout the chapter, we will build and complement the Core Data Framework class diagram. The fact that not all classes will belong to Core Data Framework will not slip away from you, some will be imported from the Foundation Framework. In parallel with the theoretical part, we will develop a small application that will work with fictitious organizational structures (managers, programmers, managers, administrators, etc.).

Open Xcode and create a Single View Application:
image

Name the project OrgChart .
Connect to the Core Data project ( see Chapter 1 ). Run the application and make sure that it does not crash.
image

The image below shows the Core Data classes we usually work with:
image

In our code, data is saved by adding new NSManagedObject to the NSManagedObjectContext , and data is obtained using the NSFetchRequest class. As shown in Chapter 1, the managed object context is created and initialized using the data warehouse manager (persistent store coordinator) implemented by the NSPersistentStoreCoordinator class, which in turn is determined by the data model implemented by the NSManagedObjectModel class. The rest of this chapter will be devoted to examining how these classes are created, how they interact with each other, and how to use them.

The classes used to create the model

As previously mentioned in Chapter 1, all applications using Core Data must have an object model of stored data. The model defines the entities and their properties. An entity has three types of properties:


The table you see below shows the various classes and a description of their role.
Going through classes for a better understanding of the model initialization mechanism is quite an interesting exercise, but in practice creating a model in XCode requires you to be able to work with the mouse in the graphical editor of models without writing a single line of code.
Class nameRole
NSManagedObjectModelData model
NSEntityDescriptionEntity in the data model
NSPropertyDescriptionAbstract description of an entity property
NSAttributeDescriptionEntity attribute
NSRelationshipDescriptionLink of one entity to another
NSFetchedPropertyDescriptionDescription of a subset of entity instances selected by a specific criterion


The table below shows the relationships between the classes used in defining the model. NSManagedObjectContext does not reference, or refers to several entity objects NSEntityDescription . Each NSEntityDescription not referenced, or it refers to several NSPropertyDescription objects. NSPropertyDescription is an abstract class with three concrete implementations:

image

This small number of classes will be enough to describe any data model that you will develop using Core Data Framework. As described earlier in Chapter 1, to create a model, you need to open Xcode, select the menu item File -> New -> New File and specify "Data Model". In this section, we will create a model that will be the organizational structure of the company.
Create a new model in Xcode and name it OrgChart . The organization has a director (CEO) in this data model. For simplicity, let's imagine that a person has two attributes: a unique employee identifier and a name. Now we are ready to begin defining the data model.
Open the data model and create a new Organization entity. Like a person, an organization is defined by its unique identifier and name. Add two attributes to the Organization entity. Attributes are constant properties of an entity that may contain values ​​of some type. Attribute data types are described in the NSAttributeType class and each data type imposes some constraints on the entity. For example, if you try to write a string to a property with an integer data type, an error will occur.
The table below describes the existing types:
Attribute type in XcodeAttribute Type in Objective-CObjective cDescription
Integer 16NSInteger16AttributeTypeNSNumber16-bit integer
Integer 32NSInteger32AttributeTypeNSNumber32-bit integer
Integer 64NSInteger64AttributeTypeNSNumber64-bit integer
DecimalNSDecimalAttributeTypeNSDecimalNumberBase 10 integer value
DoubleNSDoubleAttributeTypeNSNumberDouble object wrapper
FloatNSFloatAttributeTypeNSNumberFloat object wrapper
StringNSStringAttributeTypeNsstringCharacter string
BooleanNSBooleanAttributeTypeBoolBoolean object wrapper
DateNSDateAttributeTypeNSDatedate and time
Binary dataNSBinaryDataAttributeTypeNSDataBinary data
TransformableNSTransformableAttributeTypeAny non-standard typeAny type that can be converted to standard


Note
In Chapter 5, we will analyze the type of Transformable in more detail. Transformable attributes are a way to notify Core Data that they will use non-standard data types and, in the process of saving data to the local storage, we will tell you how to convert this type to one of the existing embedded ones.


Let's call the first attribute id , and the second name . By default, when creating a new attribute, its type is automatically set to Undefined and will not allow the project to be compiled. Your task is to choose the appropriate type for each of the entity attributes you create. Organization identifiers will always be of integer type, so we will select Integer 16 as the type for the id attribute. Type String is used for the name attribute.
At this stage, your project should look like this:
image

If at this stage the program would be running, the object graph would look like this:
image

The graph shows that the NSManagedObjectModel data model refers to an entity object NSEntityDescription , which is called Organization and uses the NSManagedObject type for the property " managedObjectClassName . An entity has two attributes. Each attribute contains two properties: name and type. In the first attribute, the name id and type NSInteger16AttributeType for the properties name and attributeType respectively.

In the same way, create a second entity named Person with two attributes: id and name . Now we can create a connection between the Organization and Person entities and name its leader . Creating a relationship is quite simple: click on the "+" button in the "Relationships" section, select "Destination" and "Source". Now add another connection between Person and Person and call it employees . The last connection we have created is a one-to-one relationship, while an employee may have several subordinates, which means that the type of communication is necessary one-to-many.
Open the panel on the right in Xcode, as shown:
image

And tick the box “Plural: To-Many Relationship”:
image

The data model looks like this:
image

Here is what the object graph will look like when the model is loaded:
image
This graph is not a graph of objects that Core Data stores, it is just a display of how the model looks in the eyes of Core Data.
A typical way to load a Core Data data model is as follows:
 NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"OrgChart" withExtension:@"momd"]; _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; 


Knowing how model objects are represented in Core Data is not very useful, unless you are writing your own data warehouse or are interested in generating a data model programmatically at runtime. An analogy can be drawn with the creation of a UIView programmatically, instead of using ready-made elements provided by Interface Builder. However, a deep understanding of how Core Data works will allow you to predict and avoid difficulties, problems, and lead to the solution of difficulties in a creative and elegant way.

We familiarized with how entities are represented in Core Data and which auxiliary classes are present in it, but you don’t have to get into the wilds every time you want to use Core Data in your project, rarely when you have to use the previously listed classes directly. All the classes we discussed above are related to the description of the data model. The remainder of this chapter will be devoted to data classes and properties that we will use to access this data.

Data Access Classes

In Chapter 1, we found that the initialization of Core Data begins with loading the data model into an NSManagedObjectModel object. In the previous section, we looked at how NSManagedObjectModel represents the internal structure of the data model using NSPropertyDescription and NSEntityDescription . The next step in the Core Data initialization process is to create and bind the data store using the NSPersistentStoreCoordinator . Finally, the final step in the initialization process is the creation of an NSManagedObjectContext object with which the interaction takes place directly (reading and writing data).
In order to clarify everything that was said above, I will give a class diagram:
image

As you can see on the graph there is also NSManagedObjectModel , which is loaded and then passed to NSPersistentStoreCoordinator to work with data warehouses (yes, there may be several of them) and determine how this data should be processed. The data storage manager (persistent store coordinator) acts as the conductor of the orchestra itself, which coordinates the work of all NSManagedObjectContext with the data storage. Among other things, the data warehouse manager is responsible for migrating data from one storage to another, achieved by using the migratePersistentStore: method.

NSPersistentStoreCoordinator initialized with NSManagedObjectModel . After our dispatcher has been created, you can upload and register data stores. The code below shows how to initialize the data warehouse manager. Initialization occurs by calling the initWithManagedObjectModel: and passing the data model, and then registering the new repository with the addPersistentStoreWithType: method.

 NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"OrgChart.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(); } 


iOS provides three types of storage:
Storage TypeDescription
NSSQLiteStoreTypeSQLite database
NSBinaryStoreTypeBinary file
NSInMemotyStoreTypeData storage in the temporary memory of the device


Note
Core Data on Mac OS X also offers a fourth storage type ( NSXMLStoreType ), which uses an XML file to store data.


A typical course would be the use of NSSQLiteStoreType , the use of which indicates that the data will be stored in a SQLite database. But it is worth all the same to be familiar with other types of storage. No matter how useless the NSInMemoryStoreType type NSInMemoryStoreType , the typical use of this type of storage is to cache the information received from the remote server and create a local copy of the data in order to reduce the network load and reduce the number of driven traffic along with delays.
After the NSTersistentStoreCoodrinator initialized, we can proceed to the creation and initialization of NSManagedObjectContext .
The object management environment is responsible for what is requested from the repository and what is written there. The task may seem simple, but here are some possible difficulties for you:


Note
Apple strongly recommends that you do not inherit your classes from NSManagedObjectContext.


The table below lists the main methods for creating, retrieving, and deleting objects from NSManagedObjectContext . Change (obovit) object by changing its properties.
Method nameMethod Description
-executeFetchRequest: error:Performs a request for receiving objects
-objectWithID:Returns an object with the specified ID.
-insertObject:Adds a new object to the environment.
-deleteObject:Removes an object from the environment.


It should be careful when deleting objects and the possible consequences of this action. When deleting an object that is associated with others, Core Data needs to decide what to do with the child objects. One of the properties of the relationship is called “Delete rule”, which can be four variants:
Rule NameEffect
No actionDoes nothing, allows the child to assume that the parent object exists
NullifyFor each child object sets the reference to the parent object to null.
CascadeDeletes each child object.
DenyForbids to delete the parent object, it has at least one child


In addition to the fact that the object management environment synchronizes data from the repository, it also allows you to perform Undo and Redo operations, as is often done in text editors and other software.
MethodDescription
-undoManagerReturns an NSUndoManager that is responsible for performing undo operations.
-setUndoManagerInstalls a new NSUndoManager
-undoSend undo message to NSUndoManager
-redoSends redo message to NSUndoManager
-resetCauses the environment to reset all pointers to the objects it processes.
-rollbackSends an undo message to the NSUndoManager as long as there are changes.
-saveSaves all changes to local storage. Consider calling this method a kind of commit in Git. All unsaved data will be lost if the application crashes during execution.
-hasChangesReturns YES if there are changes in the environment that were not synchronized with the repository.


In Chapter 5, we talk about undo and redo operations in Core Data.

Instances of the NSManagedObject class support KVC to provide a universal way to access the data contained in an object. The easiest way to access the data of an NSManagedObject instance variable is to use the valueForKey: method, to write (change) the same data using the setValue:forKey: method. The key parameter is the name of the model entity attribute.

 - (id)valueForKey:(NSString *)key; - (void)setValue:(id)value forKey:(NSString *)key; 


The NSManagedObject class also provides several helper methods to help NSManagedObjectContext to track object changes. When it comes time to commit to the data warehouse, the NSManagedObjectContext all the objects it contains and asks if their state has changed.
Apple strongly discourages overriding methods that are responsible for the state of changes in order not to disrupt the entire chain of actions when saving objects.

Note
Each object of type NSManagedObject contains a link to the environment (NSManagedObjectContext) to which it belongs. This is one of the non-master options to get a link to the environment through the object that is in it.


How to create new NSManagedObject we still remember:
 NSEntityDescription *entity = [NSEntityDescription entityForName:@"Organization" inManagedObjectContext:self.managedObjectContext]; NSManagedObject *org = [NSEntityDescription insertEntityForName:[entity name] inManagedObjectContext:self.managedObjectContext]; 


Once we have created a new object in the environment, we can proceed to setting the values ​​of its fields:
 [org setValue:@"MyCompay, Inc." forKey:@"name"]; [org setValue:@77 forKey:@"id"]; 


KVO

One of the interesting and convenient methods of receiving notification of a change in an object is a “subscription” to events of changes in a property. NSManagedObject supports KVO and takes responsibility for sending notifications about changes to a specific property of an object.KVO is not something that only from the world of Core Data, this pattern is actively used in most Cocoa frameworks. The two main methods that apply to KVO are: willChangeValueForKey:and didChangeValueForKey:. The first method is called before the property changes, and the second, respectively, after it.
Before you receive notifications, you must register our object (which will track changes) as an observer. Here is what it might look like:
 [managedObject addObserver:observerObject forKeyPath:@"theProperty" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil]; 

After the object is registered as a listener, it will receive immediate notifications about changes in the properties for which it is signed.
This is an extremely convenient and useful mechanism if you want your screen data to be constantly updated.

Query classes

At this stage, we are already familiar with how the Core Data stack is initialized and how to interact with the created objects ( NSManagedObject). In this part we will talk about how to work with requests for receiving data from the repository.
It is not strange, requests for data are instances of the class NSFetchRequest. The most interesting is the fact that it NSFetchRequestis almost the only class responsible for requesting (receiving) data in Core Data, the other "auxiliary" classes belong to the Foundation Framework.
image

Data retrieval requests consist mainly of two parts: an instance NSPredicateand an instance NSSortDescriptor. NSPredicateis designed to filter data according to certain criteria, andNSSortDescriptorresponsible for sorting data in a specific order (ascending, descending). Both of these elements are optional in the query and, if neither of them is specified, you will receive a set of all data in an arbitrary (undefined) order.

Let's create a simple request to get a list of all organizations without using filters and sorting:
 NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"Organization" inManagedObjectContext:self.managedObjectContext]; [fetchRequest setEntity:entity]; NSArray *organizations = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil]; 

Since we did not filter the list of organizations, we will have all the organizations found in the repository (objects of type NSManagedObject) in the organizations array .
Let's get only those organizations whose names contain the substring "Inc.":
 NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name contains %@", @"Inc."]; [fetchRequest setPredicate:predicate]; 


Sort the resulting set of organizations by name in the lexicographical order:
 NSSortDescriptor *sortByName = [NSSortDescriptor alloc] initWithKey:@"name" ascending:YES]; [fetchRequest setSortDescriptors:@[sortByName]]; 


In Chapter 6, we will look more closely at working with NSPredicateand NSSortDescriptor.
Below is the final graph of Core Data objects:


We will discuss NSFetchedResultsControllerin more detail in Chapter 9.

Class interaction

At this stage, you should have a good idea of ​​the classes that are the core of Core Data and what is happening under the hood. In this part we will continue to consider the OrgChart application, paying attention to the features of the work of certain classes. I want to note that this section (like the previous ones in this chapter) is aimed at developing your basic knowledge about how classes interact in Core Data. Each subsequent chapter will consider concepts (data manipulation, use of several storages of different types, increase in productivity, etc.) at a deeper level.

The data model that we built in the previous section should look like this:


For each of the entities, we added the id property(unique identifier) ​​of type Integer 16 . Most programmers, who have probably already come to this point, have already thought about auto-incrementing and may even have tried to look for something similar in XCode. Alas and oh! If you are one of these programmers, you probably already gave up trying to find something like that. And the reason why you could not find it is simple - there is simply no such thing! Core Data manages the object graph using pointers to objects. It makes no sense to use a unique identifier, as is usually done in relational databases like SQLite (yes, maybe Core Data somewhere uses auto-decrementing keys, but we don’t need to worry about this).
We intentionally entered a unique identifier in effect for the following reasons:


A personal identifier is something like a social insurance number. There is no need to increment it automatically. As for the essence Organization, it idwill be a hash of the object, although it does not give a 100% guarantee of uniqueness, but it is suitable for this application.

Note
As a programmer, you should not focus too much on what you know about databases and try to project this knowledge into Core Data. Core Data is not a database, it is a framework for working with an object graph stored locally.


Now we’ll remember a little about the initialization steps for core Core Data objects.

OrgChartAppDelegate.h
 #import <UIKit/UIKit.h> #import <CoreData/CoreData.h> @class OrgChartViewController; @interface OrgChartAppDelegate : UIResponder <UIApplicationDelegate> @property (nonatomic, strong) UIWindow *window; @property (nonatomic, strong) OrgChartViewController *viewController; @property (nonatomic, strong) NSManagedObjectModel *managedObjectModel; @property (nonatomic, strong) NSPersistentStoreCoordinator *persistentStoreCoordinator; @property (nonatomic, strong) NSManagedObjectContext *managedObjectContext; - (void)saveContext; - (NSURL *)applicationDocumentsDirectory; 


Further, in OrgChartAppDelegate.m we implement 3-4 methods already familiar to us:
 - (void)saveContext { NSError *error = nil; NSManagedObjectContext *managedObjectContext = self.managedObjectContext; if(nil != managedObjectContext) { if([managedObjectContext hasChanged] && ![managedObjectContext save:&error]){ NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } } } - (NSManagedObjectContext *)managedObjectContext { if(nil != _managedObjectContext) return _managedObjectContext; NSPersistentStoreCoordinator *storeCoordinator = self.persistentStoreCoordinator; if(nil != storeCoordinator){ _managedObjectContext = [[NSManagedObjectContext alloc] init]; [_managedObjectContext setPersistentStoreCoordinator:storeCoordinator]; } return _managedObjectContext; } - (NSManagedObjectModel *)managedObjectModel { if(nil != _managedObjectModel) return _managedObjectModel; NSURL *model = [[NSBundle mainBundle] URLForResource:@"OrgChart" withExtension:@"momd"]; _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:model]; return _managedObjectModel; } - (NSPersistentStoreCoordinator *)persistentStoreCoordinator { if(nil != _persistentStoreCoordinator) return _persistentStoreCoordinator; NSURL *storeURL = [[self applicationDocumentDirectory] URLByAppendingPathComponent:@"OrgChart.sqlite"]; NSError *error = nil; _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initManagedObjectMode: self.managedObjectModel]; if(![_persistentStoreCoordinator addStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]){ NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } return _persistentStoreCoordinator; } - (NSURL *)applicationDocumentsDirectory { return [[[NSFileManaged defaultManager] URLsForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask] lastObject]; } 

In this chapter, we will focus on working with Core Data, rather than on implementing the user interface, so all the work will be done in the method application:didFinishLaunchingWithOptions:. Then we will learn using external tools to check the operation of Core Data (whether the data is stored in our local storage or not).
Before you request any data from the repository, you need to add something there, so change ours application:didFinishLaunchingWithOptions:as follows:
 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [self createData]; self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.viewController = [[OrgChartViewController alloc] initWithNibName:@"OrgChartViewController" bundle:nil]; self.window.rootViewController = self.viewController; [self.window makeKeyAndVisible] return YES; } 


In OrgChartAppDelegate.h :
 - (void)createData; 


In OrgChartAppDelegate.m :
 - (void)createData { //.... } 


Let's start with creating an organization:
 NSManagedObject *organization = [NSEntityDescription insertNewObjectForEntityForName:@"Organization" inManagedObjectContext:self.managedObjectContext]; 

We created an organization, but did not specify its name or identifier.
 [organization setValue:@"MyCompany, Inc." forKey:@"name"]; 

As we remember, the id attribute is of type Integer 16. All set values ​​in CoreData must be objects (no int, long, char, enum). We use the wrapper class for the integer type: NSNumber.
The code will look like this:
 [organization setValue:[NSNumber numberWithInt:[@"MyCompany, Inc." hash]] forKey:@"id"]; 

Established the name of the organization and a unique identifier. Nothing has been saved to the repository yet, but it NSManagedObjectContextmonitors all changes in the state of objects and stores them in the local repository only after calling the save:method.

An organization without people cannot work, though ...
Let's create several employees:
 NSManagedObject *john = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:self.managedObjectContext]; [john setValue:@"John" forKey:@"name"]; [john setValue:[NSNumber numberWithInt:[@"John" hash]] forKey:@"id"]; NSManagedObject *jane = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:self.managedObjectContext]; [jane setValue:@"Jane" forKey:@"name"]; [jane setValue:[NSNumber numberWithInt:[@"Jane" hash]] forKey:@"id"]; NSManagedObject *bill = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:self.managedObjectContext]; [bill setValue:@"Bill" forKey:@"name"]; [bill setValue:[NSNumber numberWithInt:[@"Bill" hash]] forKey:@"id"]; 

Now we have an organization and three unrelated people. John is our CEO, and Jane and Billy are helpers.
We have two types of communication: one-to-one (the organization has only one CEO) and one-to-many (one employee may have many subordinates).
So, linking the organization (making contact) with John and making it a CEO is quite simple:
 [organization setValue:john forKey:@"leader"]; 

Core Data perfectly understands what object you have dropped into it and what connection it should be. By the way, if you try to transfer some other type instead of John, the code will not work.

Now we need to figure out what to do with Jane and Billy. Core Data returns from a one-to-many relationship a lot in the form of a type NSSet. Therefore, in order for us to have the opportunity to add new employees, it is necessary to call the method mutableSetValueForKey:, and not the valueForKey:one that will return an immutable set of objects.
This is what Jane and Billy’s add code would look like:
 NSMutableSet *johnsEmployees = [john mutableSetValueForKey:@"employees"]; [johnsEmployees addObject:jane]; [johnsEmployees addObject:bill]; 


All these changes, this whole graph of objects that we created must be saved:
 [self saveContext]; 

This completes the implementation of the method createData:, when you start the application, you will not see anything either in the console or on the simulator screen. In the next section, we will use external tools to check whether it was possible to save data or not.

Sqlite

We take a shovel in hand, launch the console and ... let's go:
 cd ~/Library/Application\ Support/iPhone\ Simulator 

Find our database:
 find . -name "OrgChart.sqlite"print 

It will give us something like this:
 ./5.0/Applications/E8654A34-8EC4-4EAF-B531-00A032DD5977/Documents/OrgChart.sqlite 

Run:
 sqlite3 ./5.0/Applications/E8654A34-8EC4-4EAF-B531-  00A032DD5977/Documents/OrgChart.sqlite 

At this stage, we have the SQLite shell. We can enter commands.
 SQLite version 3.7.5 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> 

One of the interesting and useful commands is the command .schemethat displays the structure of the tables created by Core Data:
 sqlite> .schema CREATE TABLE ZORGANIZATION ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZID INTEGER, ZLEADER INTEGER, ZNAME VARCHAR ); CREATE TABLE ZPERSON ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZID INTEGER, Z2EMPLOYEES INTEGER, ZNAME VARCHAR ); CREATE TABLE Z_METADATA (Z_VERSION INTEGER PRIMARY KEY, Z_UUID VARCHAR(255), Z_PLIST BLOB); CREATE TABLE Z_PRIMARYKEY (Z_ENT INTEGER PRIMARY KEY, Z_NAME VARCHAR, Z_SUPER INTEGER, Z_MAX INTEGER); CREATE INDEX ZORGANIZATION_ZLEADER_INDEX ON ZORGANIZATION (ZLEADER); CREATE INDEX ZPERSON_Z2EMPLOYEES_INDEX ON ZPERSON (Z2EMPLOYEES); sqlite> 

Many internal fields, some additional tables that you can hardly find in the XCode model editor. You can see the field for that automatically incremented key (INTEGER PRIMARY KEY).
Let's try to sample all organizations and employees available in the database:
 sqlite> select Z_PK, ZID, ZLEADER, ZNAME from ZORGANIZATION; 1|-19904|2|MyCompany, Inc. sqlite> select Z_PK, ZID, Z2EMPLOYEES, ZNAME from ZPERSON; 1|6050|2|Jane 2|-28989||John 3|28151|2|Bill sqlite> 

Note the 3 column in which the identifier of the parent object is indicated, in this case we have Billy and Jane under the direction of John (the unique identifier is 2).

To exit the shell:
 sqlite> .quit 


We are requesting data

In this chapter, we wrote data to the SQLite repository using Core Data, and used the SQLite shell to examine the structure of the database that Core Data creates and verify that the data was saved.
Now we will request data from the repository using the same Core Data. Open the Xcode file OrgChartAppDelegate.m.

Since the relationship between employees is recursive (one entity on both sides of the relationship), we write a method that allows you to display a list of employees who are subject to the specified c pleasant and understandable formatting:
 - (void)displayPerson:(NSManagedObject *)person withIndentation:(NSString *)indentation { NSLog(@"%@Name: %@", indentation, [person valueForKey:@"name"]); //     indentation = [NSString stringWithFormat:@"%@ ", indentation]; NSSet *employees = [person valueForKey:@"employees"]; id employee; NSEnumerator *it = [employees objectEnumerator]; while((employee = [it nextObject]) != nil) { [self displayPerson:employee withIndentation:indentation]; } } 

Now we will write the method of sampling all organizations, displaying the name of the organization with its CEO and all subordinates:
 - (void)readData { NSManagedObjectContext *context = [self managedObjectContext]; NSEntityDescription *orgEntity = [NSEntityDescription entityForName:@"Organization" inManagedObjectContext:context]; NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; [fetchRequest setEntity:orgEntity]; NSArray *organizations = [context executeFetchRequest:request error:nil]; id organization; NSEnumerator *it = [organizations objectEnumerator]; while ((organization = [it nextObject]) != nil) { NSLog(@"Organization: %@", [organization valueForKey:@"name"]); NSManagedObject *ceo = [ogranization valueForKey:@"leader"]; [self displayPerson:ceo withIndentation:@" "]; } } 

Do not forget to add methods to OrgChartAppDelegate.h:
 - (void)readData; - (void)displayPerson:(NSManagedObject *)person withIndentation:(NSString *)indentation; 

Now back to the method application:didFinishLaunchingWithOptions:and instead of calling the method, createDatacall readData:
 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //[self createData]; [self readData]; self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.viewController = [[OrgChartViewController alloc] initWithNibName:@"OrgChartViewController" bundle:nil]; self.window.rootViewController = self.viewController; [self.window makeKeyAndVisible]; return YES; } 

Start the application and take a look at the console:


Don't be afraid to experiment: add new employees, delete the database and let the application restore it again, create new organizations.
Here, for example, we added a new employee, headed by Jane:


Conclusion

To date, Core Data should not look like something incomprehensible and scary to you. In this chapter, we explored many of the classes that Core Data uses for its needs, but with which we will very, very rarely (or rather, never) work. Using Core Data is like driving a car without having to be a mechanic. The simplicity of the core data class structure should not be misleading. You have already seen what Core Data is capable of and, this is just the beginning. The more we delve into the many settings, connectivity with the UI , alternative storage types, customizable NSManagedObject's, the clearer you will be able to see the elegance of the applied approaches to data storage in Core Data.

Source: https://habr.com/ru/post/191580/


All Articles