views:

355

answers:

2

As explained in my earlier question …

This code …

- (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");
}

triggers this code …

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if ([keyPath isEqualToString:@"name"]) {
        [self performSelector:@selector(syncKVO:)];
    }
}

because I am adding objects and the KVO method is triggered when the Core Data 'name' property is changed.

I need to stop the observeValueForKeyPath:ofObject:change:context: method if it was triggered by the syncKVO method. How would I do this?

+1  A: 

The simplest thing you could do is use an instance variable which keeps track of whether you’re syncing and ignore observer changes when it’s set. It may be better to stop and start observing at the beginning and end of syncKVO:, but it depends on what you’re actually observing: you don’t want to mass unsubscribe and resubscribe if you’re watching a large collection.

Ben Stiglitz
I've tried doing that but I got a error in the debugger. I added this code to my Managed Object sub-class which is where I am observing the core data name property. http://cld.ly/616yf but I had a problem http://cld.ly/1c6ye.
Joshua
Any ideas why this is?
Joshua
Inside a class method `self` refers to a `Class` object (instance of the JGManagedObject metaclass), not to an individual instance, so you observer-managing calls are watching a key on the class instance, not on individual instances. You should move these into an instance method (preferably on the controller which contains `-syncKVO:`, since the add/remove observer interface is already provided on your managed objects).
Ben Stiglitz
So do you think I should add this http://cld.ly/147ds to my App Delegate?
Joshua
No, I don’t; that code still subscribes to notifications from the class and not the instance. You need to loop over your watched entities and remove the observers, do your work, and resume adding them. It’d be good to see where you set up the K-V observing in the first place; I don’t see you mutating the name property of anything already watched here, so you must be doing it on insertion into the context or something like that.Summary: please show more code.
Ben Stiglitz
Ok. I see. In the first place I added the observers in the awakeFromFetch: and the awakeFromInsert: methods of my NSManagedObject sub-class. The code: http://snapplr.com/v2v6. How would I loop through the entities? Or Do I need to loop through all my NSObjects from my Outline View and remove the observer for each of them?
Joshua
I think I came up with a better solution, which I posted as a separate answer. If you were to continue down this path I’d say you would need to keep track of which entities you’re observing, or just use the `BOOL` flag trick to knock out that observer method temporarily. I do think my other solution is much cleaner, and allows you to avoid KVO completely (I don’t think it’s the droid you’re looking for).
Ben Stiglitz
A: 

Looking at your code I wonder if you really want to do this syncing when entities are saved, and not as soon as the object keys changed. I think you’d be better off ditching observing completely and watching for the NSManagedObjectContextObjectsDidChangeNotification, using the values of the userInfo keys specified in the CoreData documentation to determine which entities need to be updated.

Ben Stiglitz
Hmm. How would I use the NSManagedObjectContextObjectsDidChangeNotification, does it trigger a method when something changes? Also, I am observing a Core Data property, can i do the same with NSManagedObjectContextObjectsDidChangeNotification?
Joshua
Also, would doing it this way stop my infinite loop?
Joshua
Would I add an observer like this … http://snapplr.com/qffn ?
Joshua
There’s an NSString constant called NSManagedObjectContextObjectsDidChangeNotification; you should use that instead of the hard-coded string you show in your example. Doing it this way alone would not stop your infinite loop—you still want to unsubscribe from and resubscribe to the notification (after saving changes) in syncKVO: if you’re modifying entities.As an aside—it’d be great if you posted your code snippets as text instead of images (try http://gist.github.com).
Ben Stiglitz
Ok, this is what I think is the code I need to use http://gist.github.com/221214, in my app delegate, or does some of it need to go into my NSManagedObject subclass?
Joshua
Looks good. You want to probably check the notification’s userInfo dictionary in your syncKVO: method to make sure that you actually have to sync. The dictionary will tell you which objects were added, removed and modified, and you can restrict your calendar updates to only those that affect the objects in question.
Ben Stiglitz
How do i check the notification’s userInfo dictionary?
Joshua
The notification has a userInfo method.
Ben Stiglitz
I've started a new question (http://stackoverflow.com/questions/1660280/nsmanagedobjectcontextobjectsdidchangenotification-userinfo-dictionary) because I can't find the userInfo method.
Joshua