Managed Object Model Basics - Learning Core Data for iOS (2014)

Learning Core Data for iOS (2014)

2. Managed Object Model Basics

The only source of knowledge is experience.

Albert Einstein

In Chapter 1, “Your First Core Data Application,” the fundamental Core Data building blocks were added to the Grocery Dude sample application. You configured a persistent store, coordinator, model, and context; however, the object graph remained empty. This means that although all the ingredients are ready to make cookies, you’re missing the cookie cutters! This chapter explains managed object model basics and takes you through the process of configuring an object graph for the sample application.

What Is a Managed Object Model?

A managed object model represents a data structure. The terms data structure, schema, object graph, data model, and managed object model may all be used interchangeably because they all mean more or less the same thing. If you were designing a new database without Core Data, you might configure a database schema and refer to it as a “data model.” With Core Data, the focus is (managed) objects. Therefore, instead of calling the schema a data model, you may refer to it as a managed object model. That said, it’s still perfectly appropriate to call it a data model, object graph, schema, or data structure, too!


Note

To continue building the sample application, you’ll need to have added the previous chapter’s code to Grocery Dude. Alternatively, you may download, unzip, and use the project up to this point from http://www.timroadley.com/LearningCoreData/GroceryDude-AfterChapter01.zip. Any time you start using an Xcode project from a ZIP file, it’s good practice to click Product > Clean. This practice ensures there’s no residual cache from previous projects using the same name.


Adding a Managed Object Model

In Chapter 1, you initialized a managed object model in CoreDataHelper.m using the mergedModelFromBundles method. The issue now, however, is that there are actually no models to merge! This renders Core Data pretty useless, so it’s about time to create a model file. The model file will primarily contain an object graph representing the application’s data structure and some other bits and pieces that will make your life easier, which will be explained later on.

Update Grocery Dude as follows to add a data model file:

1. Right-click the existing Grocery Dude group and then select New Group.

2. Set the new group name to Data Model.

3. Select the Data Model group.

4. Click File > New > File....

5. Select iOS > Core Data > Data Model and then click Next.

6. Ensure the Grocery Dude target is selected, leave the default filename as Model, and then click Create.

7. Select Model.xcdatamodeld, as shown in Figure 2.1.

Image

Figure 2.1 The Xcode Data Model Designer

Figure 2.1 shows the Xcode Data Model Designer, which is used to configure a data model. At first glance, some items you see in the model designer may be alien to you and raise a few questions. Entities and Fetch Requests are discussed in this chapter. Configurations are discussed inChapter 15, “Taming iCloud.”

Entities

A managed object model is made up of a collection of entity description objects called entities. You can think of an entity as an individual cookie cutter used to create managed objects. Once you have managed objects, you can work with data natively in code.

A managed object model can have one or more entities, which varies between applications. Before you can produce managed objects, you’ll first need to design each cookie cutter (entity) appropriately. Designing an entity is similar to traditional database table design.

When designing a database table, you would do the following:

image Configure a table name.

image Configure fields and set a data type for each one.

When designing an entity, you do this:

image Configure an entity name.

image Configure attributes and set a data type for each one.

image Configure an NSManagedObject subclass based on the entity (optional).

Whereas a table in a database has fields, an entity has attributes. An attribute must have a data type specified (for example, a string or integer). When it comes time to produce a managed object from an entity, you’ll usually create an NSManagedObject subclass based on the entity; however, this is not mandatory. Using an NSManagedObject subclass does have some advantages, though, such as enabling dot notation for your managed objects, which makes your code easier to read. From NSManagedObject or an NSManagedObject subclass, you create instances to begin working with data as managed objects. In database terms, managed object instances are like a row in a database table. The name of the entity and the name of the NSManagedObject subclass you create from it are usually the same. Whatever attributes you configure in an entity will become properties of the managed objects created from them. Figure 2.2 shows how entities are used to map between a persistent store’s database and managed objects.

Image

Figure 2.2 Core Data entity mapping

Entities are the foundation of a managed object model because they are used to logically group data that belongs together. One of the most important things to think about when designing a managed object model is the name you give each entity. The name you choose for an entity needs to describe (in a word or two) the data that the entity represents. For Grocery Dude, there’s a requirement to put things on a list. When you’re out shopping, you may want to put apples and oranges on the list. With this in mind, perhaps “fruit” is a good name for the entity representing things you might put on a list.

When you configure an entity, be careful to use a name that is generic enough to be flexible, yet specific enough that it’s obvious what it represents. Sometimes it’s easy to work out; sometimes it’s a delicate balancing act that forces you to think hard about your application’s purpose—now and in future releases. A shopping list is prone to containing more than just fruit, so perhaps “Item” is a better name for the entity representing things that could be put on a shopping list.

Update Grocery Dude as follows to add an Item entity:

1. Select Model.xcdatamodeld.

2. Click Add Entity.

3. Rename the new entity to Item.

