I am getting data from the server and converts them to an array of NSManagedObject objects.
The array is used to display the table.
How to insert first element array peoples in persistent store?
- (void)viewDidLoad
{
[self loadData];
[self insertFirstPeople];
}
- (NSManagedObjectContext *)managedObjectContext
{
if(!_managedObjectContext) _managedObjectContext = [NSManagedObjectContext MR_context];
return _managedObjectContext;
}
- (void)loadData
{
...
Network Request
...
peoples = [NSMutableArray array];
for (NSDictionary *item in items)
{
People *people = [Podcast MR_createInContext:self.managedObjectContext];
people.name = [item valueForKeyPath:#"im:name.label"];
[peoples addObject:people];
}
}
-(void)insertFirstPeople
{
People *people = peoples[0];
NSManagedObjectContext *moc = [NSManagedObjectContext MR_defaultContext];
[moc insertObject:people]
[moc MR_saveToPersistentStoreAndWait];
}
Error:
An NSManagedObject may only be in (or observed by) a single NSManagedObjectContext.
I myself have found a solution to the problem.
-(void)insertFirstPeople
{
People *people = peoples[0];
CoreDataHelper *helper = [[CoreDataHelper alloc] init];
NSManagerObjectContext *context = [NSManagedObjectContext MR_defaultContext];
[helper saveObject:people toContext:context];
[context MR_saveOnlySelfAndWait];
}
CoreDataHelper.h
#import <Foundation/Foundation.h>
#interface CoreDataHelper : NSObject
{
NSMutableDictionary* _lookup;
}
#property(nonatomic, retain) NSMutableDictionary *lookup;
-(void)saveFrom:(NSManagedObjectContext *)current to:(NSManagedObjectContext *)parent;
- (NSManagedObject *)saveObject:(NSManagedObject *)object toContext:(NSManagedObjectContext *)moc;
- (NSManagedObject*)copyObject:(NSManagedObject*)object
toContext:(NSManagedObjectContext*)moc
parent:(NSString*)parentEntity;
#end
CoreDataHelper.m
#import "CoreDataHelper.h"
#implementation CoreDataHelper
#synthesize lookup = _lookup;
-(void)saveFrom:(NSManagedObjectContext *)current to:(NSManagedObjectContext *)parent
{
NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
[dnc addObserverForName:NSManagedObjectContextDidSaveNotification
object:current queue:nil
usingBlock:^(NSNotification *notification)
{
[parent mergeChangesFromContextDidSaveNotification:notification];
}];
NSError *error;
if (![current save:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
[dnc removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:current];
}
- (NSManagedObject *)saveObject:(NSManagedObject *)object toContext:(NSManagedObjectContext *)moc {
NSUndoManager *docUndoMgr = [moc undoManager];
[docUndoMgr beginUndoGrouping];
NSManagedObject *object2 = [self copyObject:object toContext:moc parent:nil];
[moc processPendingChanges];
[docUndoMgr endUndoGrouping];
return object2;
}
- (NSManagedObject *)copyObject:(NSManagedObject *)object
toContext:(NSManagedObjectContext *)moc
parent:(NSString *)parentEntity; {
NSError *error = nil;
NSString *entityName = [[object entity] name];
NSManagedObject *newObject = nil;
if ([moc objectRegisteredForID:object.objectID])
newObject = [moc objectWithID:object.objectID];
else
newObject = [NSEntityDescription
insertNewObjectForEntityForName:entityName
inManagedObjectContext:moc];
if (![moc save:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
[[self lookup] setObject:newObject forKey:[object objectID]];
NSArray *attKeys = [[[object entity] attributesByName] allKeys];
NSDictionary *attributes = [object dictionaryWithValuesForKeys:attKeys];
[newObject setValuesForKeysWithDictionary:attributes];
id oldDestObject = nil;
id temp = nil;
NSDictionary *relationships = [[object entity] relationshipsByName];
for (NSString *key in [relationships allKeys]) {
NSRelationshipDescription *desc = [relationships valueForKey:key];
NSString *destEntityName = [[desc destinationEntity] name];
if ([destEntityName isEqualToString:parentEntity]) continue;
if ([desc isToMany]) {
NSMutableSet *newDestSet = [NSMutableSet set];
for (oldDestObject in [object valueForKey:key]) {
temp = [[self lookup] objectForKey:[oldDestObject objectID]];
if (!temp) {
temp = [self copyObject:oldDestObject
toContext:moc
parent:entityName];
}
[newDestSet addObject:temp];
}
[newObject setValue:newDestSet forKey:key];
} else {
oldDestObject = [object valueForKey:key];
if (!oldDestObject) continue;
temp = [[self lookup] objectForKey:[oldDestObject objectID]];
if (!temp && ![destEntityName isEqualToString:parentEntity]) {
temp = [self copyObject:oldDestObject
toContext:moc
parent:entityName];
}
[newObject setValue:temp forKey:key];
}
}
return newObject;
}
#end
Related
I'm using RestKit and RRNCollapsableTable. The problem is, when I load view for the first time, RestKit is downloading data from JSON. That delay causes menu to not load. What I'm trying to do is to make CollapsableTable wait for data.
[self requestData:^(BOOL success) {
if (success) {
_menu = [self buildMenu];
[self model];
}
}];
- (void)requestData:(void (^)(BOOL success))completionBlock {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Messages"];
NSSortDescriptor *byId = [NSSortDescriptor sortDescriptorWithKey:#"customNewsId" ascending:YES];
fetchRequest.sortDescriptors = #[byId];
NSError *error = nil;
// Setup fetched results
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[RKManagedObjectStore defaultStore].mainQueueManagedObjectContext
sectionNameKeyPath:#"customNewsId"
cacheName:nil];
//[self.fetchedResultsController setDelegate:self];
BOOL fetchSuccessful = [self.fetchedResultsController performFetch:&error];
if (!fetchSuccessful) {
abort();
}
[[RKObjectManager sharedManager] getObjectsAtPath:#"api/json/get/bZmroLaBCG" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
RKLogInfo(#"Load complete: Table should refresh...");
//[[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:#"LastUpdatedAt"];
//[[NSUserDefaults standardUserDefaults] synchronize];
NSError *error = nil;
if ([[RKManagedObjectStore defaultStore].mainQueueManagedObjectContext hasChanges] && ![[RKManagedObjectStore defaultStore].mainQueueManagedObjectContext saveToPersistentStore:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
completionBlock(YES);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
RKLogError(#"Load failed with error: %#", error);
completionBlock(NO);
}];
completionBlock(YES);
}
Then BuildMenu is just iterating over fetched objects and put them in section.
-(NSArray *)buildMenu {
__block NSMutableArray *collector = [NSMutableArray new];
NSInteger sections = [self.fetchedResultsController.sections count];
for (NSInteger i = 0; i < sections; i++) {
Messages *message = [_fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:i]];
MenuSection *section = [MenuSection new];
section.items = #[message.message,message.pushNotificationMessage];
section.title = message.title;
NSLog(#"section.title - %#",section.title);
[collector addObject:section];
}
return [collector copy];
}
Method model is responsible for DataSource for CollapsableTable.
-(NSArray *)model {
return _menu;
}
Thanks in advance for any help.
Greetings.
I am implementing CoreData stack according to
https://stackoverflow.com/a/24663533 (option A from image) but it works in an unexpected way.
I have rootContext (NSPrivateQueueConcurrencyType), it has 2 children: uiContext (NSMainQueueConcurrencyType) for objects fetching and syncContext (NSPrivateQueueConcurrencyType) for asynchronous data editing.
As I thought, when I save something in syncContext in performBlock (background queue), changes will be propagated to rootContext, but uiContext will not be changed until I observe NSManagedObjectContextDidSaveNotification and merge changes from notification. But changes are reflected immediately after syncContext save.
My first question is: why is uiContext updated without manual merge?
My second question: why is rootContext modified on background (not on main thread) after syncContext save? Some time ago I asked question about "CoreData could not fulfill a fault" problem with MagicalRecord 'CoreData could not fulfill a fault' error with MagicalRecord but I didn't receive answer, so I decided to find solution without external libraries.
It seems, that main thread is reading object properties and the same object is deleted on background whilst operators on main thread still don't return control.
Here is my source code:
#import <CoreData/CoreData.h>
#import "DataLayer.h"
#import "Person.h"
#interface DataLayer ()
#property (nonatomic, strong) NSManagedObjectModel *model;
#property (nonatomic, strong) NSPersistentStoreCoordinator *coordinator;
#property (nonatomic, strong) NSManagedObjectContext *rootContext;
#property (nonatomic, strong) NSManagedObjectContext *uiContext;
#property (nonatomic, strong) NSManagedObjectContext *syncContext;
#end
#implementation DataLayer
+ (void)load
{
[self instance];
}
+ (DataLayer *)instance
{
static DataLayer *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[DataLayer alloc] init];
});
return instance;
}
- (instancetype)init
{
self = [super init];
if (self) {
[self initModel];
[self initCoordinator];
[self initContexts];
[self observeContextSaveNotification];
[self startTesting];
}
return self;
}
- (void)initModel
{
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"Model" withExtension:#"momd"];
self.model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
}
- (void)initCoordinator
{
NSURL *directory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *storeURL = [directory URLByAppendingPathComponent:#"Model.sqlite"];
NSError *error = nil;
self.coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.model];
if (![self.coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
- (void)initContexts
{
self.rootContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
self.rootContext.persistentStoreCoordinator = self.coordinator;
self.uiContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.uiContext.parentContext = self.rootContext;
self.syncContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
self.syncContext.parentContext = self.rootContext;
}
- (void)observeContextSaveNotification
{
// [[NSNotificationCenter defaultCenter] addObserver:self
// selector:#selector(onManagedObjectContextDidSaveNotification:)
// name:NSManagedObjectContextDidSaveNotification
// object:nil];
}
- (void)onManagedObjectContextDidSaveNotification:(NSNotification *)notification
{
// NSManagedObjectContext *context = notification.object;
// if (context != self.uiContext) {
// [self.uiContext mergeChangesFromContextDidSaveNotification:notification];
// }
}
- (void)startTesting
{
NSArray *personsBeforeSave = [self fetchEntities:#"Person" fromContext:self.uiContext];
NSLog(#"Before save: %i persons in syncContext", [personsBeforeSave count]); // Before save: 0 persons in syncContext
[self.syncContext performBlock:^{
Person *person = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:self.syncContext];
person.firstName = #"Alexander";
NSError *error = nil;
[self.syncContext save:&error];
if (error) {
NSLog(#"Error during save: %#", error);
}
NSArray *personsAfterSaveFromBackground = [self fetchEntities:#"Person" fromContext:self.rootContext];
NSLog(#"After save from background: %i persons in rootContext", [personsAfterSaveFromBackground count]); // After save from background: 1 persons in rootContext
dispatch_async(dispatch_get_main_queue(), ^{
NSArray *personsAfterSaveFromMain = [self fetchEntities:#"Person" fromContext:self.uiContext];
NSLog(#"After save from main: %i persons in uiContext", [personsAfterSaveFromMain count]); // After save from main: 1 persons in uiContext
});
}];
}
- (NSArray *)fetchEntities:(NSString *)entity fromContext:(NSManagedObjectContext *)context
{
NSError *error = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entity];
NSArray *result = [context executeFetchRequest:request error:&error];
if (error) {
NSLog(#"Error during fetch %#: %#", entity, error);
return nil;
}
return result;
}
#end
They are not being merged into the UI context. You are manually fetching them.
When you save in the syncContext, the data gets pushed up into the rootContext. The data is NOT merged into the uiContext. However, when you perform the fetch, the fetch pulls data down from the parent context.
You can get the objects in a context with registeredObjects.
I have a routine that fetches RSS entries in the background and insert these in my NSManagedObjectContext if not already there.
My problem is that this object doesn't find duplicates or crashes, depending on which NSManagedObjectContext I use... Help me, please.
Here's the simplified .h
#interface AsyncFetchEngine : NSObject <NSXMLParserDelegate,NSFetchedResultsControllerDelegate>
#property (strong, nonatomic) dispatch_queue_t rssParserQueue;
#property (strong, nonatomic) NSMutableArray *uRLsToFetch;
#property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
#property (strong, nonatomic) NSManagedObjectContext *childManagedObjectContext;
#property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
- (Boolean) isAlreadyInTheFetchQueue:(Feed *)feed;
- (void) fetchPosts:(Feed *)feed;
- (void) createPostInFeed:(Feed*)feed withTitle:(NSString *)title withContent:(NSString *)content withURL:(NSString *)url withDate:(NSDate *)date;
Now here's the init method:
Note if I set the _childManagedObjectContext's parent here, the program crashes.
-(AsyncFetchEngine *)init
{
_rssParserQueue = dispatch_queue_create("com.example.MyQueue", NULL);
_uRLsToFetch = [[NSMutableArray alloc] initWithCapacity:32];
_childManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_childManagedObjectContext setPersistentStoreCoordinator:[_managedObjectContext persistentStoreCoordinator]];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:#selector(contextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:_childManagedObjectContext];
return self;
}
- (void)contextDidSave:(NSNotification*)notification
{
void (^mergeChanges) (void) = ^ {
[_managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
};
if ([NSThread isMainThread]) {
mergeChanges();
} else {
dispatch_sync(dispatch_get_main_queue(), mergeChanges);
}
}
Fetch method:
Note: not sure which MOC to use to determine localFeed, _managedObjectContext crashes the app.
- (void) fetchPosts:(Feed *)feed
{
if (!_childManagedObjectContext.parentContext) {
[_childManagedObjectContext setParentContext:self.managedObjectContext];
}
if ([self isAlreadyInTheFetchQueue:feed]) {
NSLog(#"AsyncFetchEngine::fetchPosts> \"%#\" is already in the fetch queue", feed.name);
return;
}
[_uRLsToFetch addObject:feed];
NSURL *url=[NSURL URLWithString:feed.rss];
NSURLRequest *req = [[NSURLRequest alloc] initWithURL:url];
if (![NSURLConnection canHandleRequest:req]) {
return;
}
Feed *localFeed = ((Feed *)[_childManagedObjectContext existingObjectWithID:[feed objectID] error:nil]);
dispatch_async(_rssParserQueue, ^{
NSLog(#"AsyncFetchEngine::fetchPosts> Opening %#", feed.rss);
[RSSParser parseRSSFeedForRequest:req success:^(NSArray *feedItems)
{
for(RSSItem *i in feedItems)
{
[self createPostInFeed:localFeed withTitle:i.title withContent:(i.content?i.content:i.itemDescription) withURL:[i.link absoluteString] withDate:(i.pubDate?i.pubDate:[NSDate date])];
}
NSLog(#"AsyncFetchEngine::fetchPosts> Found %d items", [feedItems count]);
[_uRLsToFetch removeObject:feed];
}
failure:^(NSError *error)
{
NSLog(#"AsyncFetchEngine::fetchPosts> RSSParser lost it: %#", [error localizedDescription]);
}];
});
}
- (Boolean) isAlreadyInTheFetchQueue:(Feed *)feed
{
Feed *f=nil;
for (f in _uRLsToFetch) {
if ([f isEqual:feed]){
return YES;
}
}
return NO;
}
NSFecthedResultsController, do I need it that way?
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
// Note: I originally tried to use the main MOC here, but it also used to crash the app.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Post" inManagedObjectContext:_childManagedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:NO];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
/* NSPredicate *predicate =[NSPredicate predicateWithFormat:#"feed.rss LIKE '%#'", _detailItem.rss];
[fetchRequest setPredicate:predicate]; */
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
aFetchedResultsController.delegate = self;
_fetchedResultsController = aFetchedResultsController;
return _fetchedResultsController;
}
It usually crashes somewhere in this method, according to the dump stack.
If it doesn't crash, I have my post added to a (null) Category...
- (void)createPostInFeed:(Feed*)feed withTitle:(NSString *)title withContent:(NSString *)content withURL:(NSString *)url withDate:(NSDate *)date
{
[_childManagedObjectContext performBlockAndWait:^{
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Error in refetch: %#",[error localizedDescription]);
abort();
}
}];
NSLog(#"AsyncFetchEngine.h: Searching similar posts among %d", [[self.fetchedResultsController fetchedObjects] count]);
Boolean found=NO;
NSPredicate *predicate=[NSPredicate predicateWithFormat:#"title == %# AND url == %# AND feed == %#", title, url, feed];
NSArray *similarPosts = [_fetchedResultsController.fetchedObjects filteredArrayUsingPredicate:predicate];
if ([similarPosts count] > 0)
{
NSLog(#"\n\n\n\t\tAsyncFetchEngine::fetchPosts> Skipping %# (%#)", title, url);
} else {
NSLog(#"\n\n\n\t\tAsyncFetchEngine::fetchPosts> Putting new post in %#", feed.name);
NSEntityDescription *postEntityDescription = [NSEntityDescription entityForName:#"Post"
inManagedObjectContext:_childManagedObjectContext];
[_childManagedObjectContext performBlock:^{
Post *initPost = (Post *)[[NSManagedObject alloc]
initWithEntity:postEntityDescription
insertIntoManagedObjectContext:_childManagedObjectContext];
initPost.title = title;
initPost.url = url;
initPost.excerpt = content;
initPost.date = date;
initPost.read = nil;
initPost.feed = feed;
NSError *error;
if (![_childManagedObjectContext save:&error])
{
NSLog(#"[createPost] Error saving context: %#", error);
}
NSLog(#"Created: %# (%#)", title, url);
//[[NSNotificationCenter defaultCenter] postNotificationName:#"NewPostAdded" object:self];
}];
}
}
So, my questions are:
When I have multiple MOCs, do I fetch from the main and write to a child?
How should I respectivelly use these above?
Thanks!
Answering my own question for the sake of eventually helping another newbie.
Check NSOperationQueue with Coredata.
There's a very good example here.
I'm having trouble dealing with object IDs in CoreData. I'm using MagicalRecord for convenience and have 3 contexts: a private queue working context, a main queue context for UI and parent to the working context, and a private queue saving context that is the parent of the main context.
My goal is to create an object in the working context, save to the persistent store, save it's objectID URL to NSUserDefaults, and then be able to pull out that MO using the objectID later on. However, what I'm finding is that after saving the permanent ID of the object is changing.
In the console output below you can see that after I request the permanent ID the value I get back is "F474F6EE-A225-456B-92EF-AB1407336F15/CDBaseAccount/p1" but later when I list all the objects in CD the only object there has the ID "F474F6EE-A225-456B-92EF-AB1407336F15/CDBaseAccount/p2". p1 vs p2, what happened?
Code:
NSManagedObjectContext *c = [NSManagedObjectContext MR_contextThatPushesChangesToDefaultContext];
[c performBlockAndWait:^{
NSArray *all = [CDBaseAccount MR_findAllInContext:c];
NSLog(#"count: %d", all.count);
NSLog(#"all accounts = %#", all);
CDBaseAccount *a = [CDBaseAccount MR_createInContext:c];
a.accountName = #"foo";
[c MR_saveNestedContexts];
NSLog(#"temp a.objectID = %#", a.objectID);
NSError *error;
if (![c obtainPermanentIDsForObjects:#[a] error:&error]) {
NSLog(#"perm id error: %#", error);
return;
}
NSLog(#"perm a.objectID = %#", a.objectID);
NSURL *u = a.objectID.URIRepresentation;
dispatch_async(dispatch_get_main_queue(), ^{
NSManagedObjectContext *d = [NSManagedObjectContext MR_defaultContext];
NSArray *all = [CDBaseAccount MR_findAllInContext:d];
NSLog(#"count: %d", all.count);
NSLog(#"all accounts = %#", all);
NSManagedObjectID *i = [d.persistentStoreCoordinator managedObjectIDForURIRepresentation:u];
NSError *objWithIdError = nil;
NSManagedObject *o = [d existingObjectWithID:i error:&objWithIdError];
if (objWithIdError != nil) {
NSLog(#"existing object error: %#", objWithIdError);
return;
}
NSLog(#"o = %#", o);
NSLog(#"o.objectID = %#", o.objectID);
});
}];
Console output:
> +[NSManagedObjectContext(MagicalRecord) MR_contextWithStoreCoordinator:](0xa7c9b0) -> Created <NSManagedObjectContext: 0x83522a0>: Context *** MAIN THREAD ***
> count: 0
> all accounts = (
> )
> -[NSManagedObjectContext(MagicalSaves) MR_saveWithErrorCallback:](0x8353de0) -> Saving <NSManagedObjectContext: 0x8353de0>: Context *** MAIN THREAD ***
> -[NSManagedObjectContext(MagicalSaves) MR_saveWithErrorCallback:](0x8195450) -> Saving <NSManagedObjectContext: 0x8195450>: *** DEFAULT *** Context *** MAIN THREAD ***
> -[NSManagedObjectContext(MagicalSaves) MR_saveWithErrorCallback:](0x83522a0) -> Saving <NSManagedObjectContext: 0x83522a0>: *** BACKGROUND SAVE *** Context *** MAIN THREAD ***
> temp a.objectID = 0x8187ee0 <x-coredata:///CDBaseAccount/tF392AC6A-3539-4F39-AC53-35F9E5B3C9322>
> perm a.objectID = 0x8355800 <x-coredata://F474F6EE-A225-456B-92EF-AB1407336F15/CDBaseAccount/p2>
> count: 1
> all accounts = (
"<CDBaseAccount: 0x844ca60> (entity: CDBaseAccount; id: 0x844a4c0 <x-coredata://F474F6EE-A225-456B-92EF-AB1407336F15/CDBaseAccount/p1> ; data: <fault>)"
)
> existing object error: Error Domain=NSCocoaErrorDomain Code=133000 "The operation couldn’t be completed. (Cocoa error 133000.)" UserInfo=0x864d8c0 {NSAffectedObjectsErrorKey=(
"<CDBaseAccount: 0x864b8c0> (entity: CDBaseAccount; id: 0x86405c0 <x-coredata://F474F6EE-A225-456B-92EF-AB1407336F15/CDBaseAccount/p2> ; data: <fault>)"
)}
Short answer is, don't do that :)
-objectID is not reliable between launches of your application. It is guaranteed to be unique and reliable under the following conditions:
Within a single lifecycle of the application
In its original object form (not in URL or NSString form)
Treating the -objectID as a permanent unique identifier to be stored outside of the persistent store is going to fail you fairly often. Core Data changes the underlying details of the -objectID many times during the life of the object.
If you need an externally reliable unique then you need to create one yourself. I generally recommend adding a [[NSProcessInfo processInfo] globallyUniqueString] to any entity that needs an externally reference-able unique. -awakeFromInsert is a great place to do that.
This may be because you're using nested context.
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
static NSArray *fetchAllPersons(NSManagedObjectContext *moc);
static NSManagedObjectModel *managedObjectModel();
static NSManagedObjectContext *createManagedObjectContext();
static NSURL *desktopDirectoryURL(void);
static void testObjectID(void);
int main(int argc, const char * argv[])
{
#autoreleasepool {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
testObjectID();
});
}
dispatch_main();
return 0;
}
static void testObjectID(void)
{
NSManagedObjectContext *c = createManagedObjectContext();
[c performBlock:^{
NSArray *all = fetchAllPersons(c);
NSLog(#"count: %lu", all.count);
NSLog(#"all accounts = %#", all);
NSManagedObject *a = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:c];
[a setValue:#"foo" forKey:#"name"];
NSLog(#"temp a.objectID = %#", a.objectID);
NSError *error = nil;
NSCAssert([c obtainPermanentIDsForObjects:#[a] error:&error], #"perm id error: %#", error);
NSLog(#"perm a.objectID = %#", a.objectID);
NSCAssert([c save:&error], #"Save failed: %#", error);
NSURL *u = a.objectID.URIRepresentation;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSManagedObjectContext *d = createManagedObjectContext();
NSArray *all = fetchAllPersons(c);
NSLog(#"count: %lu", all.count);
NSLog(#"all accounts = %#", all);
NSManagedObjectID *i = [d.persistentStoreCoordinator managedObjectIDForURIRepresentation:u];
NSError *objWithIdError = nil;
NSManagedObject *o = [d existingObjectWithID:i error:&objWithIdError];
NSCAssert(o != nil, #"existing object error: %#", objWithIdError);
NSLog(#"o = %#", o);
NSLog(#"o.objectID = %#", o.objectID);
});
}];
}
static NSArray *fetchAllPersons(NSManagedObjectContext *moc)
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Person"];
NSError *error = nil;
NSArray *result = [moc executeFetchRequest:request error:&error];
NSCAssert(result != nil, #"Fetch failed: %#", error);
return result;
}
static NSManagedObjectModel *managedObjectModel()
{
static NSManagedObjectModel *mom = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSEntityDescription *personEntity = [[NSEntityDescription alloc] init];
[personEntity setName:#"Person"];
NSAttributeDescription *nameAttribute = [[NSAttributeDescription alloc] init];
[nameAttribute setName:#"name"];
[nameAttribute setAttributeType:NSStringAttributeType];
[personEntity setProperties:#[nameAttribute]];
mom = [[NSManagedObjectModel alloc] init];
[mom setEntities:#[personEntity]];
});
return mom;
}
static NSManagedObjectContext *createManagedObjectContext()
{
static NSPersistentStoreCoordinator *coordinator;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: managedObjectModel()];
NSString *STORE_TYPE = NSSQLiteStoreType;
NSString *STORE_FILENAME = #"foobar.db";
NSError *error;
NSURL *url = [desktopDirectoryURL() URLByAppendingPathComponent:STORE_FILENAME];
NSPersistentStore *newStore = [coordinator addPersistentStoreWithType:STORE_TYPE
configuration:nil URL:url options:nil
error:&error];
if (newStore == nil) {
NSLog(#"Store Configuration Failure\n%#",
([error localizedDescription] != nil) ?
[error localizedDescription] : #"Unknown Error");
}
});
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[moc setPersistentStoreCoordinator:coordinator];
return moc;
}
static NSURL *desktopDirectoryURL(void)
{
static NSURL *URL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSError *error;
URL = [fileManager URLForDirectory:NSDesktopDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&error];
NSCAssert(URL != nil, #"Could not access Desktop directory: %#", [error localizedDescription]);
});
return URL;
}
Outputs:
count: 0
all accounts = (
)
temp a.objectID = 0x10180e640 <x-coredata:///Person/tB1D48677-0152-4DA9-8573-7C7532863B4E2>
perm a.objectID = 0x101901bb0 <x-coredata://275C90E5-2598-4DFA-BF4C-E60A336E8BE4/Person/p1>
count: 1
all accounts = (
"<NSManagedObject: 0x10180e5b0> (entity: Person; id: 0x101901bb0 <x-coredata://275C90E5-2598-4DFA-BF4C-E60A336E8BE4/Person/p1> ; data: {\n name = foo;\n})"
)
o = <NSManagedObject: 0x100416530> (entity: Person; id: 0x100415b60 <x-coredata://275C90E5-2598-4DFA-BF4C-E60A336E8BE4/Person/p1> ; data: {
name = foo;
})
o.objectID = 0x100415b60 <x-coredata://275C90E5-2598-4DFA-BF4C-E60A336E8BE4/Person/p1>
Ok guys. This one is driving me up the wall. I have
UIManagedDocument and its 2 MOContexts (regular and parent.)
A UITableViewController (subclassed to CoreDataTableViewController by Paul Hegarty) that runs off of an
NSFetchedResultsController
A background GCD Queue for syncing with the server that the parent cue accesses
I've tried this so many different ways and I run into problems each time.
When I add a new "animal" entity, it is no problem and immediately shows up on the table. But when I upload it to the server (on the upload queue) and changed its "status" (with the parent context) so that it should be in the uploaded section, it appears there but doesn't disappear from the un-uploaded section.
I END UP WITH TWINS I DIDN'T WANT! or it doesn't even make the correct one sometimes and just keeps the wrong one.
***BUT, the extra one will disappear when the app is shut down and reloaded. So it's just in memory somewhere. I can verify in the store that everything is correct. But the NSFetchedResultsController isn't firing the controllerDidChange... stuff.
Here is the superclass of my view controller
CoreDataTableViewController.m
#pragma mark - Fetching
- (void)performFetch
{
self.debug = 1;
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;
[self.fetchedResultsController performFetch:&error];
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
{
if (self.debug) NSLog(#"fetchedResultsController returns %d sections", [[self.fetchedResultsController sections] count]);
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
}
- (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
{
if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
{
[self.tableView beginUpdates];
self.beganUpdates = YES;
}
}
- (void)controller:(NSFetchedResultsController *)controller
didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex
forChangeType:(NSFetchedResultsChangeType)type
{
if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
{
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
{
if(self.debug) NSLog(#"controller didChangeObject: %#", anObject);
if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
{
NSLog(#"#########Controller did change type: %d", type);
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
{
if (self.beganUpdates) [self.tableView endUpdates];
if (self.debug) NSLog(#"controller Did Change Content");
}
- (void)endSuspensionOfUpdatesDueToContextChanges
{
_suspendAutomaticTrackingOfChangesInManagedObjectContext = NO;
}
- (void)setSuspendAutomaticTrackingOfChangesInManagedObjectContext:(BOOL)suspend
{
if (suspend) {
_suspendAutomaticTrackingOfChangesInManagedObjectContext = YES;
} else {
[self performSelector:#selector(endSuspensionOfUpdatesDueToContextChanges) withObject:0 afterDelay:0];
}
}
#end
And here's my specific view controller I subclassed from it:
- (NSArray *)sectionHeaderTitles
{
if (_sectionHeaderTitles == nil) _sectionHeaderTitles = [NSArray arrayWithObjects:#"Not Yet Uploaded", #"Uploaded But Not Featured", #"Previously Featured", nil];
return _sectionHeaderTitles;
}
- (NSDictionary *)selectedEntry
{
if (_selectedEntry == nil) _selectedEntry = [[NSDictionary alloc] init];
return _selectedEntry;
}
- (void)setupFetchedResultsController
{
[self.photoDatabase.managedObjectContext setStalenessInterval:0.0];
[self.photoDatabase.managedObjectContext.parentContext setStalenessInterval:0.0];
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Animal"];
request.sortDescriptors = [NSArray arrayWithObjects:[NSSortDescriptor sortDescriptorWithKey:#"status" ascending:YES], [NSSortDescriptor sortDescriptorWithKey:#"unique" ascending:NO], nil];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.photoDatabase.managedObjectContext sectionNameKeyPath:#"status" cacheName:nil];
NSError *error;
BOOL success = [self.fetchedResultsController performFetch:&error];
if (!success) NSLog(#"error: %#", error);
else [self.tableView reloadData];
self.fetchedResultsController.delegate = self;
}
- (void)useDocument
{
if (![[NSFileManager defaultManager] fileExistsAtPath:[self.photoDatabase.fileURL path]]) {
[self.photoDatabase saveToURL:self.photoDatabase.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
[self setupFetchedResultsController];
}];
} else if (self.photoDatabase.documentState == UIDocumentStateClosed) {
[self.photoDatabase openWithCompletionHandler:^(BOOL success) {
[self setupFetchedResultsController];
}];
} else if (self.photoDatabase.documentState == UIDocumentStateNormal) {
[self setupFetchedResultsController];
}
}
- (void)setPhotoDatabase:(WLManagedDocument *)photoDatabase
{
if (_photoDatabase != photoDatabase) {
_photoDatabase = photoDatabase;
[self useDocument];
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
label.backgroundColor = [UIColor clearColor];
label.font = [UIFont fontWithName:#"AmericanTypewriter" size:20];
label.shadowColor = [UIColor colorWithWhite:0.0 alpha:0.5];
label.textAlignment = UITextAlignmentCenter;
label.textColor = [UIColor whiteColor];
self.navigationItem.titleView = label;
label.text = self.navigationItem.title;
[label sizeToFit];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Get CoreData database made if necessary
if (!self.photoDatabase) {
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"Default Photo Database"];
self.photoDatabase = [[WLManagedDocument alloc] initWithFileURL:url];
NSLog(#"No existing photoDatabase so a new one was created from default photo database file.");
}
self.tableView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"DarkWoodBackGround.png"]];
}
- (void)syncWithServer
{
// This is done on the syncQ
// Start the activity indicator on the nav bar
dispatch_async(dispatch_get_main_queue(), ^{
[self.spinner startAnimating];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:self.spinner];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(managedObjectContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:self.photoDatabase.managedObjectContext.parentContext];
});
// Find new animals (status == 0)
NSFetchRequest *newAnimalsRequest = [NSFetchRequest fetchRequestWithEntityName:#"Animal"];
newAnimalsRequest.predicate = [NSPredicate predicateWithFormat:#"status == 0"];
NSError *error;
NSArray *newAnimalsArray = [self.photoDatabase.managedObjectContext.parentContext executeFetchRequest:newAnimalsRequest error:&error];
if ([newAnimalsArray count]) NSLog(#"There are %d animals that need to be uploaded.", [newAnimalsArray count]);
if (error) NSLog(#"fetchError: %#", error);
// Get the existing animals from the server
NSArray *parsedDownloadedAnimalsByPhoto = [self downloadedAllAnimalsFromWeb];
// In the parent context, insert downloaded animals into core data
for (NSDictionary *downloadedPhoto in parsedDownloadedAnimalsByPhoto) {
[Photo photoWithWebDataInfo:downloadedPhoto inManagedObjectContext:self.photoDatabase.managedObjectContext.parentContext];
// table will automatically update due to NSFetchedResultsController's observing of the NSMOC
}
// Upload the new animals if there are any
if ([newAnimalsArray count] > 0) {
NSLog(#"There are %d animals that need to be uploaded.", [newAnimalsArray count]);
for (Animal *animal in newAnimalsArray) {
// uploadAnimal returns a number that lets us know if it was accepted by the server
NSNumber *unique = [self uploadAnimal:animal];
if ([unique intValue] != 0) {
animal.unique = unique;
// uploadThePhotosOf returns a success BOOL if all 3 uploaded successfully
if ([self uploadThePhotosOf:animal]){
[self.photoDatabase.managedObjectContext performBlock:^{
animal.status = [NSNumber numberWithInt:1];
}];
}
}
}
}
[self.photoDatabase.managedObjectContext.parentContext save:&error];
if (error) NSLog(#"Saving parent context error: %#", error);
[self performUpdate];
// Turn the activity indicator off and replace the sync button
dispatch_async(dispatch_get_main_queue(), ^{
// Save the context
[self.photoDatabase saveToURL:self.photoDatabase.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) {
if (success)
{
NSLog(#"Document was saved");
[self.photoDatabase.managedObjectContext processPendingChanges];
} else {
NSLog(#"Document was not saved");
}
}];
[self.spinner stopAnimating];
self.navigationItem.leftBarButtonItem = self.syncButton;
});
// Here it skips to the notification I got from saving the context so I can MERGE them
}
- (NSNumber *)uploadAnimal:(Animal *)animal
{
NSURL *uploadURL = [NSURL URLWithString:#"index.php" relativeToURL:self.remoteBaseURL];
NSString *jsonStringFromAnimalMetaDictionary = [animal.metaDictionary JSONRepresentation];
NSLog(#"JSONRepresentation of %#: %#", animal.namestring, jsonStringFromAnimalMetaDictionary);
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:uploadURL];
[request setPostValue:jsonStringFromAnimalMetaDictionary forKey:#"newmeta"];
[request startSynchronous];
NSError *error = [request error];
NSString *response;
if (!error) {
response = [request responseString];
NSNumber *animalUnique = [(NSArray *)[response JSONValue]objectAtIndex:0];
return animalUnique;
} else {
response = [error description];
NSLog(#"%# got an error: %#", animal.namestring, response);
return [NSNumber numberWithInt:0];
}
}
- (BOOL)uploadThePhotosOf:(Animal *)animal
{
NSURL *uploadURL = [NSURL URLWithString:#"index.php" relativeToURL:self.remoteBaseURL];
int index = [animal.photos count];
for (Photo *photo in animal.photos) {
// Name the jpeg file
NSTimeInterval timeInterval = [NSDate timeIntervalSinceReferenceDate];
NSString *imageServerPath = [NSString stringWithFormat:#"%lf-Photo.jpeg",timeInterval];
// Update the imageServerPath
photo.imageURL = imageServerPath;
NSData *photoData = [[NSData alloc] initWithData:photo.image];
NSString *photoMeta = [photo.metaDictionary JSONRepresentation];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:uploadURL];
[request addPostValue:photoMeta forKey:#"newphoto"];
[request addData:photoData withFileName:imageServerPath andContentType:#"image/jpeg" forKey:#"filename"];
[request setUploadProgressDelegate:self.progressView];
[request startSynchronous];
NSLog(#"%# progress: %#", animal.namestring, self.progressView.progress);
NSString *responseString = [request responseString];
NSLog(#"uploadThePhotosOf:%# photo at placement: %d has responseString: %#", animal.namestring, [photo.placement intValue], responseString);
SBJsonParser *parser= [[SBJsonParser alloc] init];
NSError *error = nil;
id jsonObject = [parser objectWithString:responseString error:&error];
NSNumber *parsedPhotoUploadResponse = [(NSArray *)jsonObject objectAtIndex:0];
// A proper response is not 0
if ([parsedPhotoUploadResponse intValue] != 0) {
photo.imageid = parsedPhotoUploadResponse;
--index;
}
}
// If the index spun down to 0 then it was successful
int success = (index == 0) ? 1 : 0;
return success;
}
- (NSArray *)downloadedAllAnimalsFromWeb
{
NSURL *downloadURL = [NSURL URLWithString:#"index.php" relativeToURL:self.remoteBaseURL];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:downloadURL];
[request setPostValue:#"yes" forKey:#"all"];
request.tag = kGetHistoryRequest;
[request startSynchronous];
NSString *responseString = [request responseString];
NSLog(#"downloadedAllAnimalsFromWeb responseString: %#", responseString);
SBJsonParser *parser= [[SBJsonParser alloc] init];
NSError *error = nil;
id jsonObject = [parser objectWithString:responseString error:&error];
NSArray *parsedDownloadedResponseStringArray = [NSArray arrayWithArray:jsonObject];
return parsedDownloadedResponseStringArray;
}
- (void)performUpdate
{
NSManagedObjectContext * context = self.photoDatabase.managedObjectContext.parentContext;
NSSet * inserts = [context updatedObjects];
if ([inserts count])
{
NSError * error = nil;
NSLog(#"There were inserts");
if ([context obtainPermanentIDsForObjects:[inserts allObjects]
error:&error] == NO)
{
NSLog(#"BAM! %#", error);
}
}
[self.photoDatabase updateChangeCount:UIDocumentChangeDone];
}
- (void)managedObjectContextDidSave:(NSNotification *)notification
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:self.photoDatabase.managedObjectContext.parentContext];
NSLog(#"userInfo from the notification: %#", [notification userInfo]);
// Main thread context
NSManagedObjectContext *context = self.fetchedResultsController.managedObjectContext;
SEL selector = #selector(mergeChangesFromContextDidSaveNotification:);
[context performSelectorOnMainThread:selector withObject:notification waitUntilDone:YES];
NSLog(#"ContextDidSaveNotification was sent. MERGED");
}
#pragma mark - Table view data source
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"EntryCell"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"EntryCell"];
}
// Configure the cell here...
Animal *animal = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = animal.namestring;
if (([animal.numberofanimals intValue] > 0) && animal.species) {
cell.detailTextLabel.text = [NSString stringWithFormat:#"%#s", animal.species];
} else {
cell.detailTextLabel.text = animal.species;
}
return cell;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
Animal *animal = [self.fetchedResultsController objectAtIndexPath:indexPath];
// be somewhat generic here (slightly advanced usage)
// we'll segue to ANY view controller that has a photographer #property
if ([segue.identifier isEqualToString:#"newAnimal"]) {
NSLog(#"self.photodatabase");
[(NewMetaEntryViewController *)[segue.destinationViewController topViewController] setPhotoDatabaseContext:self.photoDatabase.managedObjectContext];
} else if ([segue.destinationViewController respondsToSelector:#selector(setAnimal:)]) {
// use performSelector:withObject: to send without compiler checking
// (which is acceptable here because we used introspection to be sure this is okay)
[segue.destinationViewController performSelector:#selector(setAnimal:) withObject:animal];
NSLog(#"animal: %# \r\n indexPath: %#", animal, indexPath);
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 30;
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
return nil;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
NSLog(#"header for section called for section: %d", section);
NSLog(#"fetchedResultsController sections: %#", self.fetchedResultsController.sections);
CGRect headerRect = CGRectMake(0, 0, tableView.bounds.size.width, 30);
UIView *header = [[UIView alloc] initWithFrame:headerRect];
UILabel *headerTitleLabel = [[UILabel alloc] initWithFrame:CGRectMake(5, 5, tableView.bounds.size.width - 10, 20)];
if ([(Animal *)[[[[self.fetchedResultsController sections] objectAtIndex:section] objects] objectAtIndex:0] status] == [NSNumber numberWithInt:0]) {
headerTitleLabel.text = [self.sectionHeaderTitles objectAtIndex:0];
} else if ([(Animal *)[[[[self.fetchedResultsController sections] objectAtIndex:section] objects] objectAtIndex:0] status] == [NSNumber numberWithInt:1]) {
headerTitleLabel.text = [self.sectionHeaderTitles objectAtIndex:1];
} else {
headerTitleLabel.text = [self.sectionHeaderTitles objectAtIndex:2];
}
headerTitleLabel.textColor = [UIColor whiteColor];
headerTitleLabel.font = [UIFont fontWithName:#"AmericanTypewriter" size:20];
headerTitleLabel.backgroundColor = [UIColor clearColor];
headerTitleLabel.alpha = 0.8;
[header addSubview:headerTitleLabel];
return header;
}
Way too much code for anyone to want to wade through.
However, from a quick inspection, it looks like you are violating the MOC constraints. Specifically, you are accessing the parent context directly, and not from its own thread, either.
Typically, you would start a new thread, then create a MOC in that thread, make its parent be the MOC of the document. then do your stuff, and call save on the new MOC. It will then notify the parent, which should handle the updating.