Advanced Querying - Pro iOS Persistence: Using Core Data (2014)

Pro iOS Persistence: Using Core Data (2014)

Chapter 3. Advanced Querying

Simple applications and simple data models can usually skate on simple queries to serve up the data specific to the applications’ requirements. Nontrivial applications, however, usually have more complex data models and more intricate requirements surrounding that data. To meet those application requirements and interact with the more complex data, you need more advanced approaches to querying your data. This chapter discusses advanced querying techniques for Core Data and will equip you with the requisite tools for extracting the data combinations you need for your data-rich applications.

Most of the classes we discuss in this chapter aren’t tied to Core Data but, rather, apply to any of the collection classes. You use NSPredicate, NSExpression, NSSortDescriptor, aggregation operators, functions, and the lot to filter any type of collection, not just Core Data result sets. Some classes, and some methods of these classes, in fact, apply only to general collections and don’t work with Core Data. Keep in mind, however, that once you have fetched a result set from Core Data, you have a standard NSArray collection that you can manipulate outside Core Data’s confines as you wish.

Building WordList

Whether you’re playing Words with Friends, Scrabble, or some other word game, you’ll find more success if you know the words that the game you’re playing allows. In this chapter, we build an application called WordList that downloads a popular word list from the Internet and runs various queries against it to demonstrate advanced queries. The application isn’t fancy—you can see the user interface (UI) in Figure 3-1—and it won’t guarantee victory or even improved scores. You may, however, learn a new word or two that you can play in your favorite word game and impress your friends.

image

Figure 3-1. The WordList application

Creating the Application

In Xcode, create a new project using the Single View Application template. Set the Product Name to WordList, select the language you’d like to use, uncheck Use Core Data (we’ll be adding it ourselves), and click Next to let Xcode go to work to create your project.

Next, add Core Data to the WordList application by following the steps outlined previously.

· Add an @import CoreData (Objective-C) or import CoreData (Swift) directive wherever you need access to the Core Data classes.

· Add the WordList data model, WordList.xcdatamodeld.

· Add the Persistence class, which encapsulates WordList’s Core Data interaction.

The header file for the Objective-C Persistence class, shown in Listing 3-1, declares the properties and methods for realizing the Core Data stack. It should look familiar—you saw it first in Chapter 1’s Persistence class.

Listing 3-1. Persistence.h

@import CoreData;

@interface Persistence : NSObject

@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;

- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;

@end

Listing 3-2 shows the Objective-C implementation file, Persistence.m.

Listing 3-2. Persistence.m

#import "Persistence.h"

@implementation Persistence

- (id)init
{
self = [super init];
if (self != nil) {
// Initialize the managed object model
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"WordList" withExtension:@"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];

// Initialize the persistent store coordinator
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"WordList.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();
}

// Initialize the managed object context
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
}
return self;
}

#pragma mark - Helper Methods

- (void)saveContext
{
NSError *error = nil;
if ([self.managedObjectContext hasChanges] && ![self.managedObjectContext save:&error]) {
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
}

- (NSURL *)applicationDocumentsDirectory
{
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}

@end

Finally, Listing 3-3 shows the Swift implementation, Persistence.swift. Notice that we sneak in an extension at the bottom of the file that we’ll later use to make formatting output simpler.

Listing 3-3. Persistence.swift

import Foundation
import CoreData

class Persistence: NSObject {

var managedObjectContext: NSManagedObjectContext = {
// Initialize the managed object model
let modelURL = NSBundle.mainBundle().URLForResource("WordListSwift", withExtension: "momd")
let managedObjectModel = NSManagedObjectModel(contentsOfURL: modelURL!)

// Initialize the persistent store coordinator
let storeURL = Persistence.applicationDocumentsDirectory.URLByAppendingPathComponent("WordListSwift.sqlite")
var error: NSError? = nil
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel!)
if persistentStoreCoordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: nil, error: &error) == nil {
abort()
}

// Initialize the managed object context
var managedObjectContext = NSManagedObjectContext()
managedObjectContext.persistentStoreCoordinator = persistentStoreCoordinator

return managedObjectContext
}()

func saveContext() {
var error: NSError? = nil
let managedObjectContext = self.managedObjectContext
if managedObjectContext != nil {
if managedObjectContext.hasChanges && !managedObjectContext.save(&error) {
abort()
}
}
}

class var applicationDocumentsDirectory: NSURL {
let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
return urls[urls.endIndex-1] as NSURL
}
}

extension Float {
func format(f: String) -> String {
return NSString(format: "%\(f)f", self)
}
}

In your application delegate, AppDelegate, add a Persistence property and initialize the Core Data stack when WordList launches. For the Objective-C version, Listing 3-4 shows the updated AppDelegate.h file, and Listing 3-5 shows the updatedapplication:didFinishLaunchingWithOptions: method in AppDelegate.m. Be sure to import Persistence.h at the top of AppDelegate.m. For the Swift version, see Listing 3-6.

Listing 3-4. AppDelegate.h

#import <UIKit/UIKit.h>

@class Persistence;

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) Persistence *persistence;

@end

Listing 3-5. The Updated application:didFinishLaunchingWithOptions: Method

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.persistence = [[Persistence alloc] init];
return YES;
}

Listing 3-6. The Updated AppDelegate.swift

import UIKit
import CoreData

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?
var persistence: Persistence?

func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
persistence = Persistence()
return true
}
/* Code snipped */
}

WordList now initializes the Core Data stack, but it has an empty data model. In the next section, we build the data model so that WordList can store and manage the word list.

Building the Data Model

You should have already created the data model, WordList.xcdatamodeld, in the previous section. In this section, you add the entities, attributes, and relationships that the WordList application requires.

The source data for the WordList application is a list of words, so we could model the data as a single entity with a single attribute to store the text of a word. The other pieces of data that WordList requires—the length of the word and the category it belongs to, as determined by its first letter—can be calculated from the word itself. We’re not going to model the data that way, however, for two reasons. First, that would thwart the purpose of the chapter; we want some attributes and relationships so we can use Core Data’s advanced querying capabilities. The second reason isn’t so artificial, however: performance. As you use Core Data in your applications, remember that your database server isn’t some high-powered server that crunches numbers and data at lightning speeds. It’s an iOS device, crowded with applications and running processes and a CPU (central processing unit) that balances size, weight, performance, and battery life. Just because you can calculate something at runtime doesn’t mean you should. Precalculating and storing metadata can be a way to improve the performance and efficiency of your Core Data applications. We discuss performance at greater depth in Chapter 9.

For the WordList data model, create two entities: WordCategory and Word. In the WordCategory entity, add an attribute of type String named firstLetter. In the Word entity, add two attributes: text, of type String, and length, of type Integer 16. Then, create a to-many relationship from WordCategory to Word, with a Nullify delete rule, and call it words. Don’t forget to create the inverse relationship from Word back to WordCategory called wordCategory. When you’re finished, the data model should match Figure 3-2.

image

Figure 3-2. The WordList data model

Creating the User Interface

The WordList UI provides a button to reload the source data (the list of words) from the Internet and a text area to display statistics about the data. To accomplish this sparse interface, open Main.storyboard and drag a button to the top center of the view and a text view filling the rest of the view below that. Set up the constraints to maintain the controls like this; the easiest way to accomplish this is to select them, click the Resolve Auto Layout issues icon in the bottom right of Xcode (it looks like a triangle between two vertical lines), and select Add Missing Constraints.

In ViewController.h, declare a method to handle button presses—the touch-up inside event—and a property to point to the text area. Your code should match Listing 3-7. For Swift, open ViewController.swift and add analogs for the same things: a function to handle button presses and a variable to point to the text area, as Listing 3-8 shows.

Listing 3-7. ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property (nonatomic, strong) IBOutlet UITextView *textView;

- (IBAction)loadWordList:(id)sender;

@end

Listing 3-8. ViewController.swift

import UIKit

class ViewController: UIViewController {

@IBOutlet weak var textView: UITextView!

@IBAction func loadWordList(sender: AnyObject) {
}
}

The view you’ve created should look like Figure 3-3.

image

Figure 3-3. The WordList UI

For Objective-C, add an empty method implementation in ViewController.m for loadWordList:. For both Objective-C and Swift, wire the touch-up inside event for the button to the loadWordList: selector. Also, wire the text view in the interface to the textView property.

We’ll come back to the UI code in a moment, to respond to button presses and update the statistics. First, however, we’re going to add code to the persistence layer, Persistence, to load the word list and provide statistics.

Loading and Analyzing the Word List

We delegate responsibility for loading the data model and providing statistics about that data to the Persistence class. We declare two methods in that class: one for taking an NSString containing all the words, parsing it, and loading it and one for returning an NSString containing statistics about the word list. Declare these methods for the Objective-C version in Persistence.h, as Listing 3-9 shows.