Attributes

An attribute is a property of an entity. In the sample application, the Item entity represents things you can put on a shopping list. To work out what attributes are appropriate for the Item entity, you should think about things all shopping list items have in common. As a reasonable starting point, you might come up with a list similar to this:

image Item name

image Item quantity

Attribute names must begin with a lowercase letter. They should also not have a name that is already in use by NSObject or NSManagedObject methods. Xcode won’t let you break this rule and will warn you if, for example, you create an illegal entity attribute called “description.”

When an NSManagedObject subclass is created from the Item entity, it will have properties with the same name as the equivalent entity attributes. As with other objects in Objective-C, you may refer to class properties of an NSManagedObject subclass using dot notation. This will make code easier to read as you get property values directly via item.name and item.quantity.

Update Grocery Dude as follows to add two new attributes:

1. Add a name and quantity attribute to the Item entity by clicking Add Attribute while the Item entity is selected. The expected result is shown in Figure 2.3.

Image

Figure 2.3 Attributes with undefined types

When you add an attribute to an entity, you must specify the type of data it represents. The default attribute type is Undefined. The data type you choose for each attribute will vary and will require some forward thinking. There are several attribute types to choose from and as an Objective-C programmer, you may already be familiar with some of them.

Integer 16/32/64

Each of these similar attribute data types represents a whole number, without a decimal point. The only difference between them is how big or small that number can be. Core Data uses signed integers, so their range starts in the negative instead of with zero:

image Integer 16 ranges from –32768 to 32767

image Integer 32 ranges from –2147483648 to 2147483647

image Integer 64 ranges from –9223372036854775808 to 9223372036854775807

The bigger a number, the more memory is needed to hold it. When choosing between the three options for integers, you need to think about the lowest and highest value that may ever be needed by this attribute. If you’re not quite sure which size to choose, it’s generally a safe bet to go with Integer 32. Just beware that if you get it wrong and it turns out that you need a bigger number, you will need to upgrade the attribute to Integer 64. This type of change requires an upgrade to the managed object model, which is discussed in Chapter 3, “Managed Object Model Migration.”

Integers use the base-2 number system, better known as binary. Calculations using integers are faster than those with floating-point numbers. This is because there’s no need to handle the remainder left over after a calculation. For example, if you divide 10 by 3, then the result is 3 and the remainder of 1 is lost. The term for this is low precision. If you choose to use integers to represent money, it is highly recommended that 1 = 1 cent. This way, there are no rounding errors if you need to do financial calculations.


Note

The minimum and maximum values for standard integers can be seen in stdint.h. Type INT32_MAX in any class file in Xcode, right-click it, then select Jump to Definition. You will then see various definitions for minimum and maximum integers. You may notice unsigned integers have higher maximums because they don’t go below zero. Core Data only uses signed integers, which means they can be positive or negative numbers at the cost of a lower maximum.


When an NSManagedObject subclass is created from an entity containing an Integer 16, Integer 32, or Integer 64 attribute, the resulting property is an NSNumber.

Float and Double

These two similar attribute data types can be thought of as non-integers with a decimal point. They are both used to represent real numbers; however, they have some limitations in doing so. Floats and doubles use the base-2 (binary) number system, which is a CPU native number system prone to rounding errors. Consider the fraction 1/5. In decimal this can be represented exactly as 0.2. In binary it can only ever be represented by an approximation. The more digits you have after the decimal point, the higher the precision and more accurate the approximation is. Higher precision comes at the cost of more memory to store that precision.

Compared to a float, a double is just that—double the amount of bits. A float takes 32 bits to store and a double takes 64 bits. They are both stored in scientific notation. That is, there is a number (called a mantissa) and an exponent (power of) that are put together to form a floating-point number. Having 64 bits means a double has a larger range of values and higher precision than a float.

The biggest float on iOS is 340282346638528859811704183484516925440.000000. Floats and doubles are also stored with a sign bit, so this means that the lowest possible float on iOS is –340282346638528859811704183484516925440.000000. The biggest double is much larger than the biggest float. At the end of this chapter, the exercises will provide code to display the minimum and maximum values for each of the numerical data types.

When deciding between float and double, you should consider the characteristics of the attribute you are configuring. What are the smallest and largest values you need? Do you really need more than the ~7 digits of precision float offers? If you don’t, then you should stick with floats on iOS because they’re better matched to the underlying processor prior to the 64-bit iPhone 5S. Although it may seem inconsequential for a handful of properties to all be doubles, be aware that databases will amplify the storage requirements because they potentially have thousands of rows. Given the power and capacity of modern devices these days, you could get away with using a double for most scenarios. On the other hand, if you need to increase the speed of floating-point calculations and precision isn’t too critical, a float may be more appropriate. You shouldn’t use a float or double to represent dollars and cents for financial calculations because rounding errors might cause money to go missing!

When an NSManagedObject subclass is created from an entity containing either a float or double attribute, the resulting property is an NSNumber.

