月度归档:2014年02月

All About Core Data–NSManagedObjectContextDidSaveNotification

Core Data sits between your application and a persistent store, which is the generic term given to a data file such as an SQLite database, XML file (which can’t be used as a persistent store on iOS), or Binary (atomic) store. These files are called “persistent” because they can survive the underlying hardware being reset.

A persistent store coordinator can have multiple persistent stores

a managed object model is shown sitting between a persistent store coordinator and a managed object context.

The NSMainQueueConcurrencyType should be used when you want the context to operate on the main thread. Any heavy work performed in this queue may slow down or even freeze the user interface. You need at least one context working in the foreground to update user interface elements.
■ The NSPrivateQueueConcurrencyType should be used when you don’t want the context to operate on the main thread. This is an ideal concurrency type for potentially heavy work, such as saving or importing data.
■ The NSConfinementConcurrencyType is the default legacy option, which you should typically avoid unless you need backward compatibility with pre–iOS 5.0 devices.


The sqlite file is the database file, as per usual.
■ The sqlite-wal file is the Write-Ahead Log file containing uncommitted database transactions. If you delete this file, you will lose data. If this file does not exist, there are no pending transactions waiting to be committed.
■ The sqlite-shm file is the Shared Memory file containing an index of the WAL file. This file can be regenerated automatically so you don’t need to worry about it.


A deep copy involves copying managed objects and their relationships from one persistent store to another.

How would you watch a document’s managedObjectContext?!
 - (void)viewDidAppear:(BOOL)animated
 {
     [super viewDidAppear:animated];
     [center addObserver:self
                selector:@selector(contextChanged:)
                    name:NSManagedObjectContextDidSaveNotification
object:document.managedObjectContext]; //don’tpassnilhere!
 }
 - (void)viewWillDisappear:(BOOL)animated
 {
     [center removeObserver:self
                       name:NSManagedObjectContextDidSaveNotification
                     object:document.managedObjectContext];
     [super viewWillDisappear:animated];
 }
NSManagedObjectContextDidSaveNotification !
  - (void)contextChanged:(NSNotification *)notification
  {
// The notification.userInfo object is an NSDictionary with the following keys: NSInsertedObjectsKey // an array of objects which were inserted NSUpdatedObjectsKey // an array of objects whose attributes changed NSDeletedObjectsKey // an array of objects which were deleted
}!
Merging changes!
If you get notified that another NSManagedObjectContext has changed your database ...! ... you can just refetch (if you haven’t changed anything in your NSMOC, for example).!
... or you can use the NSManagedObjectContext method:!
- (void)mergeChangesFromContextDidSaveNotification:(NSNotification *)notification;

[_myAppDelegate.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];

Deletion

Deleting objects from the database is easy (sometimes too easy!)! [aDocument.managedObjectContext deleteObject:photo]; !
Make sure that the rest of your objects in the database are in a sensible state after this.! Relationships will be updated for you (if you set Delete Rule for relationship attributes properly).! And don’t keep any strong pointers to photo after you delete it!!
prepareForDeletion !
This is another method we sometimes put in a category of an NSManagedObject subclass ...!
  @implementation Photo (Deletion)
  - (void)prepareForDeletion
  {
// we don’t need to set our whoTook to nil or anything here (that will happen automatically)!
// but if Photographer had, for example, a “number of photos taken” attribute,!
// we might adjust it down by one here (e.g. self.whoTook.photoCount--). }
@end

Querying

Creating an NSFetchRequest!
We’ll consider each of these lines of code one by one ...
 NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@“Photo”];
 request.fetchBatchSize = 20;
 request.fetchLimit = 100;
 request.sortDescriptors = @[sortDescriptor];
 request.predicate = ...;
Specifying the kind of Entity we want to fetch! A given fetch returns objects all of the same Entity.!
You can’t have a fetch that returns some Photos and some Photographers (it’s one or the other).! Setting fetch sizes/limits!
If you created a fetch that would match 1000 objects, the request above faults 20 at a time. And it would stop fetching after it had fetched 100 of the 1000

NSPredicate !

This is the guts of how we specify exactly which objects we want from the database.!
Predicate formats!
Creating one looks a lot like creating an NSString, but the contents have semantic meaning.
  NSString *serverName = @“flickr-5”;
  NSPredicate *predicate =
      [NSPredicate predicateWithFormat:@“thumbnailURL contains %@”, serverName];
Examples!
@“uniqueId = %@”, [flickrInfo objectForKey:@“id”] // unique a photo in the database @“name contains[c] %@”, (NSString *) // matches name case insensitively!
@“viewed > %@”, (NSDate *) // viewed is a Date attribute in the data mapping! @“whoTook.name = %@”, (NSString *) // Photo search (by photographer’s name)!
@“any photos.title contains %@”, (NSString *) // Photographer search (not a Photo search)! Many more options. Look at the class documentation for NSPredicate.

NSCompoundPredicate

NSCompoundPredicate !
You can use AND and OR inside a predicate string, e.g. @“(name = %@) OR (title = %@)”! Or you can combine NSPredicate objects with special NSCompoundPredicates.
NSArray *array = @[predicate1, predicate2];
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:array]; This predicate is “predicate1 AND predicate2”. Or available too, of course

Advanding Querying

Key Value Coding!
Can actually do predicates like @“photos.@count > 5” (Photographers with more than 5 photos).! @count is a function (there are others) executed in the database itself.! https://developer.apple.com/library/ios/documentation/cocoa/conceptual/KeyValueCoding/Articles/CollectionOperators.html.!
By the way, all this stuff (and more) works on dictionaries, arrays and sets too ...!
e.g. [propertyListResults valueForKeyPath:@“photos.photo.@avg.latitude”] on Flickr results!
returns the average latitude of all of the photos in the results (yes, really)!
e.g. @“photos.photo.title.length" would return an array of the lengths of the titles of the photos!

Example!

Let’s say we want to query for all Photographers ...!
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@“Photographer”];
... who have taken a photo in the last 24 hours ...!
NSDate *yesterday = [NSDate dateWithTimeIntervalSinceNow:-24*60*60]; !
request.predicate = [NSPredicate predicateWithFormat:@“any photos.uploadDate > %@”, yesterday]; ! ... sorted by the Photographer’s name ...
request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@“name” ascending:YES]];

How do you create an NSFetchedResultsController?

Just need the NSFetchRequest to drive it (and a NSManagedObjectContext to fetch from).
Let's say we want to show all photos taken by someone with the name photogName in our table:
 NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@“Photo”];
 request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@“title” ...]];
 request.predicate = [NSPredicate predicateWithFormat:@“whoTook.name = %@”, photogName];
 NSFetchedResultsController *frc = [[NSFetchedResultsController alloc]
     initWithFetchRequest:(NSFetchRequest *)request
     managedObjectContext:(NSManagedObjectContext *)context
       sectionNameKeyPath:(NSString *)keyThatSaysWhichSectionEachManagedObjectIsIn
                cacheName:@“MyPhotoCache”];  // careful!
Be sure that any cacheName you use is always associated with exactly the same request. It’s okay to specify nil for the cacheName (no cacheing of fetch results in that case).
It is critical that the sortDescriptor matches up with the keyThatSaysWhichSection...
The results must sort such that all objects in the first section come first, second second, etc

NSFetchedResultController–CoreDataTableViewController

//
//  CoreDataTableViewController.h
//
//  Created for Stanford CS193p Fall 2013.
//  Copyright 2013 Stanford University. All rights reserved.
//
// This class mostly just copies the code from NSFetchedResultsController's documentation page
//   into a subclass of UITableViewController.
//
// Just subclass this and set the fetchedResultsController.
// The only UITableViewDataSource method you'll HAVE to implement is tableView:cellForRowAtIndexPath:.
// And you can use the NSFetchedResultsController method objectAtIndexPath: to do it.
//
// Remember that once you create an NSFetchedResultsController, you CANNOT modify its @propertys.
// If you want new fetch parameters (predicate, sorting, etc.),
//  create a NEW NSFetchedResultsController and set this class's fetchedResultsController @property again.
//

#import 
#import 

@interface CoreDataTableViewController : UITableViewController 

// The controller (this class fetches nothing if this is not set).
@property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;

// Causes the fetchedResultsController to refetch the data.
// You almost certainly never need to call this.
// The NSFetchedResultsController class observes the context
//  (so if the objects in the context change, you do not need to call performFetch
//   since the NSFetchedResultsController will notice and update the table automatically).
// This will also automatically be called if you change the fetchedResultsController @property.
- (void)performFetch;

// Set to YES to get some debugging output in the console.
@property BOOL debug;

@end

//
//  CoreDataTableViewController.m
//
//  Created for Stanford CS193p Fall 2013.
//  Copyright 2013 Stanford University. All rights reserved.
//

#import "CoreDataTableViewController.h"

@implementation CoreDataTableViewController

#pragma mark - Fetching