Listing 3-9. Two Method Declarations in Persistence.h

- (void)loadWordList:(NSString *)wordList;
- (NSString *)statistics;

The loadWordList: method does three things.

· Deletes all existing data, in case the user has already loaded the word list;

· Creates the word categories; and

· Loads the words.

Start by creating a helper method, deleteAllObjectsForEntityWithName:, which deletes all data in a given entity. Listing 3-10 shows the Objective-C implementation of this method, and Listing 3-11 shows the Swift implementation.

Listing 3-10. deleteAllObjectsForEntityWithName: in Persistence.m

- (void)deleteAllObjectsForEntityWithName:(NSString *)name {
NSLog(@"Deleting all objects in entity %@", name);

NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:name];
fetchRequest.resultType = NSManagedObjectIDResultType;

NSArray *objectIDs = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
for (NSManagedObjectID *objectID in objectIDs) {
[self.managedObjectContext deleteObject:[self.managedObjectContext objectWithID:objectID]];
}

[self saveContext];

NSLog(@"All objects in entity %@ deleted", name);
}

Listing 3-11. deleteAllObjectsForEntityWithName in Persistence.swift

func deleteAllObjectsForEntityWithName(name: String) {
println("Deleting all objects in entity \(name)")
var fetchRequest = NSFetchRequest(entityName: name)
fetchRequest.resultType = .ManagedObjectIDResultType

if let managedObjectContext = managedObjectContext {
var error: NSError? = nil
let objectIDs = managedObjectContext.executeFetchRequest(fetchRequest, error: &error)
for objectID in objectIDs! {
managedObjectContext.deleteObject(managedObjectContext.objectWithID(objectID as NSManagedObjectID))
}

saveContext()

println("All objects in entity \(name) deleted")
}
}

This method introduces a performance optimization—rather than fetching all the objects for an entity, we fetch only the object identifiers for the matching objects. We do this by setting the resultType property of the fetch request to NSManagedObjectIDResultType. After fetching all the object identifiers into an array, we iterate through them and tell the managed object context to delete the object for the identifier. When we’re done, we save the context.

The loadWordList: method uses the method you just created to delete all WordCategory and Word instances. It then creates 26 WordCategory instances —one each for letters “a” through “z.” It then parses the word list and adds the words to our Core Data object graph, setting the text, length, and category for each and logging a message after every 100 words it adds so we can track the progress in Xcode’s console. Finally, it saves the managed object context. Listing 3-12 shows the Objective-C loadWordList: method, and Listing 3-13 shows the SwiftloadWordList function.

Listing 3-12. loadWordList: in Persistence.m

- (void)loadWordList:(NSString *)wordList {
// Delete all the existing words and categories
[self deleteAllObjectsForEntityWithName:@"Word"];
[self deleteAllObjectsForEntityWithName:@"WordCategory"];

// Create the categories
NSMutableDictionary *wordCategories = [NSMutableDictionary dictionaryWithCapacity:26];
for (char c = 'a'; c <= 'z'; c++) {
NSString *firstLetter = [NSString stringWithFormat:@"%c", c];
NSManagedObject *wordCategory = [NSEntityDescription insertNewObjectForEntityForName:@"WordCategory" inManagedObjectContext:self.managedObjectContext];
[wordCategory setValue:firstLetter forKey:@"firstLetter"];
[wordCategories setValue:wordCategory forKey:firstLetter];
NSLog(@"Added category '%@'", firstLetter);
}

// Add the words from the list
NSUInteger wordsAdded = 0;
NSArray *newWords = [wordList componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
for (NSString *word in newWords) {
if (word.length > 0) {
NSManagedObject *object = [NSEntityDescription insertNewObjectForEntityForName:@"Word" inManagedObjectContext:self.managedObjectContext];
[object setValue:word forKey:@"text"];
[object setValue:[NSNumber numberWithInteger:word.length] forKey:@"length"];
[object setValue:[wordCategories valueForKey:[word substringToIndex:1]] forKey:@"wordCategory"];
++wordsAdded;
if (wordsAdded % 100 == 0)
NSLog(@"Added %lu words", wordsAdded);
}
}
NSLog(@"Added %lu words", wordsAdded);
[self saveContext];
NSLog(@"Context saved");
}

Listing 3-13. loadWordList in Persistence.swift

func loadWordList(wordList: String) {
// Delete all the existing words and categories
deleteAllObjectsForEntityWithName("Word")
deleteAllObjectsForEntityWithName("WordCategory")

// Create the categories
var wordCategories = NSMutableDictionary(capacity: 26)
for c in "abcdefghijklmnopqrstuvwxyz" {
let firstLetter = "\(c)"
var wordCategory: AnyObject! = NSEntityDescription.insertNewObjectForEntityForName("WordCategory", inManagedObjectContext: self.managedObjectContext!)
wordCategory.setValue(firstLetter, forKey: "firstLetter")
wordCategories.setValue(wordCategory, forKey: firstLetter)
println("Added category '\(firstLetter)'")
}

// Add the words from the list
var wordsAdded = 0
let newWords = wordList.componentsSeparatedByCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
for word in newWords {
if countElements(word) > 0 {
var object: AnyObject! = NSEntityDescription.insertNewObjectForEntityForName("Word", inManagedObjectContext: self.managedObjectContext!)
object.setValue(word, forKey: "text")
object.setValue(countElements(word), forKey: "length")
object.setValue(wordCategories.valueForKey((word as NSString).substringToIndex(1)), forKey: "wordCategory")
wordsAdded++
if wordsAdded % 100 == 0 {
println("Added \(wordsAdded) words")
saveContext()
println("Context saved")
}
}
}
saveContext()
println("Context saved")
}

You might notice something curious about the way this code handles the word categories it creates. Instead of just storing the word categories in Core Data and fetching the correct word category to set in a word’s wordCategory relationship, this code puts the word categories in anNSMutableDictionary instance. This is a performance optimization; it’s much faster to look up the appropriate category for a word in a dictionary than to have Core Data explore its object graph for the same category. As you use Core Data in your applications, don’t be afraid to go outside Core Data where appropriate to improve performance.

Getting a Count

The method to provide statistics about our word list, statistics, returns an NSString that we can plop into the text view in WordList’s UI. We’ll start simply, displaying the number of words in the word list. We’ll continue to add to this method throughout this chapter as we learn more about querying Core Data. Add the code shown in Listing 3-14 to Persistence.m or the code shown in Listing 3-15 to Persistence.swift to return the number of words in the word list.

Listing 3-14. statistics in Persistence.m

- (NSString *)statistics {
NSMutableString *string = [[NSMutableString alloc] init];
[string appendString:[self wordCount]];
return string;
}

Listing 3-15. statistics in Persistence.swift

func statistics() -> String {
var string = NSMutableString()

string.appendString(wordCount())
return string
}

Now implement the wordCount method shown in Listing 3-16 (Objective-C) and Listing 3-17 (Swift).

Listing 3-16. wordCount in Persistence.m

- (NSString *)wordCount {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Word"];
NSUInteger count = [self.managedObjectContext countForFetchRequest:fetchRequest error:nil];
return [NSString stringWithFormat:@"Word Count: %lu\n", count];
}

Listing 3-17. wordCount in Persistence.swift

func wordCount() -> String {
let fetchRequest = NSFetchRequest(entityName: "Word")
var error: NSError? = nil
let count = self.managedObjectContext!.countForFetchRequest(fetchRequest, error: &error)
return "Word Count: \(count)\n"
}

This method creates a fetch request, but instead of calling executeFetchRequest:error:, it calls a different method on our managed object context: countForFetchRequest:error:. This method, instead of returning the results from the fetch request, returns the count of items that the fetch request would return. In this case, it returns the number of objects in the Word entity, which gives us the number of words.

Displaying the Statistics

Finally, we add code to the interface to handle presses on the Load Word List button and display statistics about the word list. Listing 3-18 (Objective-C) and Listing 3-19 (Swift) shows the updated ViewController.m or ViewController.swift file, respectively. This code adds anupdateStatistics method that calls Persistence’s statistics method that we just implemented and sets the text of the text view to the returned value. It calls the updateStatistics method both when the view is about to appear and after the word list loads.

The loadWordList: implementation asynchronously loads the list of words from the appropriate URL (uniform resource locator) and hands over that list of words to the application’s persistence layer for storage. On completion, it updates the statistics in the text view.

Listing 3-18. ViewController.m

#import "ViewController.h"
#import "AppDelegate.h"
#import "Persistence.h"

@implementation ViewController

- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self updateStatistics];
}

- (void)updateStatistics {
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
self.textView.text = [appDelegate.persistence statistics];
}