Decimal

Decimal is the recommended attribute data type for working with money and other scenarios where base-10 arithmetic is appropriate. Base-10 is not a CPU native number system like base-2 (binary). This means substantial processor overhead is incurred when working with decimals. Much like floats and doubles, decimals are made up of an integer mantissa, exponent, and sign. At the cost of extra memory and processing time, decimal provides excellent calculation accuracy. With this system, numbers such as 0.1 can be represented exactly. In essence, the decimal type stores this example as 1 / 10 ^ 1.

The largest decimal isn’t as big as the largest double; however, the precision is much higher and in some cases perfect. At the end of this chapter, the exercises will provide code to display an example of the precision achievable by each of the numerical data types.

When an NSManagedObject subclass is created from an entity containing a decimal attribute, the resulting property is an NSDecimalNumber. When you perform calculations with an NSDecimalNumber, it is imperative you only use its built-in methods to ensure you retain the precision.

String

The String attribute data type is used to store an array of characters, or plain old text. Being an Objective-C programmer, odds are you’re quite familiar with strings already. When an NSManagedObject subclass is created from an entity containing a string attribute, the resulting property is anNSString.

Boolean

The Boolean attribute data type is used to store a yes or no value. When an NSManagedObject subclass is created from an entity containing a Boolean attribute, the resulting property is an NSNumber. To get the Boolean value back out of the NSNumber, simply send a boolValue message to theNSNumber instance. When setting a Boolean value in an NSNumber, use the numberWithBool method.

Date

The Date attribute data type is self-explanatory. It is used to store a date and time. When an NSManagedObject subclass is created from an entity containing a date attribute, the resulting property is an NSDate.

Binary Data

If you need to store photos, audio, or some other contiguous BLOB of zeros and ones, you should use the Binary Data attribute type. When an NSManagedObject subclass is created from an entity containing a binary data attribute, the resulting property type is NSData. Depending on the data you’re storing, the approach in converting your data to and from NSData will differ. One common scenario is storing photos. To store a photo, you convert a UIImage to NSData using UIImagePNGRepresentation() or UIImageJPEGRepresentation(). To retrieve a photo, you convert the NSData toUIImage using the UIImage class method imageWithData. Binary Data is a good choice for large files because they can be seamlessly stored outside the database using the Allows External Storage attribute setting. With this setting enabled, Core Data will decide whether it is more efficient to store the file inside or outside of the database.

Transformable

The Transformable attribute data type is used to store an Objective-C object. This attribute type is a flexible option that allows you to store an instance of any class. An example of a transformable attribute is an instance of UIColor. When an NSManagedObject subclass is created from an entity containing a transformable attribute, the resulting property type is id. For the id object to make it into the store (and back again), you’ll need to use an instance of NSValueTransformer or an instance of a subclass of NSValueTransformer. This class helps transparently convert the attribute toNSData and back again. This is a reasonably simple process, especially when the class you want to store implements the NSCoding protocol. If it does, the system provides a default transformer that already knows how to archive and un-archive specific objects.

Update Grocery Dude as follows to configure its attributes:

1. Set the name attribute type to String.

2. Set the quantity attribute type to Float.

3. Add an attribute called photoData to the Item entity and set its type to Binary Data. This attribute will store the image data of an item’s photo. (Note: The Allows External Storage setting won’t be enabled until later in the book.)

4. Add an attribute called listed to the Item entity and set its type to Boolean. This attribute will be used to indicate whether an item is on the shopping list.

5. Add an attribute called collected to the Item entity and set its type to Boolean. This attribute will be used to indicate whether an item has been collected and therefore ticked off the shopping list.

The data model should now match Figure 2.4, as each attribute now has a data type configured.

Image

Figure 2.4 Grocery Dude’s data model in Table editor style

In preparation for an increasingly complicated data model, it is good to be aware you can switch to a graphical view. To change to graph editor mode, simply toggle the Editor Style button shown in the middle at the bottom of Figure 2.5. The left side of Figure 2.5 shows what the editor looks like in Graph style.

Image

Figure 2.5 Grocery Dude’s data model in Graph editor style

Attribute Settings

On the right of Figure 2.5 the Data Model Inspector is shown, which allows configuration of attribute settings beyond their type. You can access this area by pressing Option+image+3 when an attribute is selected. The options presented vary depending on the selected attribute’s type. Not all options are available to all attributes:

image Transient properties are never written to the persistent store. Although it may seem odd to have a property that is never persisted, there are scenarios where you need a property only in the managed object context. For example, you may wish to calculate a value on the fly and then store the result in a transient property. Being in a context allows those properties to benefit from features such as undo and redo.

image Optional properties aren’t required to have a value. All properties are originally created as optional. When a property is not optional, you won’t be able to save the managed object back to the store until the non-optional properties have a valid value.