- (void)performFetch
{
    if (self.fetchedResultsController) {
        if (self.fetchedResultsController.fetchRequest.predicate) {
            if (self.debug) NSLog(@"[%@ %@] fetching %@ with predicate: %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), self.fetchedResultsController.fetchRequest.entityName, self.fetchedResultsController.fetchRequest.predicate);
        } else {
            if (self.debug) NSLog(@"[%@ %@] fetching all %@ (i.e., no predicate)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), self.fetchedResultsController.fetchRequest.entityName);
        }
        NSError *error;
        BOOL success = [self.fetchedResultsController performFetch:&error];
        if (!success) NSLog(@"[%@ %@] performFetch: failed", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
        if (error) NSLog(@"[%@ %@] %@ (%@)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), [error localizedDescription], [error localizedFailureReason]);
    } else {
        if (self.debug) NSLog(@"[%@ %@] no NSFetchedResultsController (yet?)", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
    }
    [self.tableView reloadData];
}

- (void)setFetchedResultsController:(NSFetchedResultsController *)newfrc
{
    NSFetchedResultsController *oldfrc = _fetchedResultsController;
    if (newfrc != oldfrc) {
        _fetchedResultsController = newfrc;
        newfrc.delegate = self;
        if ((!self.title || [self.title isEqualToString:oldfrc.fetchRequest.entity.name]) && (!self.navigationController || !self.navigationItem.title)) {
            self.title = newfrc.fetchRequest.entity.name;
        }
        if (newfrc) {
            if (self.debug) NSLog(@"[%@ %@] %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), oldfrc ? @"updated" : @"set");
            [self performFetch];
        } else {
            if (self.debug) NSLog(@"[%@ %@] reset to nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
            [self.tableView reloadData];
        }
    }
}

#pragma mark - UITableViewDataSource

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    NSInteger sections = [[self.fetchedResultsController sections] count];
    return sections;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    NSInteger rows = 0;
    if ([[self.fetchedResultsController sections] count] > 0) {
        id  sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
        rows = [sectionInfo numberOfObjects];
    }
    return rows;
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
	return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
}

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
	return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
}

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
    return [self.fetchedResultsController sectionIndexTitles];
}

#pragma mark - NSFetchedResultsControllerDelegate

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
    [self.tableView beginUpdates];
}

- (void)controller:(NSFetchedResultsController *)controller
  didChangeSection:(id )sectionInfo
		   atIndex:(NSUInteger)sectionIndex
	 forChangeType:(NSFetchedResultsChangeType)type
{
    switch(type)
    {
        case NSFetchedResultsChangeInsert:
            [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;
            
        case NSFetchedResultsChangeDelete:
            [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}


- (void)controller:(NSFetchedResultsController *)controller
   didChangeObject:(id)anObject
	   atIndexPath:(NSIndexPath *)indexPath
	 forChangeType:(NSFetchedResultsChangeType)type
	  newIndexPath:(NSIndexPath *)newIndexPath
{		
    switch(type)
    {
        case NSFetchedResultsChangeInsert:
            [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
            
        case NSFetchedResultsChangeDelete:
            [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
            
        case NSFetchedResultsChangeUpdate:
            [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
            
        case NSFetchedResultsChangeMove:
            [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    [self.tableView endUpdates];
}

@end


iCloud

icloud

  • The Application Stores Directory, also known as the application sandbox, is a
    local directory that currently holds the original Grocery-Dude.sqlite store. An iCloud.sqlite store will be added to this folder in a subfolder specific to each iCloud user. The per-user subfolders are managed transparently by Core Data. The folder structure is shown at the end of the chapter in the “Exercises” section.
  • The Ubiquity Container is where iCloud documents and data specific to the authenticated iCloud user are found. Everything in this folder is synchronized automatically with the iCloud servers. If a directory with a .nosync suffix is stored
    in the ubiquity container, its contents will not be synchronized. You may have seen implementations of iCloud where the iCloud Store was placed in a .nosync folder of the ubiquity container. This approach is no longer recommended since iOS 7 because it prevents Core Data from transparently managing a Fallback Store. A Fallback Store is used to provide seamless transitions between iCloud accounts and reduce the time it takes for the iCloud Store to become usable for the first time. The Fallback Store is also used when the user is logged out of iCloud, or has disabled iCloud Documents & Data.
  • iCloud is the name of the service allowing a user’s data to be synchronized across all
    of his or her devices. It is possible to see the contents of your iCloud container for debugging purposes at https://developer.icloud.com. You can also perform metadata queries to inspect the contents of iCloud or use the iCloud Debug Navigator introduced in Xcode 5, which will be shown later in the chapter. Each application using iCloud has its own directory at the root of iCloud, and applications from the same developer can be configured to share a ubiquity container within it. This is useful if you need to maintain separate free and paid versions of an application that need to access the same data.

Create file to iCloud
Icloud’s local path from phone: file:///private/var/mobile/Library/Mobile%20Documents/TeamID~com~toogoogoo~iCloud/

iCloud’s local path from simulator: file:///Users/xushao/Library/Application%20Support/iPhone%20Simulator/7.0.3/Library/Mobile%20Documents/TeamID ~com~toogoogoo~Test/

如果在该目录手动创建文件,icloud也会同步创建

NSURL * iCloudURL = [self.fManager URLForUbiquityContainerIdentifier:nil];这个url的目录为:根目录

//
//  iCloudAppDelegate.m
//  iCloud
//
//  Created by xushao on 2/24/14.
//  Copyright (c) 2014 TGG. All rights reserved.
//

#import "iCloudAppDelegate.h"

@interface iCloudAppDelegate ()
@property(nonatomic, strong)NSFileManager *fManager;
@property(nonatomic, strong)NSMetadataQuery *iCloudQuery;

@end

@implementation iCloudAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    /*
    NSString *stringValuekey2 = @"My String";
    NSString *stringValueKey = @"MyStringKey";
    NSUbiquitousKeyValueStore *KVStore =  [[NSUbiquitousKeyValueStore alloc] init];
    
    if ([[KVStore stringForKey:stringValuekey2] length]==0) {
        [self alert:@"no string, settting"];
        [KVStore setString:@"this is string" forKey:stringValueKey];
        [KVStore setString:@"stringValuekey2" forKey:stringValuekey2];
    }else{
        [self alert:[NSString stringWithFormat:@"Result is :%@ and %@",[KVStore stringForKey:stringValueKey],[KVStore stringForKey:stringValuekey2]]];
    }
    
    
    
    
    if (![fManager fileExistsAtPath:[[self documentURL] path]]) {
        
        [self alert:[NSString stringWithFormat:@"createDirectoryAtPath %@ ", [self documentURL]]];
        [fManager createDirectoryAtPath:[[self documentURL] path] withIntermediateDirectories:YES attributes:nil error:nil];
    }else{
        [self alert:@"File already exit"];
    }
     

    if (![self.fManager fileExistsAtPath:[[self documentURL] path]]) {
        
        [self alert:[NSString stringWithFormat:@"createDirectoryAtURL %@ ", [self documentURL]]];
        
        [self.fManager createDirectoryAtURL:[self documentURL] withIntermediateDirectories:YES
                                 attributes:nil error:nil  ];
        //[self.fManager createDirectoryAtPath:[[self documentURL] path] withIntermediateDirectories:YES attributes:nil error:nil];
    }else{
        [self alert:@"File already exit"];
    }
     */
    self.fManager = [NSFileManager defaultManager];
   NSString * TeamID = @"FUHS6YZAHW";
    NSString *buddleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
    NSString *identifier = [NSString stringWithFormat:@"%@.%@", TeamID, buddleIdentifier];
    
    
   NSURL * iCloudURL = [self.fManager URLForUbiquityContainerIdentifier:nil];
    NSError *error = nil;
   // NSString *documentDirec = [[iCloudURL path] stringByAppendingPathComponent:@"Documents"];
    NSURL *documentURL = [iCloudURL URLByAppendingPathComponent:@"Documents" isDirectory:YES];
    //NSString *createDocument = [documentDirec stringByAppendingPathComponent:@"David"];
    if (![self.fManager fileExistsAtPath:[documentURL path]]) {
        
        
        [self.fManager createDirectoryAtPath:[documentURL path] withIntermediateDirectories:YES attributes:nil error:&error];
        if (!error) {
            [self alert:[NSString stringWithFormat:@"create iCloudURL: %@ ", iCloudURL]];
            NSLog(@"%@",iCloudURL);
        }else{
            NSLog(@"error1");
        }
        
    }else{
        
        NSString *textString = @"This is text";
        NSURL *FileURL = [documentURL URLByAppendingPathComponent:@"latest.text"];
       // NSString *path = [documentURL stringByAppendingPathComponent:@"JacobString.sql"];
        NSError *error = nil;
        if ([textString writeToURL:FileURL atomically:YES encoding:NSUTF8StringEncoding error:&error]) {
            if (!error) {
                //NSData *data = [self.fManager contentsAtPath:path];
                //NSString *string = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
                //[self alert:[NSString stringWithFormat:@"The content is : %@", string]];
            }else{
                 NSLog(@"error2");
            }
        }else{
            [self alert:@"Couldn't write to"];
        }
    }
    
    
    //
    NSURL *fileURL = [[self documentURL] URLByAppendingPathComponent:@"JacobLocal.String.txt"];
    if ([self.fManager fileExistsAtPath:[fileURL path]]) {
        if ([self.fManager setUbiquitous:YES itemAtURL:fileURL destinationURL:documentURL error:&error]) {
            if (!error) {
                NSLog(@"successful upload");
            }else{
                NSLog(@"error3");
            }
        }else{
            NSLog(@"couldn't");
        }
        
    }
    
    if ([self.iCloudQuery startQuery]) {
        [self alert:@"start query"];
    }else{
        NSLog(@"not query");
    }
    
    
    return YES;
    
}

-(NSMetadataQuery *)iCloudQuery
{
    if (!_iCloudQuery) {
        _iCloudQuery = [[NSMetadataQuery alloc] init];
        _iCloudQuery.searchScopes = [NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope];
        _iCloudQuery.predicate = [NSPredicate predicateWithFormat:@"%K LIKE '*'",NSMetadataItemFSNameKey];
        
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(preocessQueryResult:) name:NSMetadataQueryDidFinishGatheringNotification object:_iCloudQuery];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(preocessQueryResult:) name:NSMetadataQueryDidUpdateNotification object:_iCloudQuery];
    
    }
    
    return _iCloudQuery;
}

-(void)preocessQueryResult:(NSNotification *)notification{
    [self.iCloudQuery disableUpdates];
    NSMutableArray *UrlArray = [[NSMutableArray alloc] init];
    NSUInteger resultIndex = [self.iCloudQuery resultCount];
    NSLog(@"resultIndex is %d",resultIndex);

    for (int i = 0; i < resultIndex; i++) {
        NSMetadataItem *item = [self.iCloudQuery resultAtIndex:i];
        NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];

        [UrlArray addObject:[url pathComponents]];
    }
    
    [self.iCloudQuery enableUpdates];
}

-(NSURL *)documentURL{
    NSURL *documentDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
    return [documentDirectory URLByAppendingPathComponent:@"TGGDocument"];
}

-(void)alert:(NSString *)msg{
    
    [[[UIAlertView alloc] initWithTitle:@"Noticed" message:msg delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil] show];
}
	

DocumentTVC

//
// DocumentTVC.m
// Test
//
// Created by xushao on 2/23/14.
// Copyright (c) 2014 TGG. All rights reserved.
//

#import "DocumentTVC.h"

@interface DocumentTVC ()
@property(nonatomic, strong)NSArray *documents;
@property(nonatomic, strong)NSMetadataQuery *icloudQuery;
@end

@implementation DocumentTVC

-(void)viewDidLoad
{
if (DEBUG == 1) {
NSLog(@"%@ is %@",self.class, NSStringFromSelector(_cmd));
}
[super viewDidLoad];
NSLog(@"%@", [self iCloudURL]);
}
-(NSURL *)iCloudURL{
return [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
}

@synthesize documents = _documents;

-(void)setDocuments:(NSArray *)documents
{
_documents = documents;
[self.tableView reloadData];
}

-(void)processIcloudqueryResult:(NSNotification *)notificatin{
[self.icloudQuery disableUpdates];

NSUInteger resultCount = [self.icloudQuery resultCount];

for (int i = 0; i < resultCount; i ++) { NSMetadataItem *item = [self.icloudQuery resultAtIndex:i]; NSURL *url = [item valueForAttribute:NSMetadataItemURLKey]; } } /* [item valueForAttribute:NSMetadataItemURLKey]; NSString * const NSMetadataItemFSNameKey; NSString * const NSMetadataItemDisplayNameKey; NSString * const NSMetadataItemURLKey; NSString * const NSMetadataItemPathKey; NSString * const NSMetadataItemFSSizeKey; NSString * const NSMetadataItemFSCreationDateKey; NSString * const NSMetadataItemFSContentChangeDateKey; */ -(NSMetadataQuery *)icloudQuery { if (!_icloudQuery) { _icloudQuery = [[NSMetadataQuery alloc] init]; _icloudQuery.searchScopes = [NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]; //NSString * const NSMetadataQueryUbiquitousDocumentsScope; // NSString * const NSMetadataQueryUbiquitousDataScope; _icloudQuery.predicate = [NSPredicate predicateWithFormat:@"%K like '*'",NSMetadataItemFSNameKey ]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processIcloudqueryResult:) name:NSMetadataQueryDidFinishGatheringNotification object:_icloudQuery]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processIcloudqueryResult:) name:NSMetadataQueryDidUpdateNotification object:_icloudQuery]; } return _icloudQuery; } -(void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; if (![self.icloudQuery isStarted]) { [self.icloudQuery enableUpdates]; } } -(void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [self.icloudQuery disableUpdates]; } #pragma mark - Table view data source - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { // Return the number of rows in the section. return [self.documents count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Document Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; // Configure the cell... cell.textLabel.text = [[self.documents objectAtIndex:indexPath.row] lastPathComponent]; return cell; } /* // Override to support conditional editing of the table view. - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { // Return NO if you do not want the specified item to be editable. return YES; } */ /* // Override to support editing the table view. - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { // Delete the row from the data source [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade]; } else if (editingStyle == UITableViewCellEditingStyleInsert) { // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view } } */ /* // Override to support rearranging the table view. - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath { } */ /* // Override to support conditional rearranging of the table view. - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath { // Return NO if you do not want the item to be re-orderable. return YES; } */ /* #pragma mark - Navigation // In a story board-based application, you will often want to do a little preparation before navigation - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // Get the new view controller using [segue destinationViewController]. // Pass the selected object to the new view controller. } */ @end

IOS7-CoreDAta:DeepCopy

deepCopy


The existing _importContext will be reused as the target context for the deep copy demon- stration. Copying an object to another context isn’t as straightforward as a copy-and-paste command. To copy an object, you actually need to create a new object in the target context and then copy all the attribute values from a source object to the new object. That leaves the relationships, which can’t be copied in the same way. If you were to copy a relationship the same way you copied an attribute value, you would end up with an illegal cross-store relation- ship between the copied object and object(s) in the source store. Instead of copying a relation- ship, a deep copy needs to identify related copied objects in the target context and then establish a relationship to them from the copied object. Figure 9.2 illustrates a To-One relationship copy.

enumerateObjectsUsingBlock详解

NSArray *anArray=[NSArray arrayWithObjects:@"This", @"is", @"a", @"test", nil]; 
NSString *string=@"test";
[anArray enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop)
{
  if([obj localizedCaseInsensitiveCompare:string] == NSOrderedSame)
  {
    // 对返回的obj做点别的事情
  *stop=YES;

  }

要是anArray对象中有个单词是@”test”,那么就把指针*stop设置为YES,以通知anArray对象提前停止枚举。块除了id obj和BOOL *stop参数,还有一个NSUInteger index参数。index参数让块中的算法知道当前元素的位置,这对这样的并发枚举非常有用。要是没有这个参数,访问索引的唯一方式就是使用indexOfObject:方法,这样影响效率

IOS7-CoreDAta案例:PrepareTVC与shopTVC,ItemVC,Unite,UnitePicker,CoreDataTVC

The NSMainQueueConcurrencyType should be used when you want the context to operate on the main thread. Any heavy work performed in this queue may slow down or even freeze the user interface. You need at least one context working in the foreground to update user interface elements.
■ The NSPrivateQueueConcurrencyType should be used when you don’t want the context to operate on the main thread. This is an ideal concurrency type for potentially heavy work, such as saving or importing data.

The sqlite file is the database file, as per usual.
■ The sqlite-wal file is the Write-Ahead Log file containing uncommitted database transactions. If you delete this file, you will lose data. If this file does not exist, there are no pending transactions waiting to be committed.
■ The sqlite-shm file is the Shared Memory file containing an index of the WAL file. This file can be regenerated automatically so you don’t need to worry about it.

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

@interface CoreDataHelper : NSObject <UIAlertViewDelegate,NSXMLParserDelegate>

@property (nonatomic, readonly) NSManagedObjectContext       *parentContext;
@property (nonatomic, readonly) NSManagedObjectContext       *context;
@property (nonatomic, readonly) NSManagedObjectContext       *importContext;

@property (nonatomic, readonly) NSManagedObjectModel         *model;
@property (nonatomic, readonly) NSPersistentStoreCoordinator *coordinator;
@property (nonatomic, readonly) NSPersistentStore            *store;

@property (nonatomic, readonly) NSManagedObjectContext       *sourceContext;
@property (nonatomic, readonly) NSPersistentStoreCoordinator *sourceCoordinator;
@property (nonatomic, readonly) NSPersistentStore            *sourceStore;

@property (nonatomic, readonly) NSPersistentStore            *iCloudStore;

@property (nonatomic, retain) MigrationVC *migrationVC;

@property (nonatomic, retain) UIAlertView *importAlertView;

@property (nonatomic, strong) NSXMLParser *parser;

@property (nonatomic, strong) NSTimer *importTimer;

- (void)setupCoreData;
- (void)saveContext;
- (void)backgroundSaveContext;
- (BOOL)reloadStore;
- (NSURL *)applicationStoresDirectory;

- (BOOL)iCloudAccountIsSignedIn;
- (void)ensureAppropriateStoreIsLoaded;
@end

CoreDataHelper.m

#import "CoreDataHelper.h"
#import "CoreDataImporter.h"
#import "Faulter.h"

@implementation CoreDataHelper
#define debug 1

#pragma mark - FILES
NSString *storeFilename = @"Grocery-Dude.sqlite";
NSString *sourceStoreFilename = @"DefaultData.sqlite";
NSString *iCloudStoreFilename = @"iCloud.sqlite";

#pragma mark - PATHS
- (NSString *)applicationDocumentsDirectory {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class,NSStringFromSelector(_cmd));
    }
    return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES) lastObject];
}
- (NSURL *)applicationStoresDirectory {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }

    NSURL *storesDirectory =
    [[NSURL fileURLWithPath:[self applicationDocumentsDirectory]]
     URLByAppendingPathComponent:@"Stores"];

    NSFileManager *fileManager = [NSFileManager defaultManager];
    if (![fileManager fileExistsAtPath:[storesDirectory path]]) {
        NSError *error = nil;
        if ([fileManager createDirectoryAtURL:storesDirectory
                  withIntermediateDirectories:YES
                                   attributes:nil
                                        error:&error]) {
            if (debug==1) {
                NSLog(@"Successfully created Stores directory");}
        }
        else {NSLog(@"FAILED to create Stores directory: %@", error);}
    }
    return storesDirectory;
}
- (NSURL *)storeURL {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return [[self applicationStoresDirectory]
            URLByAppendingPathComponent:storeFilename];
}
- (NSURL *)sourceStoreURL {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }

    return [NSURL fileURLWithPath:[[NSBundle mainBundle]
                                   pathForResource:[sourceStoreFilename stringByDeletingPathExtension]
                                   ofType:[sourceStoreFilename pathExtension]]];
}
- (NSURL *)iCloudStoreURL {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return [[self applicationStoresDirectory]
            URLByAppendingPathComponent:iCloudStoreFilename];
}

#pragma mark - SETUP
- (id)init {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    self = [super init];
    if (!self) {return nil;}

    _model = [NSManagedObjectModel mergedModelFromBundles:nil];
    _coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:_model];

    _parentContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [_parentContext performBlockAndWait:^{
        [_parentContext setPersistentStoreCoordinator:_coordinator];
        [_parentContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
    }];

    _context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    [_context setParentContext:_parentContext];
    [_context setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];

    _importContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [_importContext performBlockAndWait:^{
        [_importContext setParentContext:_context];
        [_importContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
        [_importContext setUndoManager:nil]; // the default on iOS
    }];

    //_sourceCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:_model];
    _sourceContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [_sourceContext performBlockAndWait:^{
        [_sourceContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
        [_sourceContext setParentContext:_context];
        [_sourceContext setUndoManager:nil]; // the default on iOS
    }];

    [self listenForStoreChanges];

    return self;
}
- (void)loadStore {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (_store) {return;} // Don’t load store if it’s already loaded

    BOOL useMigrationManager = NO;
    if (useMigrationManager &&
        [self isMigrationNecessaryForStore:[self storeURL]]) {
        [self performBackgroundManagedMigrationForStore:[self storeURL]];
    } else {
        NSDictionary *options =
        @{
          NSMigratePersistentStoresAutomaticallyOption:@YES
          ,NSInferMappingModelAutomaticallyOption:@YES
          ,NSSQLitePragmasOption: @{@"journal_mode": @"DELETE"} // Uncomment to disable WAL journal mode
          };
        NSError *error = nil;
        _store = [_coordinator addPersistentStoreWithType:NSSQLiteStoreType
                                            configuration:nil
                                                      URL:[self storeURL]
                                                  options:options
                                                    error:&error];
        if (!_store) {
            NSLog(@"Failed to add store. Error: %@", error);abort();
        }
        else         {NSLog(@"Successfully added store: %@", _store);}
    }

}
- (void)loadSourceStore {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (_sourceStore) {return;} // Don’t load source store if it's already loaded

    NSDictionary *options =
    @{
      NSReadOnlyPersistentStoreOption:@YES
      };
    NSError *error = nil;
    _sourceStore =
    [_sourceCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                     configuration:nil
                                               URL:[self sourceStoreURL]
                                           options:options
                                             error:&error];
    if (!_sourceStore) {
        NSLog(@"Failed to add source store. Error: %@",
              error);abort();
    } else {
        NSLog(@"Successfully added source store: %@", _sourceStore);
    }
}
- (void)setupCoreData {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (!_store && !_iCloudStore) {
        if ([self iCloudEnabledByUser]) {
            NSLog(@"** Attempting to load the iCloud Store **");
            if ([self loadiCloudStore]) {
                return;
            }
        }
        NSLog(@"** Attempting to load the Local, Non-iCloud Store **");
        [self setDefaultDataStoreAsInitialStore];
        [self loadStore];
    } else {
        NSLog(@"SKIPPED setupCoreData, there's an existing Store:\n ** _store(%@)\n ** _iCloudStore(%@)", _store, _iCloudStore);
    }
}

#pragma mark - SAVING
- (void)saveContext {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if ([_context hasChanges]) {
        NSError *error = nil;
        if ([_context save:&error]) {
            NSLog(@"_context SAVED changes to persistent store");
        } else {
            NSLog(@"Failed to save _context: %@", error);
            [self showValidationError:error];
        }
    } else {
        NSLog(@"SKIPPED _context save, there are no changes!");
    }
}
- (void)backgroundSaveContext {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    // First, save the child context in the foreground (fast, all in memory)
    [self saveContext];

    // Then, save the parent context.
    [_parentContext performBlock:^{
        if ([_parentContext hasChanges]) {
            NSError *error = nil;
            if ([_parentContext save:&error]) {
                NSLog(@"_parentContext SAVED changes to persistent store");
            }
            else {
                NSLog(@"_parentContext FAILED to save: %@", error);
                [self showValidationError:error];
            }
        }
        else {
            NSLog(@"_parentContext SKIPPED saving as there are no changes");
        }
    }];
}

#pragma mark - MIGRATION MANAGER
- (BOOL)isMigrationNecessaryForStore:(NSURL*)storeUrl {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (![[NSFileManager defaultManager] fileExistsAtPath:[self storeURL].path]) {
        if (debug==1) {NSLog(@"SKIPPED MIGRATION: Source database missing.");}
        return NO;
    }
    NSError *error = nil;
    NSDictionary *sourceMetadata =
    [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType
                                                               URL:storeUrl error:&error];
    NSManagedObjectModel *destinationModel = _coordinator.managedObjectModel;
    if ([destinationModel isConfiguration:nil
              compatibleWithStoreMetadata:sourceMetadata]) {
        if (debug==1) {
            NSLog(@"SKIPPED MIGRATION: Source is already compatible");}
        return NO;
    }
    return YES;
}
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {

    if ([keyPath isEqualToString:@"migrationProgress"]) {

        dispatch_async(dispatch_get_main_queue(), ^{

            float progress =
            [[change objectForKey:NSKeyValueChangeNewKey] floatValue];
            self.migrationVC.progressView.progress = progress;
            int percentage = progress * 100;
            NSString *string =
            [NSString stringWithFormat:@"Migration Progress: %i%%",
             percentage];
            NSLog(@"%@",string);
            self.migrationVC.label.text = string;
        });
    }
}
- (BOOL)replaceStore:(NSURL*)old withStore:(NSURL*)new {

    BOOL success = NO;
    NSError *Error = nil;
    if ([[NSFileManager defaultManager]
         removeItemAtURL:old error:&Error]) {

        Error = nil;
        if ([[NSFileManager defaultManager]
             moveItemAtURL:new toURL:old error:&Error]) {
            success = YES;
        }
        else {
            if (debug==1) {NSLog(@"FAILED to re-home new store %@", Error);}
        }
    }
    else {
        if (debug==1) {
            NSLog(@"FAILED to remove old store %@: Error:%@", old, Error);
        }
    }
    return success;
}
- (BOOL)migrateStore:(NSURL*)sourceStore {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    BOOL success = NO;
    NSError *error = nil;

    // STEP 1 - Gather the Source, Destination and Mapping Model
    NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator
                                    metadataForPersistentStoreOfType:NSSQLiteStoreType
                                    URL:sourceStore
                                    error:&error];

    NSManagedObjectModel *sourceModel =
    [NSManagedObjectModel mergedModelFromBundles:nil
                                forStoreMetadata:sourceMetadata];

    NSManagedObjectModel *destinModel = _model;

    NSMappingModel *mappingModel =
    [NSMappingModel mappingModelFromBundles:nil
                             forSourceModel:sourceModel
                           destinationModel:destinModel];

    // STEP 2 - Perform migration, assuming the mapping model isn't null
    if (mappingModel) {
        NSError *error = nil;
        NSMigrationManager *migrationManager =
        [[NSMigrationManager alloc] initWithSourceModel:sourceModel
                                       destinationModel:destinModel];
        [migrationManager addObserver:self
                           forKeyPath:@"migrationProgress"
                              options:NSKeyValueObservingOptionNew
                              context:NULL];

        NSURL *destinStore =
        [[self applicationStoresDirectory]
         URLByAppendingPathComponent:@"Temp.sqlite"];

        success =
        [migrationManager migrateStoreFromURL:sourceStore
                                         type:NSSQLiteStoreType options:nil
                             withMappingModel:mappingModel
                             toDestinationURL:destinStore
                              destinationType:NSSQLiteStoreType
                           destinationOptions:nil
                                        error:&error];
        if (success) {
            // STEP 3 - Replace the old store with the new migrated store
            if ([self replaceStore:sourceStore withStore:destinStore]) {
                if (debug==1) {
                    NSLog(@"SUCCESSFULLY MIGRATED %@ to the Current Model",
                          sourceStore.path);}
                [migrationManager removeObserver:self
                                      forKeyPath:@"migrationProgress"];
            }
        }
        else {
            if (debug==1) {NSLog(@"FAILED MIGRATION: %@",error);}
        }
    }
    else {
        if (debug==1) {NSLog(@"FAILED MIGRATION: Mapping Model is null");}
    }
    return YES; // indicates migration has finished, regardless of outcome
}
- (void)performBackgroundManagedMigrationForStore:(NSURL*)storeURL {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }

    // Show migration progress view preventing the user from using the app
    UIStoryboard *sb = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    self.migrationVC =
    [sb instantiateViewControllerWithIdentifier:@"migration"];
    UIApplication *sa = [UIApplication sharedApplication];
    UINavigationController *nc =
    (UINavigationController*)sa.keyWindow.rootViewController;
    [nc presentViewController:self.migrationVC animated:NO completion:nil];

    // Perform migration in the background, so it doesn't freeze the UI.
    // This way progress can be shown to the user
    dispatch_async(
                   dispatch_get_global_queue(
                                             DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
                       BOOL done = [self migrateStore:storeURL];
                       if(done) {
                           // When migration finishes, add the newly migrated store
                           dispatch_async(dispatch_get_main_queue(), ^{
                               NSError *error = nil;
                               _store =
                               [_coordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                          configuration:nil
                                                                    URL:[self storeURL]
                                                                options:nil
                                                                  error:&error];
                               if (!_store) {
                                   NSLog(@"Failed to add a migrated store. Error: %@",
                                         error);abort();}
                               else {
                                   NSLog(@"Successfully added a migrated store: %@",
                                         _store);}
                               [self.migrationVC dismissViewControllerAnimated:NO
                                                                    completion:nil];
                               self.migrationVC = nil;
                           });
                       }
                   });
}

#pragma mark - VALIDATION ERROR HANDLING
- (void)showValidationError:(NSError *)anError {

    if (anError && [anError.domain isEqualToString:@"NSCocoaErrorDomain"]) {
        NSArray *errors = nil;  // holds all errors
        NSString *txt = @""; // the error message text of the alert

        // Populate array with error(s)
        if (anError.code == NSValidationMultipleErrorsError) {
            errors = [anError.userInfo objectForKey:NSDetailedErrorsKey];
        } else {
            errors = [NSArray arrayWithObject:anError];
        }
        // Display the error(s)
        if (errors && errors.count > 0) {
            // Build error message text based on errors
            for (NSError * error in errors) {
                NSString *entity =
                [[[error.userInfo objectForKey:@"NSValidationErrorObject"]entity]name];

                NSString *property =
                [error.userInfo objectForKey:@"NSValidationErrorKey"];

                switch (error.code) {
                    case NSValidationRelationshipDeniedDeleteError:
                        txt = [txt stringByAppendingFormat:
                               @"%@ delete was denied because there are associated %@\n(Error Code %li)\n\n", entity, property, (long)error.code];
                        break;
                    case NSValidationRelationshipLacksMinimumCountError:
                        txt = [txt stringByAppendingFormat:
                               @"the '%@' relationship count is too small (Code %li).", property, (long)error.code];
                        break;
                    case NSValidationRelationshipExceedsMaximumCountError:
                        txt = [txt stringByAppendingFormat:
                               @"the '%@' relationship count is too large (Code %li).", property, (long)error.code];
                        break;
                    case NSValidationMissingMandatoryPropertyError:
                        txt = [txt stringByAppendingFormat:
                               @"the '%@' property is missing (Code %li).", property, (long)error.code];
                        break;
                    case NSValidationNumberTooSmallError:
                        txt = [txt stringByAppendingFormat:
                               @"the '%@' number is too small (Code %li).", property, (long)error.code];
                        break;
                    case NSValidationNumberTooLargeError:
                        txt = [txt stringByAppendingFormat:
                               @"the '%@' number is too large (Code %li).", property, (long)error.code];
                        break;
                    case NSValidationDateTooSoonError:
                        txt = [txt stringByAppendingFormat:
                               @"the '%@' date is too soon (Code %li).", property, (long)error.code];
                        break;
                    case NSValidationDateTooLateError:
                        txt = [txt stringByAppendingFormat:
                               @"the '%@' date is too late (Code %li).", property, (long)error.code];
                        break;
                    case NSValidationInvalidDateError:
                        txt = [txt stringByAppendingFormat:
                               @"the '%@' date is invalid (Code %li).", property, (long)error.code];
                        break;
                    case NSValidationStringTooLongError:
                        txt = [txt stringByAppendingFormat:
                               @"the '%@' text is too long (Code %li).", property, (long)error.code];
                        break;
                    case NSValidationStringTooShortError:
                        txt = [txt stringByAppendingFormat:
                               @"the '%@' text is too short (Code %li).", property, (long)error.code];
                        break;
                    case NSValidationStringPatternMatchingError:
                        txt = [txt stringByAppendingFormat:
                               @"the '%@' text doesn't match the specified pattern (Code %li).", property, (long)error.code];
                        break;
                    case NSManagedObjectValidationError:
                        txt = [txt stringByAppendingFormat:
                               @"generated validation error (Code %li)", (long)error.code];
                        break;

                    default:
                        txt = [txt stringByAppendingFormat:
                               @"Unhandled error code %li in showValidationError method", (long)error.code];
                        break;
                }
            }
            // display error message txt message
            UIAlertView *alertView =
            [[UIAlertView alloc] initWithTitle:@"Validation Error"

                                       message:[NSString stringWithFormat:@"%@Please double-tap the home button and close this application by swiping the application screenshot upwards",txt]
                                      delegate:nil
                             cancelButtonTitle:nil
                             otherButtonTitles:nil];
            [alertView show];
        }
    }
}

#pragma mark – DATA IMPORT
- (BOOL)isDefaultDataAlreadyImportedForStoreWithURL:(NSURL*)url
                                             ofType:(NSString*)type {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    NSError *error;
    NSDictionary *dictionary =
    [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:type
                                                               URL:url
                                                             error:&error];
    if (error) {
        NSLog(@"Error reading persistent store metadata: %@",
              error.localizedDescription);
    }
    else {
        NSNumber *defaultDataAlreadyImported =
        [dictionary valueForKey:@"DefaultDataImported"];
        if (![defaultDataAlreadyImported boolValue]) {
            NSLog(@"Default Data has NOT already been imported");
            return NO;
        }
    }
    if (debug==1) {NSLog(@"Default Data HAS already been imported");}
    return YES;
}
- (void)checkIfDefaultDataNeedsImporting {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (![self isDefaultDataAlreadyImportedForStoreWithURL:[self storeURL]
                                                    ofType:NSSQLiteStoreType]) {
        self.importAlertView =
        [[UIAlertView alloc] initWithTitle:@"Import Default Data?"
                                   message:@"If you've never used Grocery Dude before then some default data might help you understand how to use it. Tap 'Import' to import default data. Tap 'Cancel' to skip the import, especially if you've done this before on other devices."
                                  delegate:self
                         cancelButtonTitle:@"Cancel"
                         otherButtonTitles:@"Import", nil];
        [self.importAlertView show];
    }
}
- (void)importFromXML:(NSURL*)url {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    self.parser = [[NSXMLParser alloc] initWithContentsOfURL:url];
    self.parser.delegate = self;

    NSLog(@"**** START PARSE OF %@", url.path);
    [self.parser parse];
    [[NSNotificationCenter defaultCenter]
     postNotificationName:@"SomethingChanged" object:nil];
    NSLog(@"***** END PARSE OF %@", url.path);
}
- (void)setDefaultDataAsImportedForStore:(NSPersistentStore*)aStore {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    // get metadata dictionary
    NSMutableDictionary *dictionary =
    [NSMutableDictionary dictionaryWithDictionary:[[aStore metadata] copy]];

    if (debug==1) {
        NSLog(@"__Store Metadata BEFORE changes__ \n %@", dictionary);
    }

    // edit metadata dictionary
    [dictionary setObject:@YES forKey:@"DefaultDataImported"];

    // set metadata dictionary
    [self.coordinator setMetadata:dictionary forPersistentStore:aStore];

    if (debug==1) {NSLog(@"__Store Metadata AFTER changes__ \n %@", dictionary);}
}
- (void)setDefaultDataStoreAsInitialStore {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if (![fileManager fileExistsAtPath:self.storeURL.path]) {
        NSURL *defaultDataURL =
        [NSURL fileURLWithPath:[[NSBundle mainBundle]
                                pathForResource:@"DefaultData" ofType:@"sqlite"]];
        NSError *error;
        if (![fileManager copyItemAtURL:defaultDataURL
                                  toURL:self.storeURL
                                  error:&error]) {
            NSLog(@"DefaultData.sqlite copy FAIL: %@",
                  error.localizedDescription);
        }
        else {
            NSLog(@"A copy of DefaultData.sqlite was set as the initial store for %@",
                  self.storeURL.path);
        }
    }
}
- (void)deepCopyFromPersistentStore:(NSURL*)url {
    if (debug==1) {
        NSLog(@"Running %@ '%@' %@", self.class,
              NSStringFromSelector(_cmd),url.path);
    }
    // Periodically refresh the interface during the import
    _importTimer =
    [NSTimer scheduledTimerWithTimeInterval:2.0
                                     target:self
                                   selector:@selector(somethingChanged)
                                   userInfo:nil
                                    repeats:YES];

    [_sourceContext performBlock:^{

        NSLog(@"*** STARTED DEEP COPY FROM DEFAULT DATA PERSISTENT STORE ***");

        NSArray *entitiesToCopy = [NSArray arrayWithObjects:
                                   @"LocationAtHome",@"LocationAtShop",@"Unit",@"Item", nil];

        CoreDataImporter *importer = [[CoreDataImporter alloc]
                                      initWithUniqueAttributes:[self selectedUniqueAttributes]];

        [importer deepCopyEntities:entitiesToCopy
                       fromContext:_sourceContext
                         toContext:_importContext];

        [_context performBlock:^{
            // Stop periodically refreshing the interface
            [_importTimer invalidate];

            // Tell the interface to refresh once import completes
            [self somethingChanged];
        }];

        NSLog(@"*** FINISHED DEEP COPY FROM DEFAULT DATA PERSISTENT STORE ***");
    }];
}

#pragma mark – TEST DATA IMPORT (This code is Grocery Dude data specific)
- (void)importGroceryDudeTestData {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    NSNumber *imported =
    [[NSUserDefaults standardUserDefaults] objectForKey:@"TestDataImport"];

    if (!imported.boolValue) {
        NSLog(@"Importing test data...");
        [_importContext performBlock:^{

            NSManagedObject *locationAtHome =
            [NSEntityDescription insertNewObjectForEntityForName:@"LocationAtHome"
                                          inManagedObjectContext:_importContext];
            NSManagedObject *locationAtShop =
            [NSEntityDescription insertNewObjectForEntityForName:@"LocationAtShop"
                                          inManagedObjectContext:_importContext];
            [locationAtHome setValue:@"Test Home Location" forKey:@"storedIn"];
            [locationAtShop setValue:@"Test Shop Location" forKey:@"aisle"];

            for (int a = 1; a < 101; a++) {                                  @autoreleasepool {                                          // Insert Item                     NSManagedObject *item =                     [NSEntityDescription insertNewObjectForEntityForName:@"Item"                                                   inManagedObjectContext:_importContext];                     [item setValue:[NSString stringWithFormat:@"Test Item %i",a]                             forKey:@"name"];                     [item setValue:locationAtHome                             forKey:@"locationAtHome"];                     [item setValue:locationAtShop                             forKey:@"locationAtShop"];                                          // Insert Photo                     NSManagedObject *photo =                     [NSEntityDescription insertNewObjectForEntityForName:@"Item_Photo"                                                   inManagedObjectContext:_importContext];                     [photo setValue:UIImagePNGRepresentation(                                                              [UIImage imageNamed:@"GroceryHead.png"])                              forKey:@"data"];                                          // Relate Item and Photo                     [item setValue:photo forKey:@"photo"];                                          NSLog(@"Inserting %@", [item valueForKey:@"name"]);                     [Faulter faultObjectWithID:photo.objectID                                      inContext:_importContext];                     [Faulter faultObjectWithID:item.objectID                                      inContext:_importContext];                 }             }             [_importContext reset];                          // ensure import was a one off             [[NSUserDefaults standardUserDefaults]              setObject:[NSNumber numberWithBool:YES]              forKey:@"TestDataImport"];             [[NSUserDefaults standardUserDefaults] synchronize];         }];     }     else {         NSLog(@"Skipped test data import");     } } #pragma mark - DELEGATE: UIAlertView - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {     if (debug==1) {         NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));     }     if (alertView == self.importAlertView) {         if (buttonIndex == 1) { // The ‘Import’ button on the importAlertView                          NSLog(@"Default Data Import Approved by User");             // XML Import             [_importContext performBlock:^{                 [self importFromXML:[[NSBundle mainBundle]                                      URLForResource:@"DefaultData"                                      withExtension:@"xml"]];             }];             // Deep Copy Import From Persistent Store             //[self loadSourceStore];             //[self deepCopyFromPersistentStore:[self sourceStoreURL]];                      } else {             NSLog(@"Default Data Import Cancelled by User");         }         // Set the data as imported regardless of the user's decision         [self setDefaultDataAsImportedForStore:_store];     } } #pragma mark - UNIQUE ATTRIBUTE SELECTION (This code is Grocery Dude data specific and is used when instantiating CoreDataImporter) - (NSDictionary*)selectedUniqueAttributes {     if (debug==1) {         NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));     }     NSMutableArray *entities   = [NSMutableArray new];     NSMutableArray *attributes = [NSMutableArray new];          // Select an attribute in each entity for uniqueness     [entities addObject:@"Item"];[attributes addObject:@"name"];     [entities addObject:@"Unit"];[attributes addObject:@"name"];     [entities addObject:@"LocationAtHome"];[attributes addObject:@"storedIn"];     [entities addObject:@"LocationAtShop"];[attributes addObject:@"aisle"];     [entities addObject:@"Item_Photo"];[attributes addObject:@"data"];          NSDictionary *dictionary = [NSDictionary dictionaryWithObjects:attributes                                                            forKeys:entities];     return dictionary; } #pragma mark - DELEGATE: NSXMLParser (This code is Grocery Dude data specific) - (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {     if (debug==1) {         NSLog(@"Parser Error: %@", parseError.localizedDescription);     } } - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName   namespaceURI:(NSString *)namespaceURI  qualifiedName:(NSString *)qName     attributes:(NSDictionary *)attributeDict {          [self.importContext performBlockAndWait:^{                  // STEP 1: Process only the 'item' element in the XML file         if ([elementName isEqualToString:@"item"]) {                          // STEP 2: Prepare the Core Data Importer             CoreDataImporter *importer =             [[CoreDataImporter alloc] initWithUniqueAttributes:              [self selectedUniqueAttributes]];                          // STEP 3a: Insert a unique 'Item' object             NSManagedObject *item =             [importer insertBasicObjectInTargetEntity:@"Item"                                 targetEntityAttribute:@"name"                                    sourceXMLAttribute:@"name"                                         attributeDict:attributeDict                                               context:_importContext];                          // STEP 3b: Insert a unique 'Unit' object             NSManagedObject *unit =             [importer insertBasicObjectInTargetEntity:@"Unit"                                 targetEntityAttribute:@"name"                                    sourceXMLAttribute:@"unit"                                         attributeDict:attributeDict                                               context:_importContext];                          // STEP 3c: Insert a unique 'LocationAtHome' object             NSManagedObject *locationAtHome =             [importer insertBasicObjectInTargetEntity:@"LocationAtHome"                                 targetEntityAttribute:@"storedIn"                                    sourceXMLAttribute:@"locationathome"                                         attributeDict:attributeDict                                               context:_importContext];                          // STEP 3d: Insert a unique 'LocationAtShop' object             NSManagedObject *locationAtShop =             [importer insertBasicObjectInTargetEntity:@"LocationAtShop"                                 targetEntityAttribute:@"aisle"                                    sourceXMLAttribute:@"locationatshop"                                         attributeDict:attributeDict                                               context:_importContext];                          // STEP 4: Manually add extra attribute values.             [item setValue:@NO forKey:@"listed"];                          // STEP 5: Create relationships             [item setValue:unit forKey:@"unit"];             [item setValue:locationAtHome forKey:@"locationAtHome"];             [item setValue:locationAtShop forKey:@"locationAtShop"];                          // STEP 6: Save new objects to the persistent store.             [CoreDataImporter saveContext:_importContext];                          // STEP 7: Turn objects into faults to save memory             [Faulter faultObjectWithID:item.objectID inContext:_importContext];             [Faulter faultObjectWithID:unit.objectID inContext:_importContext];             [Faulter faultObjectWithID:locationAtHome.objectID inContext:_importContext];             [Faulter faultObjectWithID:locationAtShop.objectID inContext:_importContext];         }     }]; } #pragma mark – UNDERLYING DATA CHANGE NOTIFICATION - (void)somethingChanged {     if (debug==1) {         NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));     }     // Send a notification that tells observing interfaces to refresh their data     [[NSNotificationCenter defaultCenter]      postNotificationName:@"SomethingChanged" object:nil]; } #pragma mark - CORE DATA RESET - (void)resetContext:(NSManagedObjectContext*)moc {     [moc performBlockAndWait:^{         [moc reset];     }]; } - (BOOL)reloadStore {     BOOL success = NO;     NSError *error = nil;     if (![_coordinator removePersistentStore:_store error:&error]) {         NSLog(@"Unable to remove persistent store : %@", error);     }     [self resetContext:_sourceContext];     [self resetContext:_importContext];     [self resetContext:_context];     [self resetContext:_parentContext];     _store = nil;     [self setupCoreData];     [self somethingChanged];     if (_store) {success = YES;}     return success; } - (void)removeAllStoresFromCoordinator:(NSPersistentStoreCoordinator*)psc {     if (debug==1) {         NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));     }     for (NSPersistentStore *s in psc.persistentStores) {         NSError *error = nil;         if (![psc removePersistentStore:s error:&error]) {             NSLog(@"Error removing persistent store: %@", error);         }     } } - (void)resetCoreData {     if (debug==1) {         NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));     }     [_importContext performBlockAndWait:^{         [_importContext save:nil];         [self resetContext:_importContext];     }];     [_context performBlockAndWait:^{         [_context save:nil];         [self resetContext:_context];     }];     [_parentContext performBlockAndWait:^{         [_parentContext save:nil];         [self resetContext:_parentContext];     }];     [self removeAllStoresFromCoordinator:_coordinator];     _store = nil;     _iCloudStore = nil; } #pragma mark - ICLOUD - (BOOL)iCloudAccountIsSignedIn {     if (debug==1) {         NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));     }     id token = [[NSFileManager defaultManager] ubiquityIdentityToken];     if (token) {         NSLog(@"** iCloud is SIGNED IN with token '%@' **", token);         return YES;     }     NSLog(@"** iCloud is NOT SIGNED IN **");     NSLog(@"--> Is iCloud Documents and Data enabled for a valid iCloud account on your Mac & iOS Device or iOS Simulator?");
    NSLog(@"--> Have you enabled the iCloud Capability in the Application Target?");
    NSLog(@"--> Is there a CODE_SIGN_ENTITLEMENTS Xcode warning that needs fixing? You may need to specifically choose a developer instead of using Automatic selection");
    NSLog(@"--> Are you using a Pre-iOS7 Simulator?");
    return NO;
}
- (BOOL)loadiCloudStore {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (_iCloudStore) {return YES;} // Don’t load iCloud store if it’s already loaded

    NSDictionary *options =
    @{
      NSMigratePersistentStoresAutomaticallyOption:@YES
      ,NSInferMappingModelAutomaticallyOption:@YES
      ,NSPersistentStoreUbiquitousContentNameKey:@"Grocery-Dude"
      //,NSPersistentStoreUbiquitousContentURLKey:@"ChangeLogs" // Optional since iOS7
      };
    NSError *error;
    _iCloudStore = [_coordinator addPersistentStoreWithType:NSSQLiteStoreType
                                              configuration:nil
                                                        URL:[self iCloudStoreURL]
                                                    options:options
                                                      error:&error];
    if (_iCloudStore) {
        NSLog(@"** The iCloud Store has been successfully configured at '%@' **",
              _iCloudStore.URL.path);
        return YES;
    }
    NSLog(@"** FAILED to configure the iCloud Store : %@ **", error);
    return NO;
}
- (void)listenForStoreChanges {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    NSNotificationCenter *dc = [NSNotificationCenter defaultCenter];
    [dc addObserver:self
           selector:@selector(storesWillChange:)
               name:NSPersistentStoreCoordinatorStoresWillChangeNotification
             object:_coordinator];

    [dc addObserver:self
           selector:@selector(storesDidChange:)
               name:NSPersistentStoreCoordinatorStoresDidChangeNotification
             object:_coordinator];

    [dc addObserver:self
           selector:@selector(persistentStoreDidImportUbiquitiousContentChanges:)
               name:NSPersistentStoreDidImportUbiquitousContentChangesNotification
             object:_coordinator];
}
- (void)storesWillChange:(NSNotification *)n {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [_importContext performBlockAndWait:^{
        [_importContext save:nil];
        [self resetContext:_importContext];
    }];
    [_context performBlockAndWait:^{
        [_context save:nil];
        [self resetContext:_context];
    }];
    [_parentContext performBlockAndWait:^{
        [_parentContext save:nil];
        [self resetContext:_parentContext];
    }];

    // Refresh UI
    [[NSNotificationCenter defaultCenter] postNotificationName:@"SomethingChanged"
                                                        object:nil
                                                      userInfo:nil];
}
- (void)storesDidChange:(NSNotification *)n {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    // Refresh UI
    [[NSNotificationCenter defaultCenter] postNotificationName:@"SomethingChanged"
                                                        object:nil
                                                      userInfo:nil];
}
- (void)persistentStoreDidImportUbiquitiousContentChanges:(NSNotification*)n {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [_context performBlock:^{
        [_context mergeChangesFromContextDidSaveNotification:n];
        [[NSNotificationCenter defaultCenter] postNotificationName:@"SomethingChanged"
                                                            object:nil];
    }];
}
- (BOOL)iCloudEnabledByUser {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [[NSUserDefaults standardUserDefaults] synchronize]; // Ensure current value
    if ([[[NSUserDefaults standardUserDefaults]
          objectForKey:@"iCloudEnabled"] boolValue]) {
        NSLog(@"** iCloud is ENABLED in Settings **");
        return YES;
    }
    NSLog(@"** iCloud is DISABLED in Settings **");
    return NO;
}
- (void)ensureAppropriateStoreIsLoaded {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (!_store && !_iCloudStore) {
        return; // If neither store is loaded, skip (usually first launch)
    }
    if (![self iCloudEnabledByUser] && _store) {
        NSLog(@"The Non-iCloud Store is loaded as it should be");
        return;
    }
    if ([self iCloudEnabledByUser] && _iCloudStore) {
        NSLog(@"The iCloud Store is loaded as it should be");
        return;
    }
    NSLog(@"** The user preference on using iCloud with this application appears to have changed. Core Data will now be reset. **");

    [self resetCoreData];
    [self setupCoreData];

    [[NSNotificationCenter defaultCenter] postNotificationName:@"SomethingChanged"
                                                        object:nil];

    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:
                          @"Your preference on using iCloud with this application appears to have changed"
                                                    message:
                          @"Content has been updated accordingly"
                                                   delegate:nil
                                          cancelButtonTitle:nil
                                          otherButtonTitles:@"Ok", nil];
    [alert show];
}

@end

 

CoreDataTVC.h

#import 
#import "CoreDataHelper.h"
@interface CoreDataTVC : UITableViewController

@property (strong, nonatomic) NSFetchedResultsController *frc;
@property (strong, nonatomic) NSFetchedResultsController *searchFRC;
@property (strong, nonatomic) UISearchDisplayController *searchDC;
- (void)performFetch;
- (NSFetchedResultsController*)frcFromTV:(UITableView*)tableView;
- (UITableView*)TVFromFRC:(NSFetchedResultsController*)frc;
- (void)reloadSearchFRCForPredicate:(NSPredicate*)predicate
                         withEntity:(NSString*)entity
                          inContext:(NSManagedObjectContext*)context
                withSortDescriptors:(NSArray*)sortDescriptors
             withSectionNameKeyPath:(NSString*)sectionNameKeyPath;
- (void)configureSearch;
@end

#import "CoreDataTVC.h"
@implementation CoreDataTVC
#define debug 0

#pragma mark - FETCHING
- (void)performFetch {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    
    if (self.frc) {
        [self.frc.managedObjectContext performBlockAndWait:^{
            
            NSError *error = nil;
            if (![self.frc performFetch:&error]) {
                
                NSLog(@"Failed to perform fetch: %@", error);
            }
            [self.tableView reloadData];
        }];
    } else {
        NSLog(@"Failed to fetch, the fetched results controller is nil.");
    }
}

#pragma mark - DATASOURCE: UITableView
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return [[[[self frcFromTV:tableView]sections]
             objectAtIndex:section] numberOfObjects];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return [[[self frcFromTV:tableView] sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return [[self frcFromTV:tableView]
            sectionForSectionIndexTitle:title atIndex:index];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return [[[[self frcFromTV:tableView] sections] objectAtIndex:section] name];
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return [[self frcFromTV:tableView] sectionIndexTitles];
}

#pragma mark - DELEGATE: NSFetchedResultsController
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [[self TVFromFRC:controller] beginUpdates];
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [[self TVFromFRC:controller] endUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller
  didChangeSection:(id )sectionInfo
           atIndex:(NSUInteger)sectionIndex
     forChangeType:(NSFetchedResultsChangeType)type {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    switch(type) {
        case NSFetchedResultsChangeInsert:
            [[self TVFromFRC:controller]
             insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
             withRowAnimation:UITableViewRowAnimationFade];
            break;
            
        case NSFetchedResultsChangeDelete:
            [[self TVFromFRC:controller]
             deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
             withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}
- (void)controller:(NSFetchedResultsController *)controller
   didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath
     forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    
    switch(type) {
            
        case NSFetchedResultsChangeInsert:
            [[self TVFromFRC:controller]
             insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
             withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
            
        case NSFetchedResultsChangeDelete:
            [[self TVFromFRC:controller]
             deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
             withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
            
        case NSFetchedResultsChangeUpdate:
            if (!newIndexPath) {
                [[self TVFromFRC:controller]
                 reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                 withRowAnimation:UITableViewRowAnimationNone];
            }
            else {
                [[self TVFromFRC:controller]
                 deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                 withRowAnimation:UITableViewRowAnimationNone];
                [[self TVFromFRC:controller]
                 insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                 withRowAnimation:UITableViewRowAnimationNone];
            }
            break;
            
        case NSFetchedResultsChangeMove:
            [[self TVFromFRC:controller]
             deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
             withRowAnimation:UITableViewRowAnimationAutomatic];
            [[self TVFromFRC:controller]
             insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
             withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
    }
}

#pragma mark - GENERAL
- (NSFetchedResultsController*)frcFromTV:(UITableView*)tableView {
    /*
     If the given tableView is self.tableView return self.frc,
     otherwise self.searchFRC
     */
    return (tableView == self.tableView) ? self.frc : self.searchFRC;
}
- (UITableView*)TVFromFRC:(NSFetchedResultsController*)frc {
    /*
     If the given fetched results controller is self.frc return self.tableView,
     otherwise self.searchDC.searchResultsTableView
     */
    return (frc == self.frc) ? self.tableView : self.searchDC.searchResultsTableView;
}

#pragma mark - DELEGATE: UISearchDisplayController
- (void)searchDisplayControllerDidEndSearch:(UISearchDisplayController *)controller {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    self.searchFRC.delegate = nil;
    self.searchFRC = nil;
}

#pragma mark - SEARCH
- (void)reloadSearchFRCForPredicate:(NSPredicate*)predicate
                         withEntity:(NSString*)entity
                          inContext:(NSManagedObjectContext*)context
                withSortDescriptors:(NSArray*)sortDescriptors
             withSectionNameKeyPath:(NSString*)sectionNameKeyPath {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:entity];
    request.sortDescriptors = sortDescriptors;
    request.predicate = predicate;
    request.fetchBatchSize = 15;
    
    self.searchFRC =
    [[NSFetchedResultsController alloc] initWithFetchRequest:request
                                        managedObjectContext:context
                                          sectionNameKeyPath:sectionNameKeyPath
                                                   cacheName:nil];
    self.searchFRC.delegate = self;
    
    [self.searchFRC.managedObjectContext performBlockAndWait:^{
        NSError *error;
        if (![self.searchFRC performFetch:&error]) {
            NSLog(@"SEARCH FETCH ERROR: %@", error);
        }
    }];
}
- (void)configureSearch {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    UISearchBar *searchBar =
    [[UISearchBar alloc] initWithFrame:
     CGRectMake(0, 0, self.tableView.frame.size.width, 44.0)];
    searchBar.autocorrectionType = UITextAutocorrectionTypeNo;
    self.tableView.tableHeaderView = searchBar;
    
    self.searchDC =
    [[UISearchDisplayController alloc] initWithSearchBar:searchBar
                                      contentsController:self];
    self.searchDC.delegate = self;
    self.searchDC.searchResultsDataSource = self;
    self.searchDC.searchResultsDelegate = self;
}

@end

说明:

1.获取objectID unitVC.selectedObjectID =[[self.frc objectAtIndexPath:indexPath] objectID];

2.传递的是objectID,通过[cdh.context existingObjectWithID:self.selectedObjectIDerror:nil] 获取该object;

itemtvc

vc

PrepareTVC

#import <UIKit/UIKit.h>
#import "CoreDataTVC.h"
@interface PrepareTVC : CoreDataTVC 
@property (strong, nonatomic) UIActionSheet *clearConfirmActionSheet;
@end

#import "PrepareTVC.h"
#import "CoreDataHelper.h"
#import "Item.h"
#import "Unit.h"
#import "AppDelegate.h"
#import "ItemVC.h"
#import "Thumbnailer.h"

@implementation PrepareTVC
#define debug 0

#pragma mark - DATA
- (void)configureFetch {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];

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

    request.sortDescriptors =
    [NSArray arrayWithObjects:
     [NSSortDescriptor sortDescriptorWithKey:@"locationAtHome.storedIn"
                                   ascending:YES],
     [NSSortDescriptor sortDescriptorWithKey:@"name"
                                   ascending:YES],
     nil];
    [request setFetchBatchSize:15];
    self.frc =
    [[NSFetchedResultsController alloc] initWithFetchRequest:request
                                        managedObjectContext:cdh.context
                                          sectionNameKeyPath:@"locationAtHome.storedIn"
                                                   cacheName:nil];
    self.frc.delegate = self;
}

#pragma mark - VIEW
- (void)viewDidAppear:(BOOL)animated {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [super viewDidAppear:animated];

    // Create missing Thumbnails
    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    NSArray *sortDescriptors =
    [NSArray arrayWithObjects:
     [NSSortDescriptor sortDescriptorWithKey:@"locationAtHome.storedIn"
                                   ascending:YES],
     [NSSortDescriptor sortDescriptorWithKey:@"name"
                                   ascending:YES],
     nil];

    [Thumbnailer createMissingThumbnailsForEntityName:@"Item"
                           withThumbnailAttributeName:@"thumbnail"
                            withPhotoRelationshipName:@"photo"
                               withPhotoAttributeName:@"data"
                                  withSortDescriptors:sortDescriptors
                                    withImportContext:cdh.importContext];
}
- (void)viewDidLoad {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [super viewDidLoad];
    [self configureFetch];
    [self performFetch];
    self.clearConfirmActionSheet.delegate = self;

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(performFetch)
                                                 name:@"SomethingChanged"
                                               object:nil];
    [self configureSearch];
}
- (UITableViewCell*)tableView:(UITableView *)tableView
        cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    static NSString *cellIdentifier = @"Item Cell";

    UITableViewCell *cell =
    [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1
                                      reuseIdentifier:cellIdentifier];
    }
    cell.accessoryType = UITableViewCellAccessoryDetailButton;
    Item *item = [[self frcFromTV:tableView] objectAtIndexPath:indexPath];

    NSMutableString *title = [NSMutableString stringWithFormat:@"%@%@ %@",
                              item.quantity, item.unit.name, item.name];
    [title replaceOccurrencesOfString:@"(null)"
                           withString:@""
                              options:0
                                range:NSMakeRange(0, [title length])];
    cell.textLabel.text = title;

    // make selected items orange
    if ([item.listed boolValue]) {
        [cell.textLabel setFont:[UIFont fontWithName:@"Helvetica Neue" size:18]];
        [cell.textLabel setTextColor:[UIColor orangeColor]];
    }
    else {
        [cell.textLabel setFont:[UIFont fontWithName:@"Helvetica Neue" size:16]];
        [cell.textLabel setTextColor:[UIColor grayColor]];
    }
    cell.imageView.image = [UIImage imageWithData:item.thumbnail];
    return cell;
}
- (NSArray*)sectionIndexTitlesForTableView:(UITableView *)tableView {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return nil; // we don't want a section index.
}
- (void)tableView:(UITableView *)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (editingStyle == UITableViewCellEditingStyleDelete) {

        NSFetchedResultsController *frc = [self frcFromTV:tableView];
        Item *deleteTarget = [frc objectAtIndexPath:indexPath];
        [frc.managedObjectContext deleteObject:deleteTarget];
        [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                         withRowAnimation:UITableViewRowAnimationFade];
    }
    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    [cdh backgroundSaveContext];
}
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath  {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    NSFetchedResultsController *frc = [self frcFromTV:tableView];
    NSManagedObjectID *itemid = [[frc objectAtIndexPath:indexPath] objectID];

    Item *item =
    (Item*)[frc.managedObjectContext existingObjectWithID:itemid error:nil];
    if ([item.listed boolValue]) {
        item.listed = [NSNumber numberWithBool:NO];
    }
    else {
        item.listed = [NSNumber numberWithBool:YES];
        item.collected = [NSNumber numberWithBool:NO];
    }
    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    [cdh backgroundSaveContext];
}

#pragma mark - INTERACTION
- (IBAction)clear:(id)sender {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }

    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    NSFetchRequest *request =
    [cdh.model fetchRequestTemplateForName:@"ShoppingList"];
    NSArray *shoppingList =
    [cdh.context executeFetchRequest:request error:nil];

    if (shoppingList.count > 0) {

        self.clearConfirmActionSheet =
        [[UIActionSheet alloc] initWithTitle:@"Clear Entire Shopping List?"
                                    delegate:self
                           cancelButtonTitle:@"Cancel"
                      destructiveButtonTitle:@"Clear"
                           otherButtonTitles:nil];
        [self.clearConfirmActionSheet
         showFromTabBar:self.navigationController.tabBarController.tabBar];
    }
    else {
        UIAlertView *alert =
        [[UIAlertView alloc] initWithTitle:@"Nothing to Clear"
                                   message:@"Add items to the Shop tab by tapping them on the Prepare tab. Remove all items from the Shop tab by clicking Clear on the Prepare tab"
                                  delegate:nil
                         cancelButtonTitle:@"Ok"
                         otherButtonTitles:nil];
        [alert show];
    }
    shoppingList = nil;
    [cdh backgroundSaveContext];
}
- (void)actionSheet:(UIActionSheet *)actionSheet
clickedButtonAtIndex:(NSInteger)buttonIndex {

    if (actionSheet == self.clearConfirmActionSheet) {
        if (buttonIndex == [actionSheet destructiveButtonIndex]) {
            [self performSelector:@selector(clearList)];
        }
        else if (buttonIndex == [actionSheet cancelButtonIndex]){
            [actionSheet dismissWithClickedButtonIndex:
             [actionSheet cancelButtonIndex] animated:YES];
        }
    }
}
- (void)clearList {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }

    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    NSFetchRequest *request =
    [cdh.model fetchRequestTemplateForName:@"ShoppingList"];
    NSArray *shoppingList =
    [cdh.context executeFetchRequest:request error:nil];

    for (Item *item in shoppingList) {
        item.listed = [NSNumber numberWithBool:NO];
    }
    [cdh backgroundSaveContext];
}

#pragma mark - SEGUE
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    ItemVC *itemVC = segue.destinationViewController;
    if ([segue.identifier isEqualToString:@"Add Item Segue"])
    {
        CoreDataHelper *cdh =
        [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
        Item *newItem =
        [NSEntityDescription insertNewObjectForEntityForName:@"Item"
                                      inManagedObjectContext:cdh.context];
        NSError *error = nil;
        if (![cdh.context
              obtainPermanentIDsForObjects:[NSArray arrayWithObject:newItem]
              error:&error]) {
            NSLog(@"Couldn't obtain a permanent ID for object %@", error);
        }
        itemVC.selectedItemID = newItem.objectID;
    }
    else {
        NSLog(@"Unidentified Segue Attempted!");
    }
}
- (void)tableView:(UITableView *)tableView
accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    ItemVC *itemVC =
    [self.storyboard instantiateViewControllerWithIdentifier:@"ItemVC"];
    itemVC.selectedItemID =
    [[[self frcFromTV:tableView] objectAtIndexPath:indexPath] objectID];
    [self.navigationController pushViewController:itemVC animated:YES];
}

#pragma mark - SEARCH
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (searchString.length > 0) {
        NSLog(@"--> Searching for '%@'", searchString);
        NSPredicate *predicate =
        [NSPredicate predicateWithFormat:@"name CONTAINS[cd] %@", searchString];

        NSArray *sortDescriptors =
        [NSArray arrayWithObjects:
         [NSSortDescriptor sortDescriptorWithKey:@"locationAtHome.storedIn"
                                       ascending:YES],
         [NSSortDescriptor sortDescriptorWithKey:@"name"
                                       ascending:YES], nil];

        CoreDataHelper *cdh =
        [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];

        [self reloadSearchFRCForPredicate:predicate
                               withEntity:@"Item"
                                inContext:cdh.context
                      withSortDescriptors:sortDescriptors
                   withSectionNameKeyPath:@"locationAtHome.storedIn"];
    } else {
        return NO;
    }
    return YES;
}
@end

ShopTVC.m

#import "ShopTVC.h"
#import "CoreDataHelper.h"
#import "Item.h"
#import "Unit.h"
#import "AppDelegate.h"
#import "ItemVC.h"
#import "Thumbnailer.h"

@implementation ShopTVC
#define debug 0

#pragma mark - DATA
- (void)configureFetch {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    NSFetchRequest *request =
    [[cdh.model fetchRequestTemplateForName:@"ShoppingList"] copy];

    request.sortDescriptors =
    [NSArray arrayWithObjects:
     [NSSortDescriptor sortDescriptorWithKey:@"locationAtShop.aisle"
                                   ascending:YES],
     [NSSortDescriptor sortDescriptorWithKey:@"name"
                                   ascending:YES],
     nil];
    [request setFetchBatchSize:15];

    self.frc =
    [[NSFetchedResultsController alloc] initWithFetchRequest:request
                                        managedObjectContext:cdh.context
                                          sectionNameKeyPath:@"locationAtShop.aisle"
                                                   cacheName:nil];
    self.frc.delegate = self;
}

#pragma mark - VIEW
- (void)viewDidAppear:(BOOL)animated {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [super viewDidAppear:animated];

    // Create missing Thumbnails
    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    NSArray *sortDescriptors =
    [NSArray arrayWithObjects:
     [NSSortDescriptor sortDescriptorWithKey:@"locationAtHome.storedIn"
                                   ascending:YES],
     [NSSortDescriptor sortDescriptorWithKey:@"name"
                                   ascending:YES],
     nil];

    [Thumbnailer createMissingThumbnailsForEntityName:@"Item"
                           withThumbnailAttributeName:@"thumbnail"
                            withPhotoRelationshipName:@"photo"
                               withPhotoAttributeName:@"data"
                                  withSortDescriptors:sortDescriptors
                                    withImportContext:cdh.importContext];
}
- (void)viewDidLoad {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [super viewDidLoad];
    [self configureFetch];
    [self performFetch];

    // Respond to changes in underlying store
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(performFetch)
                                                 name:@"SomethingChanged"
                                               object:nil];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    static NSString *cellIdentifier = @"Shop Cell";
    UITableViewCell *cell =
    [tableView dequeueReusableCellWithIdentifier:cellIdentifier
                                    forIndexPath:indexPath];

    Item *item = [self.frc objectAtIndexPath:indexPath];
    NSMutableString *title = [NSMutableString stringWithFormat:@"%@%@ %@",
                              item.quantity, item.unit.name, item.name];
    [title replaceOccurrencesOfString:@"(null)"
                           withString:@""
                              options:0
                                range:NSMakeRange(0, [title length])];
    cell.textLabel.text = title;

    // make collected items green
    if (item.collected.boolValue) {
        [cell.textLabel setFont:[UIFont
                                 fontWithName:@"Helvetica Neue" size:16]];
        [cell.textLabel setTextColor:
         [UIColor colorWithRed:0.368627450
                         green:0.741176470
                          blue:0.349019607 alpha:1.0]];
        cell.accessoryType = UITableViewCellAccessoryCheckmark;
    }
    else {
        [cell.textLabel setFont:[UIFont
                                 fontWithName:@"Helvetica Neue" size:18]];
        cell.textLabel.textColor = [UIColor orangeColor];
        cell.accessoryType = UITableViewCellAccessoryDetailButton;
    }

    cell.imageView.image = [UIImage imageWithData:item.thumbnail];

    return cell;
}
- (NSArray*)sectionIndexTitlesForTableView:(UITableView *)tableView {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return nil; // prevent section index.
}
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath  {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    Item *item = [self.frc objectAtIndexPath:indexPath];
    if (item.collected.boolValue) {
        item.collected = [NSNumber numberWithBool:NO];
    }
    else {
        item.collected = [NSNumber numberWithBool:YES];
    }
    [self.tableView reloadRowsAtIndexPaths:
     [NSArray  arrayWithObject:indexPath]
                          withRowAnimation:UITableViewRowAnimationNone];

    CoreDataHelper *cdh = [(AppDelegate *)[[UIApplication
                                            sharedApplication] delegate] cdh];
    [cdh backgroundSaveContext];
}

#pragma mark - INTERACTION
- (IBAction)clear:(id)sender {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if ([self.frc.fetchedObjects count] == 0) {

        UIAlertView *alert =
        [[UIAlertView alloc] initWithTitle:@"Nothing to Clear"
                                   message:@"Add items using the Prepare tab"
                                  delegate:nil
                         cancelButtonTitle:@"Ok" otherButtonTitles:nil];
        [alert show];
        return;
    }
    BOOL nothingCleared = YES;
    for (Item *item in self.frc.fetchedObjects) {

        if (item.collected.boolValue)
        {
            item.listed = [NSNumber numberWithBool:NO];
            item.collected = [NSNumber numberWithBool:NO];
            nothingCleared = NO;
        }
    }
    if (nothingCleared) {
        UIAlertView *alert =
        [[UIAlertView alloc] initWithTitle:nil message:
         @"Select items to be removed from the list before pressing Clear"
                                  delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil];
        [alert show];
    }
    CoreDataHelper *cdh = [(AppDelegate *)[[UIApplication
                                            sharedApplication] delegate] cdh];
    [cdh backgroundSaveContext];
}

#pragma mark - SEGUE
- (void)tableView:(UITableView *)tableView
accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    ItemVC *itemVC =
    [self.storyboard instantiateViewControllerWithIdentifier:@"ItemVC"];
    itemVC.selectedItemID =
    [[self.frc objectAtIndexPath:indexPath] objectID];
    [self.navigationController pushViewController:itemVC animated:YES];
}
@end

ItemVC

itemVC

#import <UIKit/UIKit.h>
#import "CoreDataHelper.h"
#import "UnitPickerTF.h"
#import "LocationAtHomePickerTF.h" 
#import "LocationAtShopPickerTF.h"

@interface ItemVC : UIViewController <UITextFieldDelegate,CoreDataPickerTFDelegate,UIImagePickerControllerDelegate,UINavigationControllerDelegate>
@property (strong, nonatomic) NSManagedObjectID *selectedItemID;
@property (strong, nonatomic) IBOutlet UIScrollView *scrollView;
@property (strong, nonatomic) IBOutlet UITextField *nameTextField;
@property (strong, nonatomic) IBOutlet UITextField *quantityTextField;
@property (strong, nonatomic) IBOutlet UnitPickerTF *unitPickerTextField;
@property (strong, nonatomic) IBOutlet LocationAtHomePickerTF *homeLocationPickerTextField;
@property (strong, nonatomic) IBOutlet LocationAtShopPickerTF *shopLocationPickerTextField;
@property (strong, nonatomic) IBOutlet UITextField *activeField;
@property (strong, nonatomic) IBOutlet UIImageView *photoImageView;
@property (strong, nonatomic) IBOutlet UIButton *cameraButton;
@property (strong, nonatomic) UIImagePickerController *camera;
@end

ItemVC

#import "ItemVC.h"
#import "AppDelegate.h" 
#import "Item.h"
#import "LocationAtHome.h"
#import "LocationAtShop.h"
#import "Unit.h"
#import "Item_Photo.h"

@implementation ItemVC
#define debug 0

#pragma mark - INTERACTION
- (IBAction)done:(id)sender {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [self hideKeyboard];
    [self.navigationController popViewControllerAnimated:YES];
}
- (void)hideKeyboardWhenBackgroundIsTapped {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    UITapGestureRecognizer *tgr =
    [[UITapGestureRecognizer alloc] initWithTarget:self
                                            action:@selector(hideKeyboard)];
    [tgr setCancelsTouchesInView:NO];
    [self.view addGestureRecognizer:tgr];
}
- (void)hideKeyboard {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [self.view endEditing:YES];
}
- (void)keyboardDidShow:(NSNotification *)n {

    // Find top of keyboard input view (i.e. picker)
    CGRect keyboardRect =
    [[[n userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    keyboardRect = [self.view convertRect:keyboardRect fromView:nil];
    CGFloat keyboardTop = keyboardRect.origin.y;

    // Resize scroll view
    CGRect newScrollViewFrame =
    CGRectMake(0, 0, self.view.bounds.size.width, keyboardTop);
    newScrollViewFrame.size.height = keyboardTop - self.view.bounds.origin.y;
    [self.scrollView setFrame:newScrollViewFrame];

    // Scroll to the active Text-Field
    [self.scrollView scrollRectToVisible:self.activeField.frame animated:YES];
}
- (void)keyboardWillHide:(NSNotification *)n {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    CGRect defaultFrame =
    CGRectMake(self.scrollView.frame.origin.x,
               self.scrollView.frame.origin.y,
               self.view.frame.size.width,
               self.view.frame.size.height);

    // Reset Scrollview to the same size as the containing view
    [self.scrollView setFrame:defaultFrame];

    // Scroll to the top again
    [self.scrollView scrollRectToVisible:self.nameTextField.frame
                                animated:YES];
}

#pragma mark - DELEGATE: UITextField
- (void)textFieldDidBeginEditing:(UITextField *)textField {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (textField == self.nameTextField) {

        if ([self.nameTextField.text isEqualToString:@"New Item"]) {
            self.nameTextField.text = @"";
        }
    }
    if (textField == _unitPickerTextField && _unitPickerTextField.picker) {
        [_unitPickerTextField fetch];
        [_unitPickerTextField.picker reloadAllComponents];
    } else if (textField == _homeLocationPickerTextField &&
               _homeLocationPickerTextField.picker) {
        [_homeLocationPickerTextField fetch];
        [_homeLocationPickerTextField.picker reloadAllComponents];
    } else if (textField == _shopLocationPickerTextField &&
               _shopLocationPickerTextField.picker) {
        [_shopLocationPickerTextField fetch];
        [_shopLocationPickerTextField.picker reloadAllComponents];
    }
    _activeField = textField;
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    Item *item =
    (Item*)[cdh.context existingObjectWithID:self.selectedItemID error:nil];

    if (textField == self.nameTextField) {
        if ([self.nameTextField.text isEqualToString:@""]) {
            self.nameTextField.text = @"New Item";
        }
        item.name = self.nameTextField.text;
    }
    else if (textField == self.quantityTextField) {
        item.quantity =
        [NSNumber numberWithFloat:self.quantityTextField.text.floatValue];
    }
}

#pragma mark - VIEW
- (void)refreshInterface {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (self.selectedItemID) {
        CoreDataHelper *cdh = [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
        Item *item = (Item*)[cdh.context existingObjectWithID:self.selectedItemID error:nil];
        self.nameTextField.text = item.name;
        self.quantityTextField.text = item.quantity.stringValue;
        self.unitPickerTextField.text = item.unit.name;
        self.unitPickerTextField.selectedObjectID = item.unit.objectID;
        self.homeLocationPickerTextField.text = item.locationAtHome.storedIn;
        self.homeLocationPickerTextField.selectedObjectID = item.locationAtHome.objectID;
        self.shopLocationPickerTextField.text = item.locationAtShop.aisle;
        self.shopLocationPickerTextField.selectedObjectID = item.locationAtShop.objectID;

        [cdh.context performBlock:^{
            self.photoImageView.image =
            [UIImage imageWithData:item.photo.data];
        }];

        [self checkCamera];
    }
}
- (void)viewDidLoad {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [super viewDidLoad];
    [self hideKeyboardWhenBackgroundIsTapped];
    self.nameTextField.delegate = self;
    self.quantityTextField.delegate = self;

    self.unitPickerTextField.delegate = self;
    self.unitPickerTextField.pickerDelegate = self;
    self.homeLocationPickerTextField.delegate = self;
    self.homeLocationPickerTextField.pickerDelegate = self;
    self.shopLocationPickerTextField.delegate = self;
    self.shopLocationPickerTextField.pickerDelegate = self;
}
- (void)viewWillAppear:(BOOL)animated {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }

    // Register for keyboard notifications while the view is visible.
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardDidShow:)
                                                 name:UIKeyboardDidShowNotification
                                               object:self.view.window];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillHide:)
                                                 name:UIKeyboardWillHideNotification
                                               object:self.view.window];

    [self ensureItemHomeLocationIsNotNull];
    [self ensureItemShopLocationIsNotNull];

    [self refreshInterface];
    if ([self.nameTextField.text isEqual:@"New Item"]) {
        self.nameTextField.text = @"";
        [self.nameTextField becomeFirstResponder];
    }
}
- (void)viewDidDisappear:(BOOL)animated {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [self ensureItemHomeLocationIsNotNull];
    [self ensureItemShopLocationIsNotNull];

    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    [cdh backgroundSaveContext];

    // Unregister for keyboard notifications while the view is not visible.
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:UIKeyboardDidShowNotification
                                                  object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:UIKeyboardWillHideNotification
                                                  object:nil];

    // Turn item & item photo into a fault
    NSError *error;
    Item *item =
    (Item*)[cdh.context existingObjectWithID:self.selectedItemID error:&error];
    if (error) {
        NSLog(@"ERROR!!! --> %@", error.localizedDescription);
    } else {
        [cdh.context refreshObject:item.photo mergeChanges:NO];
        [cdh.context refreshObject:item mergeChanges:NO];
    }
}

#pragma mark - DATA
- (void)ensureItemHomeLocationIsNotNull {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (self.selectedItemID) {
        CoreDataHelper *cdh =
        [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
        Item *item =
        (Item*)[cdh.context existingObjectWithID:self.selectedItemID
                                           error:nil];
        if (!item.locationAtHome) {
            NSFetchRequest *request =
            [[cdh model]
             fetchRequestTemplateForName:@"UnknownLocationAtHome"];
            NSArray *fetchedObjects =
            [cdh.context executeFetchRequest:request error:nil];

            if ([fetchedObjects count] > 0) {
                item.locationAtHome = [fetchedObjects objectAtIndex:0];
            }
            else {
                LocationAtHome *locationAtHome =
                [NSEntityDescription
                 insertNewObjectForEntityForName:@"LocationAtHome"
                 inManagedObjectContext:cdh.context];
                NSError *error = nil;
                if (![cdh.context obtainPermanentIDsForObjects:
                      [NSArray arrayWithObject:locationAtHome] error:&error]) {
                    NSLog(@"Couldn't obtain a permanent ID for object %@",
                          error);
                }
                locationAtHome.storedIn = @"..Unknown Location..";
                item.locationAtHome = locationAtHome;
            }
        }
    }
}
- (void)ensureItemShopLocationIsNotNull {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (self.selectedItemID) {
        CoreDataHelper *cdh =
        [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
        Item *item =
        (Item*)[cdh.context existingObjectWithID:self.selectedItemID
                                           error:nil];
        if (!item.locationAtShop) {
            NSFetchRequest *request =
            [[cdh model]
             fetchRequestTemplateForName:@"UnknownLocationAtShop"];
            NSArray *fetchedObjects =
            [cdh.context executeFetchRequest:request error:nil];

            if ([fetchedObjects count] > 0) {
                item.locationAtShop = [fetchedObjects objectAtIndex:0];
            }
            else {
                LocationAtShop *locationAtShop =
                [NSEntityDescription
                 insertNewObjectForEntityForName:@"LocationAtShop"
                 inManagedObjectContext:cdh.context];
                NSError *error = nil;
                if (![cdh.context obtainPermanentIDsForObjects:
                      [NSArray arrayWithObject:locationAtShop] error:&error]) {
                    NSLog(@"Couldn't obtain a permanent ID for object %@",
                          error);
                }
                locationAtShop.aisle = @"..Unknown Location..";
                item.locationAtShop = locationAtShop;
            }
        }
    }
}

#pragma mark - PICKERS
- (void)selectedObjectID:(NSManagedObjectID *)objectID
      changedForPickerTF:(CoreDataPickerTF *)pickerTF {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (self.selectedItemID) {
        CoreDataHelper *cdh =
        [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];

        Item *item =
        (Item*)[cdh.context existingObjectWithID:self.selectedItemID
                                           error:nil];;
        NSError *error;
        if (pickerTF == self.unitPickerTextField) {
            Unit *unit = (Unit*)[cdh.context existingObjectWithID:objectID
                                                            error:&error];
            item.unit = unit;
            self.unitPickerTextField.text = item.unit.name;
        }
        else if (pickerTF == self.homeLocationPickerTextField) {
            LocationAtHome *locationAtHome =
            (LocationAtHome*)[cdh.context existingObjectWithID:objectID
                                                         error:&error];
            item.locationAtHome = locationAtHome;
            self.homeLocationPickerTextField.text =
            item.locationAtHome.storedIn;
        }
        else if (pickerTF == self.shopLocationPickerTextField) {
            LocationAtShop *locationAtShop =
            (LocationAtShop*)[cdh.context existingObjectWithID:objectID
                                                         error:&error];
            item.locationAtShop = locationAtShop;
            self.shopLocationPickerTextField.text =
            item.locationAtShop.aisle;
        }
        [self refreshInterface];
        if (error) {
            NSLog(@"Error selecting object on picker: %@, %@",
                  error, error.localizedDescription);
        }
    }
}
- (void)selectedObjectClearedForPickerTF:(CoreDataPickerTF *)pickerTF {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (self.selectedItemID) {
        CoreDataHelper *cdh =
        [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
        Item *item =
        (Item*)[cdh.context existingObjectWithID:self.selectedItemID
                                           error:nil];

        if (pickerTF == self.unitPickerTextField) {
            item.unit = nil;
            self.unitPickerTextField.text = @"";
        }
        else if (pickerTF == self.homeLocationPickerTextField) {
            item.LocationAtHome = nil;
            self.homeLocationPickerTextField.text = @"";
        }
        else if (pickerTF == self.shopLocationPickerTextField) {
            item.LocationAtShop = nil;
            self.shopLocationPickerTextField.text = @"";
        }
        [self refreshInterface];
    }
}

#pragma mark - CAMERA
- (void)checkCamera {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    self.cameraButton.enabled =
    [UIImagePickerController
     isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera];
}
- (IBAction)showCamera:(id)sender {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if ([UIImagePickerController
         isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
    {
        NSLog(@"Camera is available");
        _camera = [[UIImagePickerController alloc] init];
        _camera.sourceType = UIImagePickerControllerSourceTypeCamera;
        _camera.mediaTypes =
        [UIImagePickerController
         availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera];
        _camera.allowsEditing = YES;
        _camera.delegate = self;
        [self.navigationController presentViewController:_camera
                                                animated:YES
                                              completion:nil];
    }
    else
    {
        NSLog(@"Camera not available");
    }
}
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];

    Item *item =
    (Item*)[cdh.context existingObjectWithID:self.selectedItemID error:nil];

    UIImage *photo =
    (UIImage *)[info objectForKey:UIImagePickerControllerEditedImage];

    NSLog(@"Captured %f x %f photo",photo.size.height, photo.size.width);

    if (!item.photo) { // Create photo object it doesn't exist
        Item_Photo *newPhoto =
        [NSEntityDescription insertNewObjectForEntityForName:@"Item_Photo"
                                      inManagedObjectContext:cdh.context];
        [cdh.context obtainPermanentIDsForObjects:
         [NSArray arrayWithObject:newPhoto] error:nil];
        item.photo = newPhoto;
    }
    item.photo.data = UIImageJPEGRepresentation(photo, 0.5);
    item.thumbnail = nil;

    self.photoImageView.image = photo;

    [picker dismissViewControllerAnimated:YES completion:nil];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [picker dismissViewControllerAnimated:YES completion:nil];
}

@end

vc

 

View – UnitesTVC

 

#import "UnitsTVC.h"
#import "CoreDataHelper.h"
#import "AppDelegate.h"
#import "Unit.h"
#import "UnitVC.h"

@implementation UnitsTVC
#define debug 0

#pragma mark - DATA
- (void)configureFetch {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    NSFetchRequest *request =
    [NSFetchRequest fetchRequestWithEntityName:@"Unit"];
    request.sortDescriptors = [NSArray arrayWithObjects:
                               [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES],nil];
    [request setFetchBatchSize:15];
    self.frc =
    [[NSFetchedResultsController alloc] initWithFetchRequest:request
                                        managedObjectContext:cdh.context
                                          sectionNameKeyPath:nil
                                                   cacheName:nil];
    self.frc.delegate = self;
}

#pragma mark - VIEW
- (void)viewDidLoad {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [super viewDidLoad];
    [self configureFetch];
    [self performFetch];
    // Respond to changes in underlying store
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(performFetch)
                                                 name:@"SomethingChanged"
                                               object:nil];
}
- (UITableViewCell*)tableView:(UITableView *)tableView
        cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    static NSString *cellIdentifier = @"Unit Cell";
    UITableViewCell *cell =
    [tableView dequeueReusableCellWithIdentifier:cellIdentifier
                                    forIndexPath:indexPath];
    Unit *unit = [self.frc objectAtIndexPath:indexPath];
    cell.textLabel.text = unit.name;
    return cell;
}
- (void)tableView:(UITableView *)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        Unit *deleteTarget = [self.frc objectAtIndexPath:indexPath];
        [self.frc.managedObjectContext deleteObject:deleteTarget];
        [self.tableView reloadRowsAtIndexPaths:
         [NSArray arrayWithObject:indexPath]
                              withRowAnimation:UITableViewRowAnimationFade];
    }

    CoreDataHelper *cdh = [(AppDelegate *)[[UIApplication
                                            sharedApplication] delegate] cdh];
    [cdh backgroundSaveContext];
}