- (IBAction)loadWordList:(id)sender {
// Hide the button while we're loading
[(UIButton *)sender setHidden:YES];

// Load the words
NSURL *url = [NSURL URLWithString:@"https://dotnetperls-controls.googlecode.com/files/enable1.txt"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSLog(@"Loading words");
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSString *words = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
// Check for successful load
if (words != nil && statusCode == 200) {
// Give the word list to the persistence layer
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.persistence loadWordList:words];
}
else {
// Load failed; show any errors
NSLog(@"Error: %lu", statusCode);
if (error != NULL)
NSLog(@"Error: %@", [error localizedDescription]);
}

// Show the button
[(UIButton *)sender setHidden:NO];

// Update the text view with statistics
[self updateStatistics];
}];
}

@end

Listing 3-19. ViewController.swift

import UIKit

class ViewController: UIViewController {
@IBOutlet weak var textView: UITextView!

override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
updateStatistics()
}

func updateStatistics() {
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
textView.text = appDelegate.persistence?.statistics()
}

@IBAction func loadWordList(sender: AnyObject) {
// Hide the button while we're loading
(sender as UIButton).hidden = true

// Load the words
let url = NSURL(string: "https://dotnetperls-controls.googlecode.com/files/enable1.txt")
let request = NSURLRequest(URL: url!)
println("Loading words")

NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) { (response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in
let words = NSString(data: data, encoding: NSUTF8StringEncoding)

// Check for successful load
if error == nil && response != nil && (response as NSHTTPURLResponse).statusCode == 200 {
// Give the word list to the persistence layer
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
appDelegate.persistence?.loadWordList(words!)
}
else {
// Load failed; show any errors
if response != nil { println("Error: \((response as NSHTTPURLResponse).statusCode)") }
println("Error: \(error.localizedDescription)")
}

// Show the button
(sender as UIButton).hidden = false

// Update the text view with statistics
self.updateStatistics()
}
}
}

Build and run the application, and then tap the Load Word List button. After a few seconds, depending on the speed of your Internet connection and the speed of your computer, you should see the Load Word List button reappear and the word count display in the text area of the application, as shown in Figure 3-4.

image

Figure 3-4. The WordList application with the word count

Throughout the rest of this chapter, we update the statistics method in Persistence to perform more queries on the word list data and return more information to display in the UI.

Querying Relationships

The next output we add to the displayed statistics about our word list is the number of words that start with each letter. The query language used in predicates for fetch requests supports traversing the relationships defined in your data model. You do this by referencing the relationship by name in your predicate, and by using dot notation to access the properties of the entity referred to by the relationship. For example, for the Word entity, wordCategory.firstLetter refers to the wordCategory relationship in Word, which refers to the WordCategory entity, and to the firstLetter attribute in the WordCategory entity.

We can use the wordCategory relationship defined in the Word entity to fetch words by the category they belong to. To do this, we iterate through the alphabet and fetch the count of the words whose wordCategory has the firstLetter set to the current letter. We create a method,wordCountForCategory:, that takes a letter and fetches the count for that letter. That code looks like Listing 3-20 (Objective-C) or Listing 3-21 (Swift).

Listing 3-20. wordCountForCategory: in Persistence.m

- (NSString *)wordCountForCategory:(NSString *)firstLetter {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Word"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"wordCategory.firstLetter = %@", firstLetter];
NSUInteger count = [self.managedObjectContext countForFetchRequest:fetchRequest error:nil];
return [NSString stringWithFormat:@"Words beginning with %@: %lu\n", firstLetter, count];
}

Listing 3-21. wordCountForCategory in Persistence.swift

func wordCountForCategory(firstLetter: String) -> String {
var fetchRequest = NSFetchRequest(entityName: "Word")
fetchRequest.predicate = NSPredicate(format: "wordCategory.firstLetter = %@", argumentArray: [firstLetter])

var error: NSError? = nil
let count = self.managedObjectContext!.countForFetchRequest(fetchRequest, error: &error)

return "Words beginning with \(firstLetter): \(count)\n"
}

Then, we update the statistics method to iterate through the alphabet, calling wordCountForCategory: for each letter and appending the results to the statistics. Listing 3-22 (Objective-C) or Listing 3-23 (Swift) shows that code.

Listing 3-22. The Updated statistics Method in Persistence.m

- (NSString *)statistics {
NSMutableString *string = [[NSMutableString alloc] init];
[string appendString:[self wordCount]];
for (char c = 'a'; c <= 'z'; c++) {
[string appendString:[self wordCountForCategory:[NSString stringWithFormat:@"%c", c]]];
}
return string;
}

Listing 3-23. The Updated statistics Function in Persistence.swift

func statistics() -> String {
var string = NSMutableString()
string.appendString(wordCount())

for c in "abcdefghijklmnopqrstuvwxyz" {
string.appendString(wordCountForCategory("\(c)"))
}

return string
}

Now when you build and run the application, you should see the word count by letter, as shown in Figure 3-5. Note that you don’t need to reload the word list each time you run the application; the words are already stored in Core Data, and our queries run against the existing data.

image

Figure 3-5. The WordList application with the word count by letter

Understanding Predicates and Expressions

Chapter 2 discusses predicates and the two ways to build them.

· Using the query language; and

· Using the NSExpression class to manually build predicates.

In the wordCountForCategory: method we just implemented, we use the predicate query language to filter the fetch results. The predicate query language has similarities to a structured query language (SQL) WHERE clause: you state the attributes and relationships you want to filter on (the columns), the values these properties should be compared to, and how they should be compared. For example, we can retrieve the word “zyzzyvas” from our word list with the predicate “text = 'zyzzyvas'.” The predicate query language is usually simpler to write and easier to use than the NSExpression class to build predicates. The query language doesn’t cover all the cases that NSExpression does, however, and you also might find scenarios in which you prefer using NSExpression, so understanding both approaches is essential for fetching the data you target.

Put simply, a predicate executes a test on an object. If the test passes, the object is fetched, and if it fails, the object is not fetched. A predicate contains two or more expressions and one or more comparators. In the predicate in the preceding paragraph, the attribute text and the constant string 'zyzzyvas' are expressions, and the operator = is the comparator, which compares the text value of a Word to 'zyzzyvas' and returns the Word object if they match.

To build an equivalent predicate using NSExpression, you would create two expressions: one for text and one for "zyzzyvas", and then create an NSComparisonPredicate instance to compare the two expressions to see whether they’re equal. That code looks like Listing 3-24(Objective-C) or Listing 3-25 (Swift).

Listing 3-24. Building a Predicate with NSExpression (Objective-C)

NSExpression *expressionText = [NSExpression expressionForKeyPath:@"text"];
NSExpression *expressionZyzzyvas = [NSExpression expressionForConstantValue:@"zyzzyvas"];
NSPredicate *predicate = [NSComparisonPredicate predicateWithLeftExpression:expressionText
rightExpression:expressionZyzzyvas
modifier:NSDirectPredicateModifier
type:NSEqualToPredicateOperatorType
options:0];

Listing 3-25. Building a Predicate with NSExpression (Swift)

let expressionText = NSExpression(forKeyPath: "text")
let expressionZyzzyvas = NSExpression(forConstantValue: "zyzzyvas")
let predicate = NSComparisonPredicate(leftExpression: expressionText, rightExpression: expressionZyzzyvas, modifier: .DirectPredicateModifier, type: .EqualToPredicateOperatorType, options: NSComparisonPredicateOptions.convertFromNilLiteral())

This code is clearly more complex, but it accomplishes the same thing as the query language predicate. Over the next few sections, we dig deeper into how to create predicates this way. Before going further, however, prove that both approaches work by adding methods to retrieve the word “zyzzyvas” both ways in the WordList application and display the results in the text view. Also, log the predicate created using NSExpression by calling NSPredicate’s predicateFormat method. Listing 3-26 shows the Objective-C code, and Listing 3-27 shows the Swift code.

Listing 3-26. Using Both the Query Language and NSExpression (Objective-C)

- (NSString *)statistics {
NSMutableString *string = [[NSMutableString alloc] init];
[string appendString:[self wordCount]];
for (char c = 'a'; c <= 'z'; c++) {
[string appendString:[self wordCountForCategory:[NSString stringWithFormat:@"%c", c]]];
}
[string appendString:[self zyzzyvasUsingQueryLanguage]];
[string appendString:[self zyzzyvasUsingNSExpression]];
return string;
}