image Indexed properties are optimized for search efficiency at the cost of additional storage space in the underlying persistent store. This additional space required for the index will range in size depending on how much data needs indexing. If you’re not going to search on a particular attribute, you can save space by not indexing the attribute.

image Validation can be used to ensure illogical data never enters the persistent store. Each of the numerical attribute types has the same validation options, which are minimum and maximum values. Similarly, you can set constraints around string lengths and date ranges, too. It is perfectly fine to have invalid values in a managed object context; as long as these are resolved before save: is called. It’s generally a good practice to validate data as soon as a user tries to take focus off an input element, such as a UITextfield.

image Reg. Ex. is short for Regular Expression and goes beyond validating a minimum or maximum string length. Although it can be used to enforce length, it is primarily used to test that an attribute’s string value matches a certain pattern. When Reg Ex validation is configured for an attribute, any related managed object property values must match the pattern; otherwise, they cannot be written to a persistent store. The pattern match configuration options must conform to the ICU Reg Ex specification. You can find further information about this specification, including valid configuration options, at http://userguide.icu-project.org/strings/regexp.

image Default values can be set for all attributes types except transformable and binary data. They are a starting point value used when no other value has been specified. It is best practice to have a default value set on numerical attributes because of the way the backend SQLite database handles null values. Setting an appropriate default value for a string attribute will be situational and therefore will depend on your requirements. For the date attribute type, you unfortunately cannot configure a default date of “now” in the model editor.

image Allows External Storage is used to permit large binary data attribute values to be stored outside the persistent store. It is recommended that you enable this option when storing large media such as photos, audio, or video. Core Data will automatically store attribute values over 1MB outside an SQLite persistent store when this option is enabled. This option has no effect when the underlying persistent store is XML (remember, this type of store isn’t supported on iOS).

image Index in Spotlight doesn’t do anything for an iOS application. It is used to integrate a Core Data–based Mac application with Spotlight. Spotlight is the search facility available via the magnifying glass in the top-right corner of the screen on a Mac. When a Mac application’s Core Data attribute is indexed in Spotlight, its values are able to appear in the results of a Spotlight search. When performing the search index, Spotlight looks for the hidden, zero-length files Core Data created that represent records from the persistent store. As indexed attribute values change in the persistent store, the equivalent files outside the store are updated automatically.

image Store in External Record File duplicates data from the persistent store into an XML representation outside the store. When used in conjunction with Index in Spotlight, this setting causes the index files created for Spotlight to be populated with values. It’s not recommended that you use this option unless you have a specific need to, such as for debugging purposes. If you choose to use external records to feed data to another application, note that the directory structure containing the records is subject to change.

image Name properties (of transformable attributes) are used to specify the name of the specific NSValueTransformer subclass that knows how to convert from any class into NSData and back again.

Update Grocery Dude as follows to configure indexing and defaults:

1. Set the name attribute to Indexed.

2. Set the name attribute Default Value to New Item.

3. Set the quantity attribute Default Value to 1.

4. Set the listed attribute Default Value to YES. This will ensure newly created items show up on the shopping list.

5. Set the collected attribute Default Value to NO. This will ensure newly created items aren’t ticked off the shopping list as soon as they’re created.

Subclassing NSManagedObject

With a basic managed object model in place, it’s time to create an NSManagedObject subclass based on the Item entity. These subclass files will let you work with data objects directly in code using dot notation, without any SQL queries. Whenever the model is updated in the future, you will need to regenerate these files again using the procedure you’re about to follow. Although it is possible to add methods to these generated files, you should avoid doing so directly because changes you’ve made will be lost if they’re ever regenerated. If you need to add custom methods, you’re better off subclassing or creating a category for the generated files.

Update Grocery Dude as follows to generate NSManagedObject subclass files:

1. Select the Item entity.

2. Click Editor > Create NSManagedObject Subclass....

3. Ensure the Model is selected and then click Next.

4. Ensure the Item entity is selected and then click Next.

5. Ensure the Grocery Dude target is ticked.

6. Ensure that Use scalar properties for primitive data types is not ticked.

7. Ensure the files will be saved into the Grocery Dude project directory and then click Create.

There will now be two new files in the Xcode project: Item.h and Item.m. These files were generated based on the Item entity, as is shown in Listing 2.1. The order the properties are listed in may vary.

Listing 2.1 Item.h


#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@interface Item : NSManagedObject

@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSNumber * listed;
@property (nonatomic, retain) NSNumber * quantity;
@property (nonatomic, retain) NSNumber * collected;
@property (nonatomic, retain) NSData * photoData;

@end


Notice the slight differences between the entity attribute types and the resulting class property types. Here’s a summary of how entity attributes translate to managed object properties:

image A Date attribute becomes an NSDate property.

image A String attribute becomes an NSString property.

image A Decimal attribute becomes an NSDecimalNumber property and all other numerical data types become an NSNumber property.

image A Binary Data attribute becomes an NSData property.