#pragma mark - INTERACTION
- (IBAction)done:(id)sender {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [self.parentViewController
     dismissViewControllerAnimated:YES completion:nil];
}

#pragma mark - SEGUE
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    UnitVC *unitVC = segue.destinationViewController;
    if ([segue.identifier isEqualToString:@"Add Object Segue"])
    {
        CoreDataHelper *cdh =
        [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
        Unit *newUnit =
        [NSEntityDescription insertNewObjectForEntityForName:@"Unit"
                                      inManagedObjectContext:cdh.context];
        NSError *error = nil;
        if (![cdh.context obtainPermanentIDsForObjects:
              [NSArray arrayWithObject:newUnit] error:&error]) {
            NSLog(@"Couldn't obtain a permanent ID for object %@", error);
        }
        unitVC.selectedObjectID = newUnit.objectID;
    }
    else if ([segue.identifier isEqualToString:@"Edit Object Segue"])
    {
        NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
        unitVC.selectedObjectID =
        [[self.frc objectAtIndexPath:indexPath] objectID];
    }
    else {
        NSLog(@"Unidentified Segue Attempted!");
    }
}

@end
#import <UIKit/UIKit.h>
#import "CoreDataHelper.h"
@interface UnitVC : UIViewController 
@property (strong, nonatomic) NSManagedObjectID *selectedObjectID;
@property (strong, nonatomic) IBOutlet UITextField *nameTextField;
@end