- (NSString *)zyzzyvasUsingQueryLanguage {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Word"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"text = 'zyzzyvas'"];
NSArray *words = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
return words.count == 0 ? @"" : [NSString stringWithFormat:@"%@\n", [words[0] valueForKey:@"text"]];
}
- (NSString *)zyzzyvasUsingNSExpression {
NSExpression *expressionText = [NSExpression expressionForKeyPath:@"text"];
NSExpression *expressionZyzzyvas = [NSExpression expressionForConstantValue:@"zyzzyvas"];
NSPredicate *predicate = [NSComparisonPredicate predicateWithLeftExpression:expressionText
rightExpression:expressionZyzzyvas
modifier:NSDirectPredicateModifier
type:NSEqualToPredicateOperatorType
options:0];
NSLog(@"Predicate: %@", [predicate predicateFormat]);
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Word"];
fetchRequest.predicate = predicate;
NSArray *words = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
return words.count == 0 ? @"" : [NSString stringWithFormat:@"%@\n", [words[0] valueForKey:@"text"]];
}

Listing 3-27. Using Both the Query Language and NSExpression (Swift)

func statistics() -> String {
var string = NSMutableString()

string.appendString(wordCount())

for c in "abcdefghijklmnopqrstuvwxyz" {
string.appendString(wordCountForCategory("\(c)"))
}

string.appendString(zyzzyvasUsingQueryLanguage())
string.appendString(zyzzyvasUsingNSExpression())

return string
}

func zyzzyvasUsingQueryLanguage() -> String {
var fetchRequest = NSFetchRequest(entityName: "Word")
fetchRequest.predicate = NSPredicate(format: "text = 'zyzzyvas'", argumentArray: [])

var error: NSError? = nil
if let words = self.managedObjectContext!.executeFetchRequest(fetchRequest, error: &error) {
if words.isEmpty { return "" }
else {
let word: AnyObject! = words[0].valueForKey("text")
return "\(word)\n"
}
else { return "" }
}
}

func zyzzyvasUsingNSExpression() -> String {
let expressionText = NSExpression(forKeyPath: "text")
let expressionZyzzyvas = NSExpression(forConstantValue: "zyzzyvas")
let predicate = NSComparisonPredicate(leftExpression: expressionText, rightExpression: expressionZyzzyvas, modifier: .DirectPredicateModifier, type: .EqualToPredicateOperatorType, options: nil)

println("Predicate: \(predicate.predicateFormat)")

var fetchRequest = NSFetchRequest(entityName: "Word")
fetchRequest.predicate = predicate

var error: NSError? = nil
if let words = self.managedObjectContext!.executeFetchRequest(fetchRequest, error: &error) {
if words.isEmpty { return "" }
else {
let word: AnyObject! = words[0].valueForKey("text")
return "\(word)\n"
}
}
else { return "" }
}

If you run the WordList application, you should see the word “zyzzyvas” appear twice in the text view. You should also see the text version of the predicate in the logs.

Predicate: text == "zyzzyvas"

Viewing Your SQL Queries

For developers more familiar with SQL statements than with NSPredicates and NSExpressions, you can instruct your application to log the SQL statements it uses when performing fetch requests. This can help you understand how Core Data interprets your predicates and expressions, making debugging your fetch requests simpler.

To turn on SQL debugging for your application, edit the Run scheme in Xcode to add these arguments in the Arguments Passed on Launch section.

-com.apple.CoreData.SQLDebug 1

Note the dash in front. Your Xcode scheme setup should match Figure 3-6.

image

Figure 3-6. Adding -com.apple.CoreData.SQLDebug 1 to the arguments passed on launch

Add these arguments to the WordList Run schema, then launch the application. You should now see the SQL statements WordList uses to fetch its data, along with other information, like query duration, in the Xcode console. The output should look something like the following:

2014-07-30 11:45:15.647 WordList[65858:2586192] CoreData: sql: SELECT COUNT(DISTINCT t0.Z_PK) FROM ZWORD t0 JOIN ZWORDCATEGORY t1 ON t0.ZWORDCATEGORY = t1.Z_PK WHERE t1.ZFIRSTLETTER = ?
2014-07-30 11:45:15.699 WordList[65858:2586192] CoreData: annotation: total count request execution time: 0.0528s for count of 10456.

You can actually coax Core Data into giving you even more SQL information in the console by specifying 2 or 3 instead of 1—the higher the number, the more information Core Data will log. Try, for example, changing the launch arguments to the following:

-com.apple.CoreData.SQLDebug 3

Run the WordList application again and look in the console for even richer information, such as the following:

2014-07-31 15:29:04.797 WordList[73416:2971038] CoreData: sql: SELECT COUNT(DISTINCT t0.Z_PK) FROM ZWORD t0 JOIN ZWORDCATEGORY t1 ON t0.ZWORDCATEGORY = t1.Z_PK WHERE t1.ZFIRSTLETTER = ?
2014-07-31 15:29:04.797 WordList[73416:2971038] CoreData: details: SQLite bind[0] = "s"
2014-07-31 15:29:04.822 WordList[73416:2971038] CoreData: annotation: count request <NSFetchRequest: 0x7fc6a292d850> (entity: Word; predicate: (wordCategory.firstLetter == "s"); sortDescriptors: ((null)); type: NSCountResultType;) returned 18988
2014-07-31 15:29:04.822 WordList[73416:2971038] CoreData: annotation: total count request execution time: 0.0254s for count of 18988.

Seeing the actual SQL statements that Core Data derives from your predicates and expressions can help you troubleshoot and verify how you’re retrieving data.

Creating Single-Value Expressions

Expressions can represent either single values or collections of values. NSPredicate supports four types of single-value expressions, as shown in Table 3-1.

Table 3-1. The Four Types of Single-Value Expressions

image

The zyzzyvasUsingNSExpression method in the WordList application creates two single-value expressions: one using a key path and one using a constant expression. The expressionText expression resolves to the value stored in the key path "text" of a Word entity.

NSExpression *expressionText = [NSExpression expressionForKeyPath:@"text"]; // Objective-C
let expressionText = NSExpression(forKeyPath: "text") // Swift

The constant value expression, expressionZyzzyvas, represents the constant value "zyzzyvas".

NSExpression *expressionZyzzyvas = [NSExpression expressionForConstantValue:@"zyzzyvas"]; // Objective-C
let expressionZyzzyvas = NSExpression(forConstantValue: "zyzzyvas") // Swift

You might not use the evaluated object type often, if at all, when using Core Data. The evaluated object type refers to the NSManagedObject instance itself, and usually you’ll want to query the attributes and relationships of the objects, not the objects themselves. We can do something like query for certain object IDs, which might meet some esoteric need of your application. We leave other ideas for use up to the reader.

Creating Collection Expressions

The single-value expressions allow predicates to compare objects against single values to determine inclusion in the result set. Sometimes you want to compare objects against multiple values, however, to determine whether to include them. A collection expression contains the multipleNSExpression instances that predicates use for evaluation. In this section, we examine the Aggregate expression type, which you create using NSExpression’s +expressionForAggregate: static initializer in Objective-C, or init(forAggregate collection: [AnyObject]!) initializer in Swift.

Although Apple’s documentation for NSExpression says, “Aggregate expressions are not supported by Core Data,” in practice this isn’t always true. Let’s suppose, for example, that we wanted to retrieve words that have between 20 and 25 letters, inclusively. We would create twoNSExpression instances, one for the constant value 20 and one for the constant value 25, and then create an aggregate expression that contains them both. We would then use that aggregate expression in our predicate. Listing 3-28 shows how to create that aggregate expression in Objective-C, and Listing 3-29 shows how to create it in Swift.

Listing 3-28. Creating an Aggregate Expression in Objective-C

NSExpression *lower = [NSExpression expressionForConstantValue:@20];
NSExpression *upper = [NSExpression expressionForConstantValue:@25];
NSExpression *expr = [NSExpression expressionForAggregate:@[lower, upper]];

Listing 3-29. Creating an Aggregate Expression in Swift

let lower = NSExpression(forConstantValue: 20)
let upper = NSExpression(forConstantValue: 25)
let expr = NSExpression(forAggregate: [lower, upper])

To incorporate this into the WordList application, add the Objective-C method shown in Listing 3-30 or the Swift function shown in Listing 3-31, which takes a range (an NSRange instance) and finds the count of words whose length lies in that range. Then, add a call to it in thestatistics method.

[string appendString:[self wordCountForRange:NSMakeRange(20, 25)]]; // Objective-C
string.appendString(wordCountForRange(NSMakeRange(20, 25))) // Swift

Listing 3-30. Using an Aggregate Expression to Find Words with a Length in Range (Objective-C)

- (NSString *)wordCountForRange:(NSRange)range {
NSExpression *length = [NSExpression expressionForKeyPath:@"length"];
NSExpression *lower = [NSExpression expressionForConstantValue:@(range.location)];
NSExpression *upper = [NSExpression expressionForConstantValue:@(range.length)];
NSExpression *expr = [NSExpression expressionForAggregate:@[lower, upper]];
NSPredicate *predicate = [NSComparisonPredicate predicateWithLeftExpression:length
rightExpression:expr
modifier:NSDirectPredicateModifier
type:NSBetweenPredicateOperatorType
options:0];
NSLog(@"Aggregate Predicate: %@", [predicate predicateFormat]);
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Word"];
fetchRequest.predicate = predicate;
NSUInteger count = [self.managedObjectContext countForFetchRequest:fetchRequest error:nil];
return [NSString stringWithFormat:@"%lu-%lu letter words: %lu\n", range.location, range.length, count];
}