image A Transformable attribute becomes an id property.

When you examine the contents of Item.m, notice its implementation just lists each property as @dynamic. This is Core Data’s way of indicating that it has dynamically generated the required methods to allow getting and setting values, so you don’t have to.

Scalar Properties for Primitive Data Types

When you created an NSManagedObject subclass for the Item entity, you came across the option Use scalar properties for primitive data types. This option allows the resulting NSManagedObject subclass to use object properties only when it has no other recourse. Here’s a summary of how entity attributes translate to managed object properties when this option is selected:

image A Date attribute becomes an NSTimeInterval.

image A Double attribute becomes a double.

image A Float attribute becomes a float.

image An Integer 16/32/64 attribute becomes an int16_t, int32_t, or int64_t, respectively.

image A Boolean attribute becomes a BOOL.

This option has no effect on String, Decimal, Binary Data, or Transformable attributes, so their resulting property will still be an object pointer. Using scalar properties for primitive data types generates different “getter” methods in the NSMangedObject subclass files, so you don’t have to unbox the scalar values before using them in code.

Snippet Demo Method

Throughout this book there’s code that demonstrates a point and yet isn’t required in the final application. Listing 2.2 shows a new demo method along with an updated applicationDidBecomeActive method that calls demo after ensuring Core Data is ready with [self cdh].

Listing 2.2 AppDelegate.m: demo


- (void)demo {
if (debug==1) {
NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
}
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
if (debug==1) {
NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
}
[self cdh];
[self demo];
}


Update Grocery Dude as follows to implement the demo method:

1. Add the demo method from Listing 2.2 to the top of AppDelegate.m on the line after #define debug 1.

2. Replace the applicationDidBecomeActive method of AppDelegate.m with the applicationDidBecomeActive method from Listing 2.2.

Creating a Managed Object

Everything is now in place to create some new managed objects. New objects are based off the NSEntityDescription of a particular entity, specified by name. In addition to specifying the entity to base an object on, you’ll also need to provide a pointer to a managed object context where the new managed object will go. In the application delegate, access to an appropriate context is achieved via the context property of [self cdh] or _coreDataHelper.

The code in Listing 2.3 demonstrates how to insert a new managed object based on an entity into a context. Inserting a new managed object is as simple as calling the insertNewObjectForEntityForName method of the NSEntityDescription class and then passing it an appropriate entity name and context pointer.

Once a new Item-based managed object is created, you can then manipulate its values directly in code. The NSLog command at the end of Listing 2.3 demonstrates this when newItem.name is passed in as a string variable. The dot notation shown is a particularly clean way of working with objects because it makes your code easier to read.

Listing 2.3 AppDelegate.m: demo (Inserting)


NSArray *newItemNames =
[NSArray arrayWithObjects:
@"Apples", @"Milk", @"Bread", @"Cheese", @"Sausages", @"Butter",
@"Orange Juice", @"Cereal", @"Coffee", @"Eggs", @"Tomatoes", @"Fish",
nil];

for (NSString *newItemName in newItemNames) {
Item *newItem =
[NSEntityDescription insertNewObjectForEntityForName:@"Item"
inManagedObjectContext:_coreDataHelper.context];
newItem.name = newItemName;
NSLog(@"Inserted New Managed Object for '%@'", newItem.name);
}


Update Grocery Dude as follows to practice inserting managed objects:

1. Add #import "Item.h" to the top of AppDelegate.m.

2. Add the code from Listing 2.3 to the bottom of the demo method of AppDelegate.m.

Run the application, and you should see managed object names listed in the console. If you can, give yourself a pat on the back because you’re successfully using Core Data! Figure 2.6 shows the expected result.

Image

Figure 2.6 Inserting managed objects


Note

If you’ve been launching the project between adding each attribute, you may have run up against an error stating “The model used to open the store is incompatible with the one used to create the store.” If you get this error, you’ll need to delete the application and re-run it to install a fresh copy. If that doesn’t fix it, you should also click Product > Clean. Graceful model upgrades are discussed and implemented in Chapter 3.


Backend SQL Visibility

At face value, examining the console logs for Core Data results is rather underwhelming. How do you know what’s really going on under the covers? What is Core Data actually doing to get your data into the persistent store? Is it doing its job properly? What SQL queries are being generated to provide the seamless Core Data experience? Are duplicate objects being inserted every time you run the app in the simulator?

These questions can be answered with an extremely verbose debug option that provides plenty of information regarding what’s going on under the hood. The debug option will expose the auto-generated SQL queries and give some great insight to the workings of Core Data.

Update Grocery Dude as follows to enable SQL Debug mode:

1. Click Product > Scheme > Edit Scheme....

2. Ensure Run Grocery Dude and the Arguments tab is selected.

3. Add a new argument by clicking + in the Arguments Passed On Launch section.

4. Enter -com.apple.CoreData.SQLDebug 3 as a new argument and then click OK.