#import "UnitVC.h"
#import "Unit.h"
#import "AppDelegate.h"

@implementation UnitVC
#define debug 0

#pragma mark - VIEW
- (void)refreshInterface {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (self.selectedObjectID) {
        CoreDataHelper *cdh =
        [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
        Unit *unit =
        (Unit*)[cdh.context existingObjectWithID:self.selectedObjectID
                                           error:nil];
        self.nameTextField.text = unit.name;
    }
}
- (void)viewDidLoad {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [super viewDidLoad];
    [self hideKeyboardWhenBackgroundIsTapped];
    self.nameTextField.delegate = self;
}
- (void)viewWillAppear:(BOOL)animated {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [self refreshInterface];
    [self.nameTextField becomeFirstResponder];
}
- (void)viewDidDisappear:(BOOL)animated {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    [cdh backgroundSaveContext];
    [[NSNotificationCenter defaultCenter] postNotificationName:@"SomethingChanged"
                                                        object:nil
                                                      userInfo:nil];
}

#pragma mark - TEXTFIELD
- (void)textFieldDidEndEditing:(UITextField *)textField {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    Unit *unit =
    (Unit*)[cdh.context existingObjectWithID:self.selectedObjectID
                                       error:nil];
    if (textField == self.nameTextField) {
        unit.name = self.nameTextField.text;
        [[NSNotificationCenter defaultCenter]
         postNotificationName:@"SomethingChanged"
         object:nil];
    }
}

#pragma mark - INTERACTION
- (IBAction)done:(id)sender {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [self hideKeyboard];
    [self.navigationController popViewControllerAnimated:YES];
}
- (void)hideKeyboardWhenBackgroundIsTapped {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    UITapGestureRecognizer *tgr =
    [[UITapGestureRecognizer alloc] initWithTarget:self
                                            action:@selector(hideKeyboard)];
    [tgr setCancelsTouchesInView:NO];
    [self.view addGestureRecognizer:tgr];
}
- (void)hideKeyboard {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [self.view endEditing:YES];
}
@end

CoreDataPickerTF 此VC是textField的subclass, 并且定义两个dalegate, 供itemvc调用数据时用;

CoreDataPickerTF

#import <UIKit/UIKit.h>
#import "CoreDataHelper.h"
@class CoreDataPickerTF;
@protocol CoreDataPickerTFDelegate 
- (void)selectedObjectID:(NSManagedObjectID*)objectID
      changedForPickerTF:(CoreDataPickerTF*)pickerTF;
@optional
- (void)selectedObjectClearedForPickerTF:(CoreDataPickerTF*)pickerTF;
@end

@interface CoreDataPickerTF : UITextField
<UIKeyInput, UIPickerViewDelegate, UIPickerViewDataSource>
@property (nonatomic, weak) id  pickerDelegate;
@property (nonatomic, strong) UIPickerView *picker;
@property (nonatomic, strong) NSArray *pickerData;
@property (nonatomic, strong) UIToolbar *toolbar;
@property (nonatomic) BOOL showToolbar;
@property (nonatomic, strong) NSManagedObjectID *selectedObjectID;
@end

#import "CoreDataPickerTF.h"
@implementation CoreDataPickerTF
#define debug 0

#pragma mark - DELEGATE & DATASOURCE: UIPickerView
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return 1;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView
numberOfRowsInComponent:(NSInteger)component {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return [self.pickerData count];
}
- (CGFloat)pickerView:(UIPickerView *)pickerView
rowHeightForComponent:(NSInteger)component {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return 44.0f;
}
- (CGFloat)pickerView:(UIPickerView *)pickerView
    widthForComponent:(NSInteger)component {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return 280.0f;
}
- (NSString *)pickerView:(UIPickerView *)pickerView
             titleForRow:(NSInteger)row
            forComponent:(NSInteger)component {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return [self.pickerData objectAtIndex:row];
}
- (void)pickerView:(UIPickerView *)pickerView
      didSelectRow:(NSInteger)row
       inComponent:(NSInteger)component {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    NSManagedObject *object = [self.pickerData objectAtIndex:row];
    [self.pickerDelegate selectedObjectID:object.objectID
                       changedForPickerTF:self];
}

#pragma mark - INTERACTION
- (void)done {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [self resignFirstResponder];
}
- (void)clear {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [self.pickerDelegate selectedObjectClearedForPickerTF:self];
    [self resignFirstResponder];
}

#pragma mark - DATA
- (void)fetch {
    [NSException raise:NSInternalInconsistencyException format:
     @"You must override the '%@' method to provide data to the picker",
     NSStringFromSelector(_cmd)];
}
- (void)selectDefaultRow {
    [NSException raise:NSInternalInconsistencyException format:
     @"You must override the '%@' method to set the default picker row",
     NSStringFromSelector(_cmd)];
}

#pragma mark - VIEW
- (UIView *)createInputView {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    self.picker = [[UIPickerView alloc] initWithFrame:CGRectZero];
    self.picker.showsSelectionIndicator = YES;
    self.picker.autoresizingMask = UIViewAutoresizingFlexibleHeight;
    self.picker.dataSource = self;
    self.picker.delegate = self;
    [self fetch];
    return self.picker;
}
- (UIView *)createInputAccessoryView {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    self.showToolbar = YES;
    if (!self.toolbar && self.showToolbar) {
        self.toolbar = [[UIToolbar alloc] init];
        self.toolbar.barStyle = UIBarStyleBlackTranslucent;
        self.toolbar.autoresizingMask = UIViewAutoresizingFlexibleHeight;
        [self.toolbar sizeToFit];
        CGRect frame = self.toolbar.frame;
        frame.size.height = 44.0f;
        self.toolbar.frame = frame;
        UIBarButtonItem *clearBtn = [[UIBarButtonItem alloc]
                                     initWithTitle:@"Clear"
                                     style:UIBarButtonItemStyleBordered
                                     target:self
                                     action:@selector(clear)];
        UIBarButtonItem *spacer = [[UIBarButtonItem alloc]
                                   initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
                                   target:nil
                                   action:nil];
        UIBarButtonItem *doneBtn =[[UIBarButtonItem alloc]
                                   initWithBarButtonSystemItem:UIBarButtonSystemItemDone
                                   target:self
                                   action:@selector(done)];
        NSArray *array =
        [NSArray arrayWithObjects:clearBtn, spacer, doneBtn, nil];
        [self.toolbar setItems:array];
    }
    return self.toolbar;
}
-  (id)initWithFrame:(CGRect)aRect {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (self = [super initWithFrame:aRect]) {

        self.inputView = [self createInputView];
        self.inputAccessoryView = [self createInputAccessoryView];
    }
    return self;
}
- (id)initWithCoder:(NSCoder*)aDecoder {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (self = [super initWithCoder:aDecoder]) {
        self.inputView = [self createInputView];
        self.inputAccessoryView = [self createInputAccessoryView];
    }
    return self;
}
- (void)deviceDidRotate:(NSNotification*)notification {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [self.picker setNeedsLayout];
}

@end

UnitPickerTF subclass of CoreDataPickerTF

难点: enumerateObjectsUsingBlock

#import "CoreDataPickerTF.h"

@interface UnitPickerTF : CoreDataPickerTF

- (void)fetch;

@end

#import "UnitPickerTF.h"
#import "CoreDataHelper.h"
#import "AppDelegate.h"
#import "Unit.h"
@implementation UnitPickerTF
#define debug 0
- (void)fetch {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    CoreDataHelper *cdh =
    [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
    NSFetchRequest *request =
    [NSFetchRequest fetchRequestWithEntityName:@"Unit"];
    NSSortDescriptor *sort =
    [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES];
    [request setSortDescriptors:[NSArray arrayWithObject:sort]];
    [request setFetchBatchSize:15];
    NSError *error;
    self.pickerData = [cdh.context executeFetchRequest:request
                                                 error:&error];
    if (error) {
        NSLog(@"Error populating picker: %@, %@"
              , error, error.localizedDescription);}
    [self selectDefaultRow];
}
- (void)selectDefaultRow {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (self.selectedObjectID && [self.pickerData count] > 0) {
        CoreDataHelper *cdh =
        [(AppDelegate *)[[UIApplication sharedApplication] delegate] cdh];
        Unit *selectedObject =
        (Unit*)[cdh.context existingObjectWithID:self.selectedObjectID
                                           error:nil];
        [self.pickerData enumerateObjectsUsingBlock:^(
                                                      Unit *unit, NSUInteger idx, BOOL *stop) {
            if ([unit.name compare:selectedObject.name] == NSOrderedSame) {
                [self.picker selectRow:idx inComponent:0 animated:NO];
                [self.pickerDelegate selectedObjectID:self.selectedObjectID
                                   changedForPickerTF:self];
                *stop = YES;
            }
        }];
    }
}
- (NSString *)pickerView:(UIPickerView *)pickerView
             titleForRow:(NSInteger)row
            forComponent:(NSInteger)component {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    Unit *unit = [self.pickerData objectAtIndex:row];
    return unit.name;
}
@end

If the user modifies items during the import process, there is the potential that the same object could be modified in two contexts at the same time. When one of those contexts is then saved, a merge conflict could result. To handle merge conflicts, a merge policy will be configured in advance. The merge policy decides who wins when these conflicts arise. There are five options:

NSErrorMergePolicy is the default policy. Merge conflicts prevent the context from being saved.

NSMergeByPropertyObjectTrumpMergePolicy resolves merge conflicts using object
property values from the context to overwrite those in the persistent store.
NSMergeByPropertyStoreTrumpMergePolicy resolves merge conflicts using object property values from the persistent store to overwrite those in the context.
NSOverwriteMergePolicy resolves merge conflicts using entire objects from the context to overwrite those in the persistent store.
NSRollbackMergePolicy resolves merge conflicts using entire objects from the persistent store to overwrite those in the context.

CoreData: Relationship 中的Delete Rules

下面是详细说明,简单点说,假如有一个relationship: Photo and Photographer如下图:

relateionship

 

当photos设置:nullify 时, 删除一个photographer, 则对应的photos 会设置他的所有者为nil;

Cascade则会删除对应的所有photos;

The Nullify delete rule is a good default for most situations. When an object is deleted and this rule is in place, related objects nil out their relationship to the deleted object. For example, assume a unit object named Kg is related to some item objects. If a Nullify delete rule was set on the items relationship and the Kg unit object was deleted, the related item objects would set their unit property to nil.

■ The Cascade delete rule propagates deletions through the relationship. For example, assume a unit object named Kg is related to some item objects. If a Cascade delete rule was set on the items relationship and the Kg unit object was deleted, all the related item objects would be deleted, too.

■ The Deny delete rule prevents the deletion of an object if it still has related objects. For example, assume a unit object named Kg is related to some item objects. If a Deny delete rule was set on the items relationship and the Kg unit object was deleted, the existence of related item objects would cause a validation error when the context was saved. When you use a Deny delete rule, you need to ensure that there are no related objects at the relationship destination before deleting the source object.

■ The No Action delete rule is a strange one that will actually leave your object graph in an inconsistent state. If you use this delete rule, it is up to you to manually set the inverse relationship to something valid. Only corner-case situations call for this delete rule.

用代码创建一个CoreData

下图描绘了被管理对象上下文(Managed Object Context)包含2个被管理对象(Managed Object),对应着数据库中2条记录。其中一个对象的属性值已经在内存中更新过了,但还没有提交到数据库保存。另外,还有2条记录在数据库中,并没有对应的被管理对象(Managed Object)。
Managed-Object-Context

一个实体描述对象(Entity Description Object)描述了数据库中一个数据表实体,包括表名称、表对应的被管理对象的名称、属性(Attributes)和关系(Relationships)等等。

下图描绘了实体描述对象、数据库中数据表和被管理对象(对应数据库表的一条记录)之间的关系。
Managed-Object-Model

从上图可以看出,每一个被管理对象(Managed Object)都有对应实体对象的引用。
Core Data使用模型(Model)来映射应用程序中被管理对象和数据库中的数据记录。需要注意的是,如果我们修改数据库的Schema,Core Data将无法使用之前创建的模型来读取数据库记录了。很多持久化机制都存在这个问题。

advanced_persistent_stack

NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask] lastObject];
NSURL *storeDatabaseURL = [url URLByAppendingPathComponent:@"ShoppingCart Database"];
// url is now /ShoppingCart Database
NSError *error = nil;
NSPersistentStoreCoordinator *persistentStoreCoordinator =
[[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:[NSManagedObjectModel mergedModelFromBundles:nil]];
if(![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeDatabaseURL options:nil error:&error])
{
NSLog(@"Error while loading persistent store ...%@", error);
}

_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:persistentStoreCoordinator];