Listing 3-31. Using an Aggregate Expression to Find Words with a Length in Range (Swift)

func wordCountForRange(range: NSRange) -> String {
let length = NSExpression(forKeyPath: "length")
let lower = NSExpression(forConstantValue: range.location)
let upper = NSExpression(forConstantValue: range.length)
let expr = NSExpression(forAggregate: [lower, upper])
let predicate = NSComparisonPredicate(leftExpression: length, rightExpression: expr, modifier: .DirectPredicateModifier, type: .BetweenPredicateOperatorType, options: nil)

println("Aggregate predicate: \(predicate.predicateFormat)")

var fetchRequest = NSFetchRequest(entityName: "Word")
fetchRequest.predicate = predicate

var error: NSError? = nil
let count = self.managedObjectContext!.countForFetchRequest(fetchRequest, error: &error)

return "\(range.location)-\(range.length) letter words: \(count)\n"
}

The next section, “Comparing Expressions Using Different Predicate Types,” covers the different predicate types, including NSBetweenPredicateOperatorType.

Run the WordList application, and you should see a line in the output like the following:

20-25 letter words: 276

You can also see, in the Xcode console, that the predicate’s query language incorporates the two constant values in a BETWEEN clause, as we’d expect.

length BETWEEN {20, 25}

Comparing Expressions Using Different Predicate Types

In addition to expressions, predicates require comparators to evaluate whether to include objects in their result sets. The NSComparisonPredicate class, a subclass of NSPredicate, compares two expressions with each other using the type of comparator you specify when you create it. You can, for example, require that the expressions are equal for the evaluated object to be included in the result set. We saw this when comparing Word objects with the constant expression "zyzzyvas": only words with that exact text were returned.

We also saw a "between" type comparator with the wordCountForRange: method, which required that a word have a length between two integers for inclusion in the result set. NSComparisonPredicate offers a slew of other comparison types to fit other comparison needs. SeeTable 3-2 for a list of the comparison types. The Query Language and Logical Description columns use L and R to represent the left expression and right expression being compared, respectively. The Swift types are the same as the Objective-C types, except that the Objective-C types begin with NS and the Swift types do not.

Table 3-2. The Comparison Types

Objective-C or Swift Type

Query Language

Logical Description

(NS)LessThanPredicateOperatorType

L < R

L less than R

(NS)LessThanOrEqualToPredicateOperatorType

L <= R

L less than or equal to R

(NS)GreaterThanPredicateOperatorType

L > R

L greater than R

(NS)GreaterThanOrEqualToPredicateOperatorType

L >= R

L greater than or equal to R

(NS)EqualToPredicateOperatorType

L = R

L equal to R

(NS)NotEqualToPredicateOperatorType

L != R

L not equal to R

(NS)MatchesPredicateOperatorType

L MATCHES R

L matches the R regular expression

(NS)LikePredicateOperatorType

L LIKE R

L = R where R can contain * wildcards

(NS)BeginsWithPredicateOperatorType

L BEGINSWITH R

L begins with R

(NS)EndsWithPredicateOperatorType

L ENDSWITH R

L ends with R

(NS)InPredicateOperatorType

L IN R

L is in the collection R

(NS)ContainsPredicateOperatorType

L CONTAINS R

L is a collection that contains R

(NS)BetweenPredicateOperatorType

L BETWEEN R

L is a value between the two values of the array R

Note NSLikePredicateOperatorType is a simplified version of NSMatchesPredicateOperatorType. While NSMatchesPredicateOperatorType can use complex regular expressions, NSLikePredicateOperatorType uses simple wildcard replacement (*) characters, so that the expression text LIKE ‘b*ball’ would match baseball and basketball but not football.

We can use NSEndsWithPredicateOperatorType, for example, to find all the words that end with the letters "gry." Listing 3-32 shows the endsWithGryWords method in Objective-C, and Listing 3-33 shows it in Swift, to do just that. Don’t forget to add a call to it in yourstatistics method, and then build and run the application to see "puggry, hungry, angry" (in no particular order) added to the output.

Listing 3-32. The endsWithGryWords Method in Objective-C

- (NSString *)endsWithGryWords {
NSExpression *text = [NSExpression expressionForKeyPath:@"text"];
NSExpression *gry = [NSExpression expressionForConstantValue:@"gry"];
NSPredicate *predicate = [NSComparisonPredicate predicateWithLeftExpression:text
rightExpression:gry
modifier:NSDirectPredicateModifier
type:NSEndsWithPredicateOperatorType
options:0];
NSLog(@"Predicate: %@", [predicate predicateFormat]);
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Word"];
fetchRequest.predicate = predicate;
NSArray *gryWords = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
return [NSString stringWithFormat:@"-gry words: %@\n",
[[gryWords valueForKey:@"text"] componentsJoinedByString:@","]];
}

Listing 3-33. The endsWithGryWords Method in Swift

func endsWithGryWords() -> String {
let text = NSExpression(forKeyPath: "text")
let gry = NSExpression(forConstantValue: "gry")
let predicate = NSComparisonPredicate(leftExpression: text, rightExpression: gry, modifier: .DirectPredicateModifier, type: .EndsWithPredicateOperatorType, options: nil)

println("Predicate: \(predicate.predicateFormat)")

var fetchRequest = NSFetchRequest(entityName: "Word")
fetchRequest.predicate = predicate

let list = NSMutableString()
var error: NSError? = nil
if let gryWords = self.managedObjectContext!.executeFetchRequest(fetchRequest, error: &error) {
for word in gryWords {
list.appendString(((word as NSManagedObject).valueForKey("text") as String)+",")
}
}
return "-gry words: \(list)\n"
}

You can look in the Xcode console to see that the query language equivalent of this predicate is

text ENDSWITH "gry"

Using Different Comparison Modifiers

The third parameter to the NSComparisonPredicate static initializer method we’ve been using is modifier, and we’ve been passing NSDirectPredicateModifier exclusively for this parameter. If the left expression is a collection rather than a single object, however, you have a few more modifiers to choose from, as shown in Table 3-3. As with the predicate type constants, the Objective-C constants begin with NS and the Swift ones do not.

Table 3-3. The Comparison Predicate Modifiers

Modifier

Query Language Example

Description

(NS)DirectPredicateModifier

X

Compares the left expression directly to the right expression

(NS)AllPredicateModifier

ALL X

Compares the left expression (a collection) to the right expression, returning YES only if all the values match

(NS)AnyPredicateModifier

ANY X

Compares the left expression (a collection) to the right expression, returning YES if any of the values match

The NSAllPredicateModifier constant, as the table shows, evaluates to the query language expression ALL X, and returns YES only if all the values in the collection match the predicate. Otherwise, it returns NO. The NSAnyPredicateModifier constant, on the other hand, which evaluates to ANY X, returns YES if at least one of the values in the collection matches the predicate. Otherwise, it returns NO.

Remember earlier in this chapter when we mentioned that Apple’s documentation claims that Core Data doesn’t support aggregate expressions? In the case of NSAllPredicateModifier, this is true. If you try to run an ALL X query, your application will crash. You can use theNSAnyPredicateModifier, however, as the anyWordContainsZ method shown in Listing 3-34 (Objective-C) and Listing 3-35 (Swift) illustrate. This method lists all the categories for which any word contains the letter "z." Add the method and a call to it in statistics, build and run the application, and you’ll see that all categories except "x" have at least one word that contains the letter "z."

Listing 3-34. The anyWordContainsZ Method (Objective-C)

- (NSString *)anyWordContainsZ { NSExpression *text = [NSExpression expressionForKeyPath:@"words.text"];
NSExpression *z = [NSExpression expressionForConstantValue:@"z"];
NSPredicate *predicate = [NSComparisonPredicate predicateWithLeftExpression:text
rightExpression:z
modifier:NSAnyPredicateModifier
type:NSContainsPredicateOperatorType
options:0];
NSLog(@"Predicate: %@", [predicate predicateFormat]);
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"WordCategory"];
fetchRequest.predicate = predicate;
NSArray *categories = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
return [NSString stringWithFormat:@"ANY: %@\n",
[[categories valueForKey:@"firstLetter"] componentsJoinedByString:@","]];
}

Listing 3-35. The anyWordContainsZ Method (Swift)