Now that you’ve enabled SQL Debug mode level 3, run the application again. Press the home button (Shift+image+H or Hardware > Home) and reexamine the logs. Look at all those INSERT statements you didn’t have to write! Figure 2.7 shows a fraction of the expected results.

Image

Figure 2.7 Core Data–generated SQL queries

Each of the SQLite binds shown in Figure 2.7 is a variable used to form an INSERT statement. The purpose of the statement is to insert managed object property values into a row of the ZITEM table found in the persistent store. The ZITEM table is associated with the Item entity. You can tell which entity attributes relate to what database fields by their name. The Z prefix is just a Core Data standard naming convention.

To verify managed objects are being persisted to an SQLite persistent store, you can actually look inside using a third-party utility. Be aware that altering a database directly isn’t recommended. Figure 2.8 shows where you can find the iOS Simulator working directory containing Grocery-Dude.sqlite. The Library directory is hidden, so you may have to right-click Finder, select Go to Folder, and then type the directory name in manually. The exact location (with a substituted username, of course) is /Users/Username/Library/Application Support/iPhone Simulator/. The directory name under Applications will vary because it is a randomly generated GUID.

Image

Figure 2.8 The iOS Simulator’s Grocery Dude SQLite database

Alongside Grocery-Dude.sqlite are two accompanying files ending with –wal and –shm. These are the result of a new default database journaling mode introduced in iOS 7. This journaling mode is discussed in further detail in Chapter 8, “Preloading Data.” For the time being, this journaling mode needs to be disabled so you can examine the contents of Grocery-Dude.sqlite. Disabling the default journaling mode requires that a new option be passed when adding the persistent store.

Update Grocery Dude as follows to disable the default journaling mode:

1. Add the following code to the loadStore method of CoreDataHelper.m on the line before NSError *error = nil:

NSDictionary *options =
@{NSSQLitePragmasOption: @{@"journal_mode": @"DELETE"}};

2. Replace options:nil with options:options in the addPersistentStoreWithType call found in the loadStore method of CoreDataHelper.m.

3. Run the application again and the –wal file should disappear, which means that all the data will now be located in Grocery-Dude.sqlite. The -shm file can be deleted or ignored.

To open an SQLite database file, you can use one of many freely available SQLite database browser utilities found by searching Google. There’s a good one on Sourceforge called SQLite Database Browser you may wish to try, so take a moment now to download and install it. This application isn’t signed, so you will need to “Allow applications downloaded from Anywhere” in System Preferences > Security & Privacy > General. If you’re not comfortable allowing this, search the Mac App Store for extension:sqlite to find another suitable signed application that can open .sqlite files.

Even though browsing the contents of a database is great for debugging purposes, you should not code your application to rely on the internal, private schema of a Core Data–managed database because Apple could change it without notice.


Note

Given that the Library folder is hidden, it’s easier opening Grocery-Dude.sqlite by right-clicking it, selecting Open With, and then selecting SQLite Database Browser. Be careful not to have the SQLite file already open with the database browser while Xcode is running Grocery Dude. If you do, the application may time out when attempting to open the database.


Figure 2.9 shows how the values of the managed object properties have been persisted to the store.

Image

Figure 2.9 Browsing the Grocery Dude SQLite persistent store

If you have duplicate entries, they are a result of the context being saved more than once. At the moment, the only time a context is saved is when the home button is pressed. This, alongside the demo method inserting new objects every time the application is launched, can cause duplication. Don’t worry if you have duplicates; you’ll soon be shown how to delete managed objects.

Update Grocery Dude as follows to avoid further data duplication:

1. Remove everything from within the demo method of AppDelegate.m except the NSLog code.

Fetching Managed Objects

To work with existing data from a managed object context, you’ll first need to fetch it. If the data isn’t already in a context when fetched, it will be retrieved from the underlying persistent store transparently. To fetch, you’ll need an instance of NSFetchRequest, which will return an NSArray of managed objects. When the fetch is executed, every managed object for the specified entity will be returned in the resulting array. In SQL database terms, a fetch is similar to a SELECT statement. The code involved is shown in Listing 2.4.

Listing 2.4 AppDelegate.m: demo (Fetch Request)


NSFetchRequest *request =
[NSFetchRequest fetchRequestWithEntityName:@"Item"];
[_coreDataHelper.context executeFetchRequest:request error:nil];


Update Grocery Dude as follows to fetch all the Item entity instances:

1. Add the code from Listing 2.4 to the demo method of AppDelegate.m.

Run the application to examine the logs and you’ll see a dozen similar lines showing each managed object that has been retrieved from the database. This number may vary depending on whether you have any duplicated data. Figure 2.10 shows the expected result.

Image

Figure 2.10 Fetched managed objects

To see the property values of each managed object, you can use a for loop to iterate through the NSArray and log the value of the item’s name. The code involved is shown in Listing 2.5.

Listing 2.5 AppDelegate.m: demo (Fetching)