相关文章: core data创建shopping app

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

@interface CoreDataHelper :NSObject

@property (nonatomic, readonly) NSManagedObjectContext       *context;
@property (nonatomic, readonly) NSManagedObjectModel         *model;
@property (nonatomic, readonly) NSPersistentStoreCoordinator *coordinator;
@property (nonatomic, readonly) NSPersistentStore            *store;

- (void)setupCoreData;
- (void)saveContext;
@end

CoreDataHelper.m

#import "CoreDataHelper.h"

@implementation CoreDataHelper
#define debug 1

#pragma mark - FILES
NSString *storeFilename = @"Grocery-Dude.sqlite";

#pragma mark - PATHS
- (NSString *)applicationDocumentsDirectory {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class,NSStringFromSelector(_cmd));
    }
    return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES) lastObject];
}
- (NSURL *)applicationStoresDirectory {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }

    NSURL *storesDirectory =
    [[NSURL fileURLWithPath:[self applicationDocumentsDirectory]]
     URLByAppendingPathComponent:@"Stores"];

    NSFileManager *fileManager = [NSFileManager defaultManager];
    if (![fileManager fileExistsAtPath:[storesDirectory path]]) {
        NSError *error = nil;
        if ([fileManager createDirectoryAtURL:storesDirectory
                  withIntermediateDirectories:YES
                                   attributes:nil
                                        error:&error]) {
            if (debug==1) {
                NSLog(@"Successfully created Stores directory");}
        }
        else {NSLog(@"FAILED to create Stores directory: %@", error);}
    }
    return storesDirectory;
}
- (NSURL *)storeURL {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    return [[self applicationStoresDirectory]
            URLByAppendingPathComponent:storeFilename];
}