func anyWordContainsZ() -> String {
let text = NSExpression(forKeyPath: "words.text")
let z = NSExpression(forConstantValue: "z")
let predicate = NSComparisonPredicate(leftExpression: text, rightExpression: z, modifier:.AnyPredicateModifier, type: .ContainsPredicateOperatorType, options: nil)

println("Predicate: \(predicate.predicateFormat)")

var fetchRequest = NSFetchRequest(entityName: "WordCategory")
fetchRequest.predicate = predicate

let list = NSMutableString()
var error: NSError? = nil
if let categories = self.managedObjectContext!.executeFetchRequest(fetchRequest, error: &error) {
for word in categories {
list.appendString(((word as NSManagedObject).valueForKey("firstLetter") as String)+",")
}
}

return "ANY: \(list)\n"
}

Using Different Options

The final parameter to the NSComparisonPredicate static initializer is options, for which we’ve been blithely passing 0 (or NSComparisonPredicatOptions.convertFromNilLiteral() for Swift). You actually have four other values to choose from for this parameter for string comparison comparators only. Each option has an equivalent code you can use in the query language by appending the code between square brackets right after the operator. Table 3-4 lists the available options, codes, and how to use them in the query language. Again, the Objective-C options begin with NS, and the Swift ones do not.

Table 3-4. The String Comparison Options

image

You can specify multiple options using bitwise OR flags, or put multiple codes together if using the query language. To compare both case insensitively and diacritic insensitively, for example, you would pass for the options parameter.

NSCaseInsensitivePredicateOption | NSDiacriticInsensitivePredicateOption

Using the query language, you’d do something like the following:

"È" =[cd] "e"

Although our list of words doesn’t contain any accented or non-English characters, we can verify that the case-insensitive option works. Create a new method called caseInsensitiveFetch: that takes an NSString parameter. In that method, convert the string to upper case, then fetch the word using the NSCaseInsensitivePredicateOption option. Listing 3-36 (Objective-C) and Listing 3-37 (Swift) show what your method or function should look like.

Listing 3-36. The caseInsensitiveFetch: Method (Objective-C)

- (NSString *)caseInsensitiveFetch:(NSString *)word {
NSExpression *text = [NSExpression expressionForKeyPath:@"text"];
NSExpression *allCapsWord = [NSExpression expressionForConstantValue:[word uppercaseString]];
NSPredicate *predicate = [NSComparisonPredicate predicateWithLeftExpression:text
rightExpression:allCapsWord
modifier:NSDirectPredicateModifier
type:NSEqualToPredicateOperatorType
options:NSCaseInsensitivePredicateOption];
NSLog(@"Predicate: %@", [predicate predicateFormat]);
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Word"];
fetchRequest.predicate = predicate;
NSArray *words = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
return [NSString stringWithFormat:@"%@\n", words.count == 0 ? @"" : [words[0] valueForKey:@"text"]];
}

Listing 3-37. The caseInsensitiveFetch: Function (Swift)

func caseInsensitiveFetch(word: String) -> String {
let text = NSExpression(forKeyPath: "text")
let allCapsWord = NSExpression(forConstantValue: word.uppercaseString)
let predicate = NSComparisonPredicate(leftExpression: text, rightExpression: allCapsWord, modifier: .DirectPredicateModifier, type: .EqualToPredicateOperatorType, options: .CaseInsensitivePredicateOption)

println("Predicate: \(predicate.predicateFormat)")

var fetchRequest = NSFetchRequest(entityName: "Word")
fetchRequest.predicate = predicate

var error: NSError? = nil
if let words = self.managedObjectContext!.executeFetchRequest(fetchRequest, error: &error) {
if words.isEmpty { return "" }
else {
let word: AnyObject! = words[0].valueForKey("text")
return "\(word)\n"
}
}
else { return "" }
}

Then, add a call to that method in your statistics method, passing the word of your choice, as follows:

[string appendString:[self caseInsensitiveFetch:@"qiviut"]]; // Objective-C
string.appendString(caseInsensitiveFetch("qiviut")) // Swift

You should see the query-language equivalent in your Xcode console, and your word in the WordList display area. The query-language equivalent looks as follows:

text ==[c] "QIVIUT"

Adding It Up: Using Compound Predicates

We’ve been using predicates with one criterion, whether it has been words that belong to a certain category or words that match a specific text. You often must combine criteria, however, to extract the data you’re really after. You can form predicates by combining multiple subpredicates using the logical operators OR, AND, and NOT. You might, for example, want to find all the 20-letter words that end in "-ing," or words that have a "q" but not a "u." For these types of predicates, you need multiple criteria.

Core Data allows you to build compound predicates using the NSCompoundPredicate class. You build them using one of the three static initializers (or the same-named functions in Swift) shown in Table 3-5.

Table 3-5. Static Initializers for Creating Compound Predicates

Initializer Name

Query Language Example

Description

+andPredicateWithSubpredicates:

P1 AND P2 AND P3

Returns YES only if all subpredicates return YES

+orPredicateWithSubpredicates:

P1 OR P2 OR P3

Returns YES if at least one of the subpredicates returns YES

+notPredicateWithSubpredicate:

NOT P

Returns YES if its only subpredicate returns NO

To get a list of the 20-letter words that end in "-ing," create a method called twentyLetterWordsEndingInIng, as shown in Listing 3-38 (Objective-C) and Listing 3-39 (Swift), and add a call to it in the statistics method. This code creates a predicate that compares the length of each word to 20. It creates another predicate that compares the ending of each word to "-ing." It then creates a compound predicate that combines the two predicates using the logical operator AND.

Listing 3-38. Using Compound Predicates in Objective-C

- (NSString *)twentyLetterWordsEndingInIng {
// Create a predicate that compares length to 20
NSExpression *length = [NSExpression expressionForKeyPath:@"length"];
NSExpression *twenty = [NSExpression expressionForConstantValue:@20];
NSPredicate *predicateLength = [NSComparisonPredicate predicateWithLeftExpression:length
rightExpression:twenty
modifier:NSDirectPredicateModifier
type:NSEqualToPredicateOperatorType
options:0];

// Create a predicate that compares text to "ends with -ing"
NSExpression *text = [NSExpression expressionForKeyPath:@"text"];
NSExpression *ing = [NSExpression expressionForConstantValue:@"ing"];
NSPredicate *predicateIng = [NSComparisonPredicate predicateWithLeftExpression:text
rightExpression:ing
modifier:NSDirectPredicateModifier
type:NSEndsWithPredicateOperatorType
options:0];

// Combine the predicates
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:@[predicateLength, predicateIng]];
NSLog(@"Compound predicate: %@", [predicate predicateFormat]);

NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Word"];
fetchRequest.predicate = predicate;
NSArray *words = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
return [NSString stringWithFormat:@"%@\n", [[words valueForKey:@"text"] componentsJoinedByString:@","]];
}

Listing 3-39. Using Compound Predicates in Swift

func twentyLetterWordsEndingInIng() -> String {
// Create a predicate that compares length to 20
let length = NSExpression(forKeyPath: "length")
let twenty = NSExpression(forConstantValue: 20)
let predicateLength = NSComparisonPredicate(leftExpression: length, rightExpression: twenty, modifier: .DirectPredicateModifier, type: .EqualToPredicateOperatorType, options: nil)

// Create a predicate that compares text to "ends with -ing"
let text = NSExpression(forKeyPath: "text")
let ing = NSExpression(forConstantValue: "ing")
let predicateIng = NSComparisonPredicate(leftExpression: text, rightExpression: ing, modifier: .DirectPredicateModifier, type: .EndsWithPredicateOperatorType, options: nil)

// Combine the predicates
let predicate = NSCompoundPredicate.andPredicateWithSubpredicates([predicateLength, predicateIng])

println("Compound predicate: \(predicate.predicateFormat)")

var fetchRequest = NSFetchRequest(entityName: "Word")
fetchRequest.predicate = predicate

let list = NSMutableString()
var error: NSError? = nil
if let words = self.managedObjectContext!.executeFetchRequest(fetchRequest, error: &error) {
for word in words {
list.appendString(((word as NSManagedObject).valueForKey("text") as String)+",")
}
}

return "\(list)\n"
}

When you build and run WordList, you’ll see the four 20-letter words that end in "-ing" in the WordList UI. You’ll also see the query language equivalent in the Xcode console, which looks like the following:

length == 20 AND text ENDSWITH "ing"

When you have to filter your data against multiple criteria to fetch what you want, remember to combine predicates into a compound predicate and let Core Data do the work of applying all the criteria for you. Oh, and if you build the compound predicate referred to previously, to find words that contain a "q" but not a "u," you’ll see the 21 words that match.

Aggregating

Aggregating data refers to gathering global statistics on a collection using a collection operator. Collection operators are part of the Key-Value Coding paradigm and can be added to a key path to indicate that an aggregation operation should be executed. The syntax for using these aggregation operators is