NSFetchRequest *request =
[NSFetchRequest fetchRequestWithEntityName:@"Item"];
NSArray *fetchedObjects =
[_coreDataHelper.context executeFetchRequest:request error:nil];
for (Item *item in fetchedObjects) {
NSLog(@"Fetched Object = %@", item.name);
}


Update Grocery Dude as follows to configure item names to display in the console log:

1. Update the demo method of AppDelegate.m to match Listing 2.5. Feel free to keep or remove the NSLog statement from the start of the method.

Run the application again to examine the logs, and you should see each of the managed object’s name property values listed in the console window. Figure 2.11 shows the expected result.

Image

Figure 2.11 Fetched managed object names

Fetch Request Sorting

An NSFetchRequest returns an NSArray, which by nature supports being sorted. As such, you may optionally configure an NSFetchRequest with a sort descriptor configured to order managed objects in a certain way. Sort descriptors are passed to an NSFetchRequest as an instance ofNSSortDescriptor. In SQL database terms, a sort descriptor is similar to an ORDER BY statement. Listing 2.6 shows the code involved.

Listing 2.6 AppDelegate.m: demo (Sorting)


NSFetchRequest *request =
[NSFetchRequest fetchRequestWithEntityName:@"Item"];

NSSortDescriptor *sort =
[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sort]];

NSArray *fetchedObjects =
[_coreDataHelper.context executeFetchRequest:request error:nil];
for (Item *item in fetchedObjects) {
NSLog(@"Fetched Object = %@", item.name);
}


Update Grocery Dude as follows to ensure the fetch is sorted:

1. Insert the bold code from Listing 2.6 into the equivalent place within the demo method of AppDelegate.m.

Run the application again to examine the logs, and you should see the managed object names are now sorted in alphabetical order. Figure 2.12 shows the expected result.

Image

Figure 2.12 Fetched managed object names (sorted)

Fetch Request Filtering

When it isn’t appropriate to fetch everything possible for an entity, you can filter fetches using a predicate. A predicate is defined for a fetch request using an instance of NSPredicate and then passing it to an instance of NSFetchRequest. Using a predicate will limit the number of managed objects returned in the fetched results based on the criteria specified. Predicates are persistent store agnostic, so you can use the same predicates regardless of the backend store. That said, there are some corner cases where particular predicates won’t work with certain stores. For example, thematches operator works with in-memory filtering; however, it does not with an SQLite store. In SQL database terms, a predicate is similar to a WHERE clause.

A predicate is evaluated against each potential managed object as a part of fetch execution. The predicate evaluation result is a Boolean value. If YES, the predicate criteria are satisfied and the managed object will be a part of the fetched results. If NO, the predicate criteria are not satisfied and the managed object will not be a part of the fetched results.

Once you have the NSArray of fetched results from executing an NSFetchRequest, it is possible to filter the array again if you wish. To do this you can use the filteredArrayUsingPredicate method of NSArray, or filter “in place” using the filterUsingPredicate method of NSMutableArray.

In Grocery Dude, let’s say you wanted to exclude items with the name “Coffee.” When you create the NSPredicate that will be passed to the NSFetchrequest, you would specify that name is not equal to the string “Coffee”. In code, this predicate is written as name != @"Coffee". Because predicates support variable substitution, a string could also be passed to the predicate at runtime, as shown in bold in Listing 2.7. Building predicates can be a complicated topic, depending on your requirements. For further information, search http://developer.apple.com for the Predicate Programming Guide.

Listing 2.7 AppDelegate: demo (Filtering)


NSFetchRequest *request =
[NSFetchRequest fetchRequestWithEntityName:@"Item"];

NSSortDescriptor *sort =
[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sort]];

NSPredicate *filter =
[NSPredicate predicateWithFormat:@"name != %@", @"Coffee"];
[request setPredicate:filter];

NSArray *fetchedObjects =
[_coreDataHelper.context executeFetchRequest:request error:nil];
for (Item *item in fetchedObjects) {
NSLog(@"Fetched Object = %@", item.name);
}


Update Grocery Dude as follows to add a predicate to filter the fetch:

1. Add the bold code from Listing 2.7 into the equivalent place within the demo method of AppDelegate.m.

Run the application again to examine the logs and you should see the list of item names now excludes Coffee. Figure 2.13 shows the expected results.

Image

Figure 2.13 Fetched managed object names (sorted and filtered)

Fetch Request Templates

Determining the correct predicate format to use for every fetch can become laborious. Thankfully, the Xcode Data Model Designer supports predefining fetch requests. These reusable templates are easier to configure than predicates and reduce repeated code. Fetch request templates are configured using a series of drop-down boxes and fields specific to the application’s model. Unfortunately, given their simplicity they aren’t as powerful as predicates. If you need features such as custom AND/OR combinations, they’re missing and you’ll have to revert to predicate programming.