#pragma mark - SETUP
- (id)init {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    self = [super init];
    if (!self) {return nil;}

    _model = [NSManagedObjectModel mergedModelFromBundles:nil];
    _coordinator = [[NSPersistentStoreCoordinator alloc]
                    initWithManagedObjectModel:_model];
    _context = [[NSManagedObjectContext alloc]
                initWithConcurrencyType:NSMainQueueConcurrencyType];
    [_context setPersistentStoreCoordinator:_coordinator];
    return self;
}
- (void)loadStore {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (_store) {return;} // Don’t load store if it's already loaded

    NSDictionary *options =
  @{
    NSMigratePersistentStoresAutomaticallyOption:@YES ,NSInferMappingModelAutomaticallyOption:@NO ,NSSQLitePragmasOption: @{@"journal_mode": @"DELETE"}
    };

    NSError *error = nil;
    _store = [_coordinator addPersistentStoreWithType:NSSQLiteStoreType
                                        configuration:nil
                                                  URL:[self storeURL]
                                              options:options error:&error];
    if (!_store) {NSLog(@"Failed to add store. Error: %@", error);abort();}
    else         {if (debug==1) {NSLog(@"Successfully added store: %@", _store);}}
}
- (void)setupCoreData {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    [self loadStore];
}

#pragma mark - SAVING
- (void)saveContext {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if ([_context hasChanges]) {
        NSError *error = nil;
        if ([_context save:&error]) {
            NSLog(@"_context SAVED changes to persistent store");
        } else {
            NSLog(@"Failed to save _context: %@", error);
        }
    } else {
        NSLog(@"SKIPPED _context save, there are no changes!");
    }
}