(key path to collection).@(operator).(key path to argument property)

For example, to refer to the average length of the words in the word list, you’d use

words.@avg.length

The Apple documentation refers to these operators as Set and Array Operators, as you can use them on any collection, not just Core Data result sets. You can also use them as part of a predicate; for example, to get all the categories that have more than 10,000 words, you’d use a predicate with the query language.

words.@count > 10000

Table 3-6 lists the operators, which are the same in Objective-C or Swift.

Table 3-6. The Aggregation Operators

Operator

Constant

Description

avg

NSAverageKeyValueOperator

Computes the average of the argument property in the collection

count

NSCountKeyValueOperator

Computes the number of items in the collection (does not use an argument property)

min

NSMinimumKeyValueOperator

Computes the minimum value of the argument property in the collection

max

NSMaximumKeyValueOperator

Computes the maximum value of the argument property in the collection

sum

NSSumKeyValueOperator

Computes the sum value of the argument property in the collection

Using @count to Retrieve Counts

The @count function returns a count of the number of objects that match the predicate. We can use this to return a list of letters that have a high number of words, which we arbitrarily set at more than 10,000, as described previously. The fetch request we set up to retrieve these data target theCategory entity and fetch the category objects whose count of word relationships exceeds 10,000. This gives us the letters that begin more than 10,000 words.

We use the query language to set up this predicate, as seen in the highCountCategories method shown in Listing 3-40 (Objective-C) and Listing 3-41 (Swift).

Listing 3-40. Using the @count Function in Objective-C

- (NSString *)highCountCategories {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"words.@count > %d", 10000];
NSLog(@"Predicate: %@", [predicate predicateFormat]);

NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"WordCategory"];
fetchRequest.predicate = predicate;
NSArray *categories = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
return [NSString stringWithFormat:@"High count categories: %@\n",
[[categories valueForKey:@"firstLetter"] componentsJoinedByString:@","]];
}

Listing 3-41. Using the @count Function in Swift

func highCountCategories() -> String {
let predicate = NSPredicate(format: "words.@count > %d", argumentArray: [10000])
println("Predicate: \(predicate.predicateFormat)")

var fetchRequest = NSFetchRequest(entityName: "WordCategory")
fetchRequest.predicate = predicate

let list = NSMutableString()
var error: NSError? = nil
if let categories = self.managedObjectContext!.executeFetchRequest(fetchRequest, error: &error) {
for word in categories {
list.appendString(((word as NSManagedObject).valueForKey("firstLetter") as String)+",")
}
}

return "High count categories: \(list)\n"
}

This code creates a predicate that checks the count of the words relationship, verifying that it’s greater than 10,000, and then creates a fetch request for the WordCategory entity that uses that predicate. It returns the qualifying letters in a comma-separated list.

Add a call to this method in the statistics method. Then, build and run the application to see the letters that have more than 10,000 words: a, c, d, p, r, and s.

Getting the Average Length of All Words—Two Ways

Using the aggregation operators directly on collections is straightforward: fetch the data using a Core Data fetch request, then apply the operator. Listing 3-42 (Objective-C) and Listing 3-43 (Swift), for example, show how to retrieve the average word length directly.

Listing 3-42. Getting the Average Word Length from the Collection in Objective-C

- (NSString *)averageWordLengthFromCollection {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Word"];
NSArray *words = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
return [NSString stringWithFormat:@"Average from collection: %.2f\n",
[[words valueForKeyPath:@"@avg.length"] floatValue]];
}

Listing 3-43. Getting the Average Word Length from the Collection in Swift

func averageWordLengthFromCollection() -> String {
var fetchRequest = NSFetchRequest(entityName: "Word")

var error: NSError? = nil
let words = self.managedObjectContext!.executeFetchRequest(fetchRequest, error: &error)

var formattedAverage = "0"
if let words = words {
if let avg = (words as NSArray).valueForKeyPath("@avg.length") as? Float {
let format = ".2"
formattedAverage = avg.format(format)
}
}
return "Average from collection: \(formattedAverage)\n"
}

This code fetches all the words, then applies the @avg operator using the following code:

[words valueForKeyPath:@"avg.length"] // Objective-C
(words as NSArray).valueForKeyPath("@avg.length") as Float // Swift

Add a call to it in the statistics method, then run it to see that the average length is 9.09 characters.

You can see, however, that you’re trading simplicity for efficiency. This code reads in all the words, then calculates the average of the lengths. We can instead use NSExpression and another class, NSExpressionDescription, to fetch only the average using Core Data. To begin, we use another Objective-C static initializer that NSExpression offers: +expressionForFunction:arguments:, which takes a predefined function name and an array of NSExpression objects. The Swift counterpart is similar: a function in the format init(forFunction name: String!, arguments parameters: [AnyObject]!) that also takes a predefined function name and an array of NSExpression objects.

The predefined function names are similar to, but don’t quite match, the aggregator function names. The Apple-provided documentation lists them all, but we highlight only the few shown in Table 3-7.

Table 3-7. Some of the Predefined Functions for +expressionForFunction:arguments:

Function Name

Description

average:

Returns an average of the values in the array

sum:

Returns the sum of the values in the array

count:

Returns the count of the values in the array

min:

Returns the minimum value in the array

max:

Returns the maximum value in the array

Since we want the average word length, we use the average: predefined function and we pass an NSExpression that represents the length property of the words. Listing 3-44 shows the Objective-C code and Listing 3-45 shows the Swift code to create this expression.

Listing 3-44. Create an NSExpression using +expressionForFunction:arguments:

NSExpression *length = [NSExpression expressionForKeyPath:@"length"];
NSExpression *average = [NSExpression expressionForFunction:@"average:" arguments:@[length]];

Listing 3-45. Create an NSExpression using init(forFunction name: String!, arguments parameters: [AnyObject]!)

let length = NSExpression(forKeyPath: "length")
let average = NSExpression(forFunction: "average:", arguments: [length])

Next, we set up an NSExpressionDescription instance to describe what we want to fetch from our Core Data store. An NSExpressionDescription represents a property on a Core Data entity that doesn’t actually appear in the Core Data model, which is perfect for an expression that fetches the average (since it doesn’t actually exist in our model). As a property, an NSExpressionDescription has a name and a type, as well as an expression. Listing 3-46 (Objective-C) and Listing 3-47 (Swift) show how to create the NSExpressionDescription for fetching the word length average.

Listing 3-46. Creating an NSExpressionDescription for the Word Length Average (Objective-C)

NSExpressionDescription *averageDescription = [[NSExpressionDescription alloc] init];
averageDescription.name = @"average";
averageDescription.expression = average;
averageDescription.expressionResultType = NSFloatAttributeType;

Listing 3-47. Creating an NSExpressionDescription for the Word Length Average (Swift)

var averageDescription = NSExpressionDescription()
averageDescription.name = "average"
averageDescription.expression = average
averageDescription.expressionResultType = .FloatAttributeType

To finish, we create a fetch request, and then set its propertiesToFetch property to an array containing the NSExpressionDescription we just created. We also set the fetch request’s resultType property to a dictionary, so we can reference theNSExpressionDescription we created by the name we set: "average". We execute the fetch request and extract the average value from the result dictionary. Listing 3-48 (Objective-C) and Listing 3-49 (Swift) show the completeaverageWordLengthFromExpressionDescription method. Call it from your statistics method and run to see that, however you get the average, you get 9.09 characters.

Listing 3-48. Getting the Average Word Length Using an Expression Description (Objective-C)

- (NSString *)averageWordLengthFromExpressionDescription {
NSExpression *length = [NSExpression expressionForKeyPath:@"length"];
NSExpression *average = [NSExpression expressionForFunction:@"average:" arguments:@[length]];

NSExpressionDescription *averageDescription = [[NSExpressionDescription alloc] init];
averageDescription.name = @"average";
averageDescription.expression = average;
averageDescription.expressionResultType = NSFloatAttributeType;

NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Word"];
fetchRequest.propertiesToFetch = @[averageDescription];
fetchRequest.resultType = NSDictionaryResultType;
NSArray *results = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
return [NSString stringWithFormat:@"Average from expression description: %.2f\n",
[[results[0] valueForKey:@"average"] floatValue]];
}

Listing 3-49. Getting the Average Word Length Using an Expression Description (Swift)

