IOS How to sync multithreading NSManagedObjectContext? - objective-c

Application must update data from WebService in loop each 10 sec in background and display data to user by his request in the main thread. Also I need update and delete records by user request.
Updates done with runloop.
I have registered notification in the AppDelegate
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:nil];
- (void)contextChanged:(NSNotification*)notification
{
if ([notification object] == [self managedObjectContext]) return;
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(contextChanged:) withObject:notification waitUntilDone:YES];
return;
}
[[self managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
[self saveContext]; //do I need this here or marge save data too?
}
I have Storage sharedInstance class with
NSOperationQueue* operationQueue;
then inserts,updates,selects operators added this way from any thread:
-(void)do_something
{
[self.operationQueue addOperationWithBlock:^{
NSManagedObjectContext*moc; //creating new NSManagedObjectContext with AppDelegate.persistentStoreCoordinator
//do my staff
[moc save:&error]
}]
}
The problem is when I try update entities with #"my_id=%#", #(my_id)
[moc countForFetchRequest:fetchRequest error:&error]
return 0 and cause inserting of duplicate exists entity
The problem is with synchronization.
Advice please.
should I use instance of dispatch_queue_create("com.my.", 0); instead for each CoreData operation?
I did try remove operationQuiue
-(void)query:(void(^)(NSManagedObjectContext *context))queryBlock
{
NSLog(#"query CALL");
__block NSManagedObjectContext *context;
//if remove dispatch_sync and/or run in main thread result the same
dispatch_sync( dispatch_queue_create("com.myapp.db-queue", 0), ^{
AppDelegate*app = AppDelegate();
//same result if I use
//app.persistentStoreCoordinator or
//[app.managedObjectContext persistentStoreCoordinator]
NSPersistentStoreCoordinator *persistentStoreCoordinator= [app.managedObjectContext persistentStoreCoordinator];
context = [NSManagedObjectContext new];
[context setPersistentStoreCoordinator:persistentStoreCoordinator];
[context setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
queryBlock(context);
if ([context hasChanges])
{
NSError*err;
[context save:&err];
if (err) {
NSLog(#"context save: %#",[err localizedDescription]);
}
}
});
}
and call it as :
CoreStorage* cs = [CoreStorage sharedInstance];
NSArray* list = [ws GetSections]; //array of NSDictionaries
//if add this to operationQuiue resunt the same
[cs query:^(NSManagedObjectContext *moc) {
NSLog(#"START");
for (NSDictionary *section in list) {
NSNumber* Id= #([[section objectForKey:#"section_id"] integerValue]);
NSFetchRequest * fetchRequest = [NSFetchRequest new];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Section" inManagedObjectContext: moc];
[fetchRequest setEntity:entity];
[fetchRequest setFetchLimit:1];
[fetchRequest setIncludesSubentities:NO];
[fetchRequest setPredicate: [NSPredicate predicateWithFormat:#"section_id=%#",Id]];
NSError *error =nil;
Section *entry;
if ([moc countForFetchRequest:fetchRequest error:&error] >0)
{
entry = [moc executeFetchRequest:fetchRequest error:nil][0];
NSLog(#"exist"); //this never call
}
else
{
entry = [NSEntityDescription insertNewObjectForEntityForName:#"Section" inManagedObjectContext:moc];
NSLog(#"NEW");
}
entry.section_id = Id;
entry.timeStamp = [NSDate date];
}
}];
Any sugastions please?

The problem is probably in the operation queue. You haven't configured it with max concurrent operations to be 1, right? In this case it is not serial, and operations that you add to it run concurrently. So here what happens. First operation fetches for the count of object with some ID, doesn't find it and creates one. At some point before it saves, another operation is added. This second operation fetches for the object with the same ID, doesn't find it and creates one. Then the first operation saves, then the second one saves, and you have a duplicate.
So try to make your operation queue serial [operationQueue maxConcurrentOperationCount:1];.
And no, you don't have to save after calling merge method of the managed object context.

Related

Core Data TableView - Multiple Selection During Edit Mode

I have two TableViews using Core Data. I have an ItemTableview with multiple rows of Item listed by the user, and it allows multiple selection during edit mode. During edit mode, it allows user to delete selected items, or delete all of the items at once. I want the items that's been deleted to be added to a TrashTableView.
Here's what I have so far:
- (IBAction)deleteAction:(id)sender
{
NSManagedObjectContext *context = [self managedObjectContext];
NSArray *selectedRows = [self.tableView indexPathsForSelectedRows];
BOOL noItemsAreSelected = selectedRows.count == 0;
BOOL deleteSpecificRows = selectedRows.count > 0;
if (noItemsAreSelected) {
// Delete all objects from the Core Data.
NSFetchRequest *allItems = [[NSFetchRequest alloc] init];
[allItems setEntity:[NSEntityDescription entityForName:#"Item" inManagedObjectContext:context]];
[allItems setIncludesPropertyValues:NO];
NSError *error = nil;
NSArray *items = [context executeFetchRequest:allItems error:&error];
for (NSManagedObject *object in items) {
[context deleteObject:object];
}
// Add to Trash
for (NSManagedObject *trashObject in items) {
Item *selectedItems = trashObject; <-- #warning -Incompatible pointer types initializing "Items" with an expression of type "NSManagedObject"-
Trash *newTrash = [NSEntityDescription insertNewObjectForEntityForName:#"Trash" inManagedObjectContext:context];
newTrash.trashname = selectedItems.itemname;
newTrash.created = [NSDate date];
// Save the context
NSError *saveError = nil;
if (![context save:&saveError]) {
NSLog(#"Save Failed! %# %#", saveError, [saveError localizedDescription]);
}
}
// Delete from the Array
[_item removeAllObjects];
// Tell the tableView that we deleted the objects.
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView setEditing:NO animated:YES];
}
else if (deleteSpecificRows) {
NSMutableIndexSet *indicesOfItemsToDelete = [NSMutableIndexSet new];
for (NSIndexPath *selectionIndex in selectedRows)
{
[indicesOfItemsToDelete addIndex:selectionIndex.row];
}
// Delete from the Array
[_item removeObjectsAtIndexes:indicesOfItemsToDelete];
// Tell the tableView that we deleted the objects
[self.tableView deleteRowsAtIndexPaths:selectedRows withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView setEditing:NO animated:YES];
}
}
When if (noItemsAreSelected) is called, all of the items are deleted and they all get added to the TrashTableView, but only a first row from the ItemTableView gives the string. So in the TrashTableView, first row has a text but the rest of the rows are just blank cells without any text.
In the debugger, blank cells have NSString trashname = nil; but NSDate created = "2015-01-22 03:41:30 +0000"; has a date.
For else if (deleteSpecificRows) I have no idea how to do it in Core Data....
I've spent quiet a lot of time trying to figure this out, so any help would be greatly appreciated. Thanks!
You are doing things in the wrong order: you currently delete all the items from the context, then iterate through the items, creating the corresponding trash items and saving the context each time. This works OK for the first item. But after the first item, the context has already been saved, so the delete operation (for ALL the items) will have happened, which nils out all their properties. Hence your null values.
I would restructure it as follows:
if (noItemsAreSelected) {
NSFetchRequest *allItems = [[NSFetchRequest alloc] init];
[allItems setEntity:[NSEntityDescription entityForName:#"Item" inManagedObjectContext:context]];
[allItems setIncludesPropertyValues:NO];
NSError *error = nil;
NSArray *items = [context executeFetchRequest:allItems error:&error];
for (Item *trashObject in items) {
// Add to Trash
Trash *newTrash = [NSEntityDescription insertNewObjectForEntityForName:#"Trash" inManagedObjectContext:context];
newTrash.trashname = trashObject.itemname;
newTrash.created = [NSDate date];
// Delete
[context deleteObject:trashObject];
}
// Save the context
NSError *saveError = nil;
if (![context save:&saveError]) {
NSLog(#"Save Failed! %# %#", saveError, [saveError localizedDescription]);
}
Note that changing the cast in the for(Item *trashObject ...) should avoid the compiler warning.
EDIT
For the deleteSpecificRows case, you can use similar code, but using your _item (I assume that is a mutable array which is the datasource for your tableView):
else if (deleteSpecificRows) {
NSMutableIndexSet *indicesOfItemsToDelete = [NSMutableIndexSet new];
for (NSIndexPath *selectionIndex in selectedRows)
{
// First, get the trash object...
Item *trashObject = [_item objectAtIndex:selectionIndex.row];
// Add to Trash
Trash *newTrash = [NSEntityDescription insertNewObjectForEntityForName:#"Trash" inManagedObjectContext:context];
newTrash.trashname = trashObject.itemname;
newTrash.created = [NSDate date];
// and delete the object from the context
[context deleteObject:trashObject];
// and update the list of items to delete
[indicesOfItemsToDelete addIndex:selectionIndex.row];
}
// Delete from the Array
[_item removeObjectsAtIndexes:indicesOfItemsToDelete];
// Tell the tableView that we deleted the objects
[self.tableView deleteRowsAtIndexPaths:selectedRows withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView setEditing:NO animated:YES];
}
Note that this is untested, so might need tidying up....
Longer term, you might want to consider a) using a NSFetchedResultsController to act as your datasource for your tableView, and b) rather than creating separate entities for the trash, add flag to the existing entities (inTrash?) and just changing that to true. Your tableView would then have to show only items with inTrash = false.

Does AFIncrementalStore need an NSMainQueueConcurrencyType context?

I've been working on incorporating AFIncrementalStore into our app following the example code in the repository that uses an SQLite backing store. All the examples use a singleton managedObjectContext with an NSMainQueueConcurrencyType.
+ (NSManagedObjectContext *)managedObjectContext {
static NSManagedObjectContext *_managedObjectContext = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
});
return _managedObjectContext;
}
Using this moc, I'm able to perform a fetch, see it get pulled from the network, and stored in the sqlite backing store. I tried changing it to use the NSPrivateQueueConcurrencyType, and while I would see the network request, nothing was ever saved to the SQLite backing store. However, if I leave this moc with main queue concurrency, and then create a child from it, and use that moc, then everything saves fine.
+ (User *)user
{
// grab a user if we already have one
NSManagedObjectContext *managedObjectContext = [VigilCoreDatabase managedObjectContext];
NSManagedObjectContext *tmpContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
tmpContext.parentContext = managedObjectContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.fetchLimit = 1;
[fetchRequest setAffectedStores:#[ ]];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"User" inManagedObjectContext:tmpContext];
[fetchRequest setEntity:entity];
__block User *user = nil;
[tmpContext performBlockAndWait:^{
NSError *error = nil;
NSArray *fetchedObjects = [tmpContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(#"error");
}
if(fetchedObjects.count > 0) {
user = fetchedObjects[0];
}
}];
return user;
}
I wanted to figure out if I was missing something in my understanding. I can't seem to find any examples that don't use a moc with main queue concurrency as the parent context (with the backing store using a private queue context), but at the same time can't find any documentation explaining whether this is required, or whether I need to do something to push changes to the parent manually when using a private queue context vs. having a main queue context in the stack.
At this time AFIncrementalStore suffers from a bug. I asked an SO question about another IncrementalStore (it utilizes the same code) and the response leads me to believe AFIS requires NSMainQueueConcurrencyType

Threads Using Managed Objects Between Contexts

I am at that point where I am losing hair on this so I figured I'd reach out to the great minds here who have had experience using Objective C with Threads and core data. I am having issues with managed objects inserted in on thread in a NSPrivateQueue Context being accessed from the main thread. So at a high level I am using AFNetworking to generate a thread to make requests to retrieve JSON data from a server and then insert the values into my persistent store core data. After this is done I have another thread for downloading some binary data using AFNetworking as well. I have set up 2 managed contexts for this as shown below:
(NSManagedObjectContext *)masterManagedContext {
if (_masterManagedContext != nil) {
return _masterManagedContext;
}
NSPersistentStoreCoordinator *coord = [self coordinator];
if (coord != nil) {
_masterManagedContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_masterManagedContext.stalenessInterval = 0.0;
[_masterManagedContext performBlockAndWait:^{
[_masterManagedContext setPersistentStoreCoordinator:coord];
}];
}
return _masterManagedContext;
}
// Return the NSManagedObjectContext to be used in the background during sync
- (NSManagedObjectContext *)backgroundManagedContext {
if (_backgroundManagedContext != nil) {
return _backgroundManagedContext;
}
NSManagedObjectContext *masterContext = [self masterManagedContext];
if (masterContext != nil) {
_backgroundManagedContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
_backgroundManagedContext.stalenessInterval = 0.0;
[_backgroundManagedContext performBlockAndWait:^{
[_backgroundManagedContext setParentContext:masterContext];
}];
}
return _backgroundManagedContext;
}
As is shown above I am using a child context and the parent context. When I make I call to fetch the json data I have something like below:
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
//Initially delete all records in table. This will change
[[Singleton sharedInstance]removeEntityObjects:className];
for (int x=0; x < [JSON count]; x++) {
NSMutableDictionary *curDict = [JSON objectAtIndex:x];
[[CoreDatam sharedinstance] insertEmployeesWithDictionary:curDict];
}else {
/* do nothing */
}
}
}
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error,id JSON) {
[delegate performSelector:#selector(didNotCompleteSync:) withObject:className];
}];
[operations addObject:operation];
}
[self.AFClient enqueueBatchOfHTTPRequestOperations:operations progressBlock:^(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations) {
NSLog(#"Currenlty downloaded table data %d of %d!",numberOfCompletedOperations,totalNumberOfOperations);
} completionBlock:^(NSArray *operations) {
if (_syncInProgress) {
[[CoreDatam sharedInstance]updateEmpForId];
[self downloadAllFiles];
}
}];
}`
for the insert function I have something like below:
insertEmployeesWithDictionary:curDict {
[[self backgroundManagedContext]performBlockAndWait:^{
Employee *emp = [NSEntityDescription insertNewObjectForEntityForName:#"Employee"
inManagedObjectContext:[self backgroundManagedContext]];
/* Issues saving null into core data based on type.*/
[emp setFirst:[dictCopy objectForKey:#"first"]];
[emp setLast:[dictCopy objectForKey:#"last"]];
NSError *error = nil;
BOOL saved;
saved = [[self backgroundManagedContext] save:&error];
if (!saved) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
[self saveMasterContext];
}];
}
The issue is below where I am trying to access the managed objects in the method that is in the completion block above:
updateEmpId {
[self.backgroundManagedContext performBlockAndWait:^{
NSError *error = nil;
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"Employee"];
[request setSortDescriptors:[NSArray arrayWithObject:
[NSSortDescriptor sortDescriptorWithKey:#"last" ascending:YES]]];
myEmps = [self.backgroundManagedContext executeFetchRequest:request error:nil];
for (Employee *moEmp in myEmps) {
[[self backgroundManagedContext]refreshObject:moEmp mergeChanges:YES];
moEmp.photo = #'default.pic';
}
NSError *saveError = nil;
if (![self.backgroundManagedContext save:&saveError]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
[self saveMasterContext];
}
The issue is that I am getting very inconsistent behavior when looking at the managed objects that are modified in the main thread. Is it still necessary to pass managed objectIds when using a parent child context relation? if so how can I do so for the above example? Any help greatly appreciated.
You should pass NSManagedObjectIDs or re-fetch in the main thread context, yeah. If you pass object IDs, get the IDs from the background context after saving the new Employee objects, and use existingObjectWithID:error: in the parent context to instantiate them there. Or just re-do the fetch request from your updateEmpId code block in the masterManagedContext.

Core Data Objective C update content of Entity

I am first time asking question here, sorry, but I can not find similar one.
So, I need update data in Entity "City" attribute - #"name".
for Example in my Core Data I already have #"New York", #"Boston".
And by parsing XML I have NSMutableArray *Cities = (#"New York", #"Boston", #"Los Angeles", #"Washington");
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
NSString *attributeString = #"name";
NSManagedObject *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
//save to the TableView
cell.textLabel.text = [[object valueForKey:attributeString] description];
if ((indexPath.row + 1) == numberOfSectionsInTableView && (self.isParsingDone))
[self.insertNewObjectToCities:nil];
//When coredata updating - tableView is also updating automatically
//Here is just adding new data, but I do not know how to update
- (void)insertNewObjectToCities_translation:(id)sender
{
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
NSString *attributeString = #"name";
if (![[self.parseCities.Cities objectAtIndex:i] isEqualToString:[newManagedObject valueForKey:attributeString]])
{
[newManagedObject setValue:[self.parseCities.Cities objectAtIndex:i] forKey:attributeString];
NSLog(#"OBBB %#", [self.parseCities.Cities objectAtIndex:i]);
NSLog(#"dessss %#", [[newManagedObject valueForKey:attributeString] description]);
i++;
if (i==[self.parseCities.Cities count])
{
i = 0;
return;
}
else
{
NSLog(#"valueForKey %#", [newManagedObject valueForKey:attributeString]);
[self insertNewObjectToCities_translation:nil];
}
}
else
{
NSLog(#"else");
return;
}
// Save the context.
NSError *error = nil;
if (![context save:&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();
}
}
To update a managed object, you first need to fetch it, make any changes to the fields in the fetched NSManagedObject, and then save the context you used to fetch the object. If you call insertNewObjectForEntityForName again, it will insert a new managed object every time, even if it already exists in Core Data.
It's quite slow to fetch a single object every time you need to check and see if a new one needs to be added. You might want to cache the objects you currently have loaded (or their unique identifying field) into an NSArray or NSSet so you can check that for membership, instead.

Method/IBAction stuck in infinite loop. Still no success

Now this may sound like my earlier problem/question but I've changed and tried a few things that were answered in my other questions to try to make it work, but I've still got the same problem.
I am observing a core data property from within a NSManagedObject sub-class and the method that gets called when the property changes calls another method but in this method it adds Core Data objects which triggers the KVO method which triggers the method again and so forth. Or so it seems, I'm not too sure about that because something different seems to happen, here is the series of events …
I click a button syncing with iCal (this in an IBAction with the exact same code thats in the method syncKVO). This sync works fine.
I add an object to my outline view. All is well.
I change its name which triggers the KVO Declaration (because I changed the 'name' property) which syncs with iCal. Works fine.
I delete the object I just added and somehow it triggers the KVO declaration (thus triggering the method) and puts me into an infinite loop.
Now for some code.
Code inside the NSManagedObject Subclass (called JGManagedObject) …
- (void) awakeFromFetch {
[self addObserver:[NSApp delegate] forKeyPath:#"name" options:0 context:nil];
}
- (void) awakeFromInsert {
[self addObserver:[NSApp delegate] forKeyPath:#"name" options:0 context:nil];
}
+ (void) addObserver{
[self addObserver:[NSApp delegate] forKeyPath:#"name" options:0 context:nil];
}
+ (void) removeObserver{
[self removeObserver:[NSApp delegate] forKeyPath:#"name"];
}
The KVO Declaration (inside the App Delegate) …
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:#"name"]) {
[self performSelector:#selector(syncKVO:)];
}
}
The Method (also inside the App Delegate)…
- (void)syncKVO:(id)sender {
NSManagedObjectContext *moc = [self managedObjectContext];
[syncButton setTitle:#"Syncing..."];
NSString *dateText = (#"Last Sync : %d", [NSDate date]);
[syncDate setStringValue:dateText];
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:#"projects" inManagedObjectContext:moc];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entityDescription];
NSError *error = nil;
NSArray *array = [moc executeFetchRequest:request error:&error];
if (array == nil)
{
NSAlert *anAlert = [NSAlert alertWithError:error];
[anAlert runModal];
}
NSArray *namesArray = [array valueForKey:#"name"];
NSPredicate *predicate = [CalCalendarStore taskPredicateWithCalendars:[[CalCalendarStore defaultCalendarStore] calendars]];
NSArray *tasksNo = [[CalCalendarStore defaultCalendarStore] tasksWithPredicate:predicate];
NSArray *tasks = [tasksNo valueForKey:#"title"];
NSMutableArray *namesNewArray = [NSMutableArray arrayWithArray:namesArray];
[namesNewArray removeObjectsInArray:tasks];
NSLog(#"%d", [namesNewArray count]);
NSInteger *popIndex = [calenderPopup indexOfSelectedItem];
//Load the array
CalCalendarStore *store = [CalCalendarStore defaultCalendarStore];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString *supportDirectory = [paths objectAtIndex:0];
NSString *fileName = [supportDirectory stringByAppendingPathComponent:#"oldtasks.plist"];
NSMutableArray *oldTasks = [[NSMutableArray alloc] initWithContentsOfFile:fileName];
[oldTasks removeObjectsInArray:namesArray];
NSLog(#"%d",[oldTasks count]);
//Use the content
NSPredicate* taskPredicate = [CalCalendarStore taskPredicateWithCalendars:[[CalCalendarStore defaultCalendarStore] calendars]];
NSArray* allTasks = [[CalCalendarStore defaultCalendarStore] tasksWithPredicate:taskPredicate];
// Get the calendar
CalCalendar *calendar = [[store calendars] objectAtIndex:popIndex];
// Note: you can change which calendar you're adding to by changing the index or by
// using CalCalendarStore's -calendarWithUID: method
// Loop, adding tasks
for(NSString *title in namesNewArray) {
// Create task
CalTask *task = [CalTask task];
task.title = title;
task.calendar = calendar;
// Save task
if(![[CalCalendarStore defaultCalendarStore] saveTask:task error:&error]) {
NSLog(#"Error");
// Diagnostic error handling
NSAlert *anAlert = [NSAlert alertWithError:error];
[anAlert runModal];
}
}
NSMutableArray *tasksNewArray = [NSMutableArray arrayWithArray:tasks];
[tasksNewArray removeObjectsInArray:namesArray];
NSLog(#"%d", [tasksNewArray count]);
for(NSString *title in tasksNewArray) {
NSManagedObjectContext *moc = [self managedObjectContext];
JGManagedObject *theParent =
[NSEntityDescription insertNewObjectForEntityForName:#"projects"
inManagedObjectContext:moc];
[theParent setValue:nil forKey:#"parent"];
// This is where you add the title from the string array
[theParent setValue:title forKey:#"name"];
[theParent setValue:[NSNumber numberWithInt:0] forKey:#"position"];
}
for(CalTask* task in allTasks)
if([oldTasks containsObject:task.title]) {
[store removeTask:task error:nil];
}
// Create a predicate for an array of names.
NSPredicate *mocPredicate = [NSPredicate predicateWithFormat:#"name IN %#", oldTasks];
[request setPredicate:mocPredicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
// Execute the fetch request put the results into array
NSArray *resultArray = [moc executeFetchRequest:request error:&error];
if (resultArray == nil)
{
// Diagnostic error handling
NSAlert *anAlert = [NSAlert alertWithError:error];
[anAlert runModal];
}
// Enumerate through the array deleting each object.
// WARNING, this will delete everything in the array, so you may want to put more checks in before doing this.
for (JGManagedObject *objectToDelete in resultArray ) {
// Delete the object.
[moc deleteObject:objectToDelete];
}
//Save the array
[namesArray writeToFile:fileName atomically:YES];
[syncButton setTitle:#"Sync Now"];
NSLog(#"Sync Completed");
}
What I've tried …
Filtering the Keypaths that call the KVO Declaration with
if ([keyPath isEqualToString:#"name"]) {
…
}
Detaching and reattaching observers with
[JGManagedObject removeObserver];
//and
[JGManagedObject addObserver];
but with that it works the first time but stops the method the second time saying that it cannot remove the observer because it is not observing, which doesn't make sense because I added the observer again the first time. That is why I left this code out of the actual method else it would stop on the second sync.
I'm not sure whats going on with this, I think I've tried everything. Whats gone wrong?
Any help would be greatly appreciated.
The problem might be here:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:#"name"]) {
[self performSelector:#selector(syncKVO:)];
}
}
You call syncKVO: everytime something happens to 'name' regardless of what it is that has actually happened to 'name'. I suggest you start using the object, change and context parameters to determine what has just happened and what action, if any, should be undertaken.
BTW, it's not considered good practice to add a lot of stuff to the app delegate. You might want to put all this syncing stuff into a proper controller class and call [NSApp delegate] when you need it.