@end

#import "AppDelegate.h"
#import "Item.h"
#import "Amount.h"

@implementation AppDelegate
#define debug 1

- (void)demo {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    /*
    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); }

     for (int i = 1; i < 5000; i++) {      Meansure *newMeasurement =      [NSEntityDescription insertNewObjectForEntityForName:@"Meansure"      inManagedObjectContext:_coreDataHelper.context];      newMeasurement.abc =      [NSString stringWithFormat:@"-->> LOTS OF TEST DATA x%i",i]; NSLog(@"Inserted %@",newMeasurement.abc);
     }
     [_coreDataHelper saveContext];
   */

    NSFetchRequest *request =
    [NSFetchRequest fetchRequestWithEntityName:@"Amount"]; [request setFetchLimit:50];
    NSError *error = nil;
    NSArray *fetchedObjects =
    [_coreDataHelper.context executeFetchRequest:request error:&error];
    if (error) {NSLog(@"%@", error);} else {
        for (Amount *measurement in fetchedObjects) { NSLog(@"Fetched Object = %@", measurement.xyz);
        } }

}

- (CoreDataHelper*)cdh {
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    if (!_coreDataHelper) {
        _coreDataHelper = [CoreDataHelper new];
        [_coreDataHelper setupCoreData];
    }
    return _coreDataHelper;

}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    // Override point for customization after application launch.
    return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application
{
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    [[self cdh] saveContext];
}

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.

    [self cdh];
    [self demo];
}

- (void)applicationWillTerminate:(UIApplication *)application
{
    if (debug==1) {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
    }
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    [[self cdh] saveContext];
}

@end

IOS7-CoreDAta系列 fetch Request Template ForName

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.

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];
Fetching Managed Objects 41

42
Chapter 2 Managed Object Model Basics
for (Item *item in fetchedObjects) { NSLog(@"Fetched Object = %@", item.name);
}

IOS7-CoreDAta系列:Attribute相关设置

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.

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.

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

用Binary Data时:

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).

Index in Spotlight doesn’t do anything for an iOS application. It is used to integrate a Core Data–based Mac application with Spotlight

Store in External Record File duplicates data from the persistent store into an XML representation outside the store.

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:
■ A Date attribute becomes an NSTimeInterval.
■ A Double attribute becomes a double.
■ A Float attribute becomes a float.
■ An Integer 16/32/64 attribute becomes an int16_t, int32_t, or int64_t, respectively.
■ A Boolean attribute becomes a BOOL.