func averageWordLengthFromExpressionDescription() -> String {
let length = NSExpression(forKeyPath: "length")
let average = NSExpression(forFunction: "average:", arguments: [length])

var averageDescription = NSExpressionDescription()
averageDescription.name = "average"
averageDescription.expression = average
averageDescription.expressionResultType = .FloatAttributeType

var fetchRequest = NSFetchRequest(entityName: "Word")
fetchRequest.propertiesToFetch = [averageDescription]
fetchRequest.resultType = .DictionaryResultType

var error: NSError? = nil
let results = self.managedObjectContext!.executeFetchRequest(fetchRequest, error: &error)

var formattedAverage = "0"
if let results = results {
if let avg = (words as NSArray).valueForKeyPath("@avg.length") as? Float {
let format = ".2"
formattedAverage = avg.format(format)
}
}
return "Average from collection: \(formattedAverage)\n"
}

Using a Function as an Rvalue

You can also use a function in the right side of your predicate. Suppose, for example, we wanted to get the text of the longest word from the word list. We can’t simply use the @max(length) aggregator, because that returns the length of the longest word, not the longest word itself. Instead, we want to return the word object whose length equals the maximum length value, so the predicate tests the length against the max: function as follows:

length = max:(length)

Listing 3-50 (Objective-C) and Listing 3-51 (Swift) show the longestWords method (after all, you could have more than one word with the same length) that uses this predicate. Using SQL Logging, you can see that the SQL that Core Data uses has an inner SELECT statement, as you’d expect. The inner SELECT statement gets the maximum length of a word, and the outer SELECT statement gets all the words with that length.

SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZLENGTH, t0.ZTEXT, t0.ZWORDCATEGORY FROM ZWORD t0 WHERE t0.ZLENGTH = (SELECT max(t1.ZLENGTH) FROM ZWORD t1)

Listing 3-50. The longestWords: Method (Objective-C)

- (NSString *)longestWords {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Word"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"length = max:(length)"];
NSLog(@"Predicate: %@", [predicate predicateFormat]);
fetchRequest.predicate = predicate;
NSArray *words = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
return [NSString stringWithFormat:@"Longest words: %@\n",
[[words valueForKey:@"text"] componentsJoinedByString:@","]];
}

Listing 3-51. The longestWords: Function (Swift)

func longestWords() -> String {
var fetchRequest = NSFetchRequest(entityName: "Word")
let predicate = NSPredicate(format: "length = max:(length)", argumentArray: [])

println("Predicate: \(predicate.predicateFormat)")

fetchRequest.predicate = predicate

let list = NSMutableString()
var error: NSError? = nil
if let words = self.managedObjectContext!.executeFetchRequest(fetchRequest, error: &error) {
for word in words {
list.appendString(((word as NSManagedObject).valueForKey("text") as String)+",")
}
}
return "Longest words: \(list)\n"
}

After adding a call to this method in statistics and running WordList, you can see that the longest word is the 28-letter gem "ethylenediaminetetraacetates."

Using Subqueries

In some cases, you want to fetch objects based on criteria that pertain to other related objects. Suppose, for example, you want to fetch all the word categories that have at least one 25-letter word. Without subqueries, you’d normally have to fetch these data in two steps.

1. Fetch all the word categories.

2. For each word category, fetch the count of words with 25 letters.

Subquery expressions allow you to combine those steps into a single query. Your entire predicate and logic can be expressed as

SUBQUERY(words, $x, $x.length == 25).@count > 0

The general format for subqueries is

SUBQUERY(collection_expression, variable_expression, predicate)

The collection_expression parameter refers to the collection property in the entity you’re fetching, so we use words to reference the words collection. The variable_expression can be called anything you’d like; it’s the variable that will temporarily hold each item in the collection as the subquery iterates through the collection. The predicate is, of course, the test for inclusion in the result set.

As with other expression types, you can create subqueries using the query language, as shown previously, or you can build them manually with NSExpression. The NSExpression class has a static initializer called+expressionForSubQuery:usingIteratorVariable:predicate: (or the Swift counterpart init(forSubquery expression: NSExpression!, usingIteratorVariable variable: String!, predicate predicate: AnyObject!)) for creating subquery expressions. The parameters to that method line up with the query language format: the first parameter is the collection expression to evaluate. The second is the name of the temporary variable to use during iteration. Finally, the last is the predicate to use for evaluation.

The wordCategoriesWith25LetterWords method shown in Listing 3-52 (Objective-C) and Listing 3-53 (Swift) uses the subquery described earlier to get all the categories that have at least one 25-letter word. Add a call to it in your statistics method to see that two categories, "i" and "p," have at least one 25-letter word.

Listing 3-52. Using a Subquery in Objective-C

- (NSString *)wordCategoriesWith25LetterWords {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"WordCategory"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SUBQUERY(words, $x, $x.length = 25).@count > 0"];
fetchRequest.predicate = predicate;
NSLog(@"Subquery Predicate: %@", [predicate predicateFormat]);
NSArray *categories = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
return [NSString stringWithFormat:@"Categories with 25-letter words: %@\n",
[[categories valueForKey:@"firstLetter"] componentsJoinedByString:@","]];
}

Listing 3-53. Using a Subquery in Swift

func wordCategoriesWith25LetterWords() -> String {
var fetchRequest = NSFetchRequest(entityName: "WordCategory")
let predicate = NSPredicate(format: "SUBQUERY(words, $x, $x.length = 25).@count > 0", argumentArray: [])

println("Subcategoy predicate: \(predicate.predicateFormat)")

fetchRequest.predicate = predicate

let list = NSMutableString()
var error: NSError? = nil
if let categories = self.managedObjectContext!.executeFetchRequest(fetchRequest, error: &error) {
for word in categories {
list.appendString(((word as NSManagedObject).valueForKey("firstLetter") as String)+",")
}
}

return "Categories with 25-letter words: \(list)\n"
}

Sorting

Until now, we’ve accepted the output order of any fetch request as Core Data chose to return it, but Core Data allows you to set a sort order for any fetch request. You can select multiple columns for the sort, as well as the sort direction for each column. To sort a fetch request, you set the fetch request’s sortDescriptors property to an array of NSSortDescriptor instances. Each NSSortDescriptor stores the column (or property) it sorts on, as well as the order to sort.

To sort all the words that start with "z" in ascending length order, for example, you’d create an NSSortDescriptor as follows:

[NSSortDescriptor sortDescriptorWithKey:@"length" ascending:YES] // Objective-C
NSSortDescriptor(key: "length", ascending: true) // Swift

That would sort the words in ascending length order but arbitrarily sort the words that have the same length. We can add another sort descriptor that sorts the words in descending alphabetical order, as follows:

[NSSortDescriptor sortDescriptorWithKey:@"text" ascending:NO] // Objective-C
NSSortDescriptor(key: "text", ascending: false) // Swift

The order in which you put your sort descriptors in the array that you use to set your fetch request’s sortDescriptors is significant. The results will be sorted according to the order of the sort descriptors. In our example, that means that words of the same length will be grouped together, sorted in descending alphabetical order.

Listing 3-54 (Objective-C) and Listing 3-55 (Swift) show the zWords method that sorts the words starting with "z" using those two sort descriptors.

Listing 3-54. Sorting the Results in Objective-C

- (NSString *)zWords {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Word"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"text BEGINSWITH 'z'"];
NSSortDescriptor *lengthSort = [NSSortDescriptor sortDescriptorWithKey:@"length" ascending:YES];
NSSortDescriptor *alphaSort = [NSSortDescriptor sortDescriptorWithKey:@"text" ascending:NO];

fetchRequest.predicate = predicate;
fetchRequest.sortDescriptors = @[lengthSort, alphaSort];

NSLog(@"Predicate: %@", predicate);
NSArray *words = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
return [NSString stringWithFormat:@"Z words:\n%@\n",
[[words valueForKey:@"text"] componentsJoinedByString:@"\n"]];
}

Listing 3-55. Sorting the Results in Swift

func zWords() -> String {
var fetchRequest = NSFetchRequest(entityName: "Word")
let predicate = NSPredicate(format: "text BEGINSWITH 'z'", argumentArray: [])

println("Predicate: \(predicate.predicateFormat)")

let lengthSort = NSSortDescriptor(key: "length", ascending: true)
let alphaSort = NSSortDescriptor(key: "text", ascending: false)

fetchRequest.predicate = predicate
fetchRequest.sortDescriptors = [lengthSort, alphaSort]

let list = NSMutableString()
var error: NSError? = nil
if let words = self.managedObjectContext!.executeFetchRequest(fetchRequest, error: &error) {
for word in words {
list.appendString(((word as NSManagedObject).valueForKey("text") as String)+",")
}
}

return "Z words: \(list)\n"
}

Remember to add a call to the zWords method in statistics to see the execution of the sort.

Summary

This chapter might or might not have helped you become a better Words with Friends player, but it should certainly have improved your skills at querying your Core Data stores. Whether using the query language syntax or building your predicates using NSExpression, you should be able to formulate the queries to extract your data to meet your applications’ requirements. Don’t forget to use tools like SQL Logging and logging the predicate format to tweak your queries and understand exactly what your application is doing when it fetches data.