Update Grocery Dude as follows to create a fetch request template:

1. Select Model.xcdatamodeld.

2. Click Editor > Add Fetch Request.

3. Set the name of the fetch request template to Test.

4. Click + and configure the Test fetch request template, as shown in Figure 2.14.

Image

Figure 2.14 Fetch request template configuration

To use a fetch request template, you’ll need to send a message to the managed object model, telling it the name of the template to use. This will give you an NSFetchRequest to work with. Because the fetch request has been created from a template, there’s no longer a need to pass in a predicate for filtering. To modify the fetch request in any way (for example, to sort it), you must first copy the fetch request. This is because a fetch request template comes from an immutable (unchangeable) model. Listing 2.8 shows the code involved.

Listing 2.8 AppDelegate: demo (Fetch Request Template)


NSFetchRequest *request =
[[[_coreDataHelper model] fetchRequestTemplateForName:@"Test"] copy];

NSSortDescriptor *sort =
[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sort]];

NSArray *fetchedObjects =
[_coreDataHelper.context executeFetchRequest:request error:nil];

for (Item *item in fetchedObjects) {
NSLog(@"Fetched Object = %@", item.name);
}


Update Grocery Dude as follows to use the Test fetch request template:

1. Replace the existing code in the demo method of AppDelegate.m with the code from Listing 2.8. The new code is shown in bold and the predicate code has been removed.

Run the application again to examine the logs, and you’ll see the sorted managed object names all contain the letter e, as configured in the fetch request template. Figure 2.15 shows the expected results.

Image

Figure 2.15 Fetched managed object names (filtered via template)

Did you also notice the SQL statement generated for this fetch? You can see all the elements required to retrieve (SELECT) a filtered (WHERE) and sorted (ORDER BY) set of results. Figure 2.16 shows the expected results.

Image

Figure 2.16 Automatically generated SQL

Deleting Managed Objects

Deleting a managed object is as easy as calling deleteObject or deleteObjects on a containing context. Note that the deletion isn’t permanent until you also call save: on the context. The code involved is shown in Listing 2.9.

Listing 2.9 AppDelegate: demo (Deleting)


NSFetchRequest *request =
[NSFetchRequest fetchRequestWithEntityName:@"Item"];

NSArray *fetchedObjects =
[_coreDataHelper.context executeFetchRequest:request error:nil];

for (Item *item in fetchedObjects) {
NSLog(@"Deleting Object '%@'", item.name);
[_coreDataHelper.context deleteObject:item];
}


Update Grocery Dude as follows to delete all the objects:

1. Replace all code in the demo method with the code from Listing 2.9.

2. Run the application.

3. Press the home button (Shift+image+H or Hardware > Home) to save the changes to the context.

Did you notice all the SQL statements calling DELETE that were logged to the console once you pressed the home button to trigger a save? By now you should have a reasonable level of comfort that Core Data is taking care of the backend SQL for you. Turn off SQLDebug and remove all code from the demo method before heading to Chapter 3.

Summary

You’ve now been introduced to the steps required to configure a basic managed object model. Entities and attributes have been discussed, along with some advice on choosing the right data types. Inserting, fetching, filtering, sorting, and deleting managed objects have all been covered, followed up with an introduction to fetch request templates. The covers have also been lifted off Core Data to give you some insight into what it’s doing for you under the hood.

Exercises

Why not build on what you’ve learned by experimenting?

1. Insert some new managed objects and set their name and quantity.

2. Alter the Test fetch request template and experiment with other filtering options.

3. Open Grocery-Dude.sqlite in SQLite Database Browser then click the Database Structure button. Notice the existence of ZITEM_ZNAME_INDEX? That’s where the indexing for the name attribute is stored since you ticked the Indexed check box.

4. Temporarily run the code from Listing 2.10 in the demo method. Examine the range limitations of the numerical data types.

Listing 2.10 Numerical Attribute Ranges


NSLog(@"Integer 16 Range: %d to %d", INT16_MIN, INT16_MAX);
NSLog(@"Integer 32 Range: %d to %d", INT32_MIN, INT32_MAX);
NSLog(@"Integer 64 Range: %lld to %lld", INT64_MIN, INT64_MAX);
NSLog(@"Float Range = %f to %f", -FLT_MAX, FLT_MAX);
NSLog(@"Double Range = %f to %f", -DBL_MAX, DBL_MAX);
NSLog(@"Decimal Range = %@ to %@",
[NSDecimalNumber minimumDecimalNumber],
[NSDecimalNumber maximumDecimalNumber]);

NSLog(@" Float 1/3 = %@", [NSNumber numberWithFloat:1.0f/3]);
NSLog(@" Double 1/3 = %@", [NSNumber numberWithDouble:1.0/3]);
NSLog(@"Decimal 1/3 = %@",
[[NSDecimalNumber one] decimalNumberByDividingBy:
[NSDecimalNumber decimalNumberWithString:@"3"]]);