views:

215

answers:

1

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 …

  1. 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.
  2. I add an object to my outline view. All is well.
  3. I change its name which triggers the KVO Declaration (because I changed the 'name' property) which syncs with iCal. Works fine.
  4. 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.

+3  A: 

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.

Elise van Looij
Hmm. I see, Yes, That probably is the problem. How would I get the parameters so I could find out what happened?
Joshua
You already have them.
Peter Hosey
Oh, Yes, It's part of the method. I've looked in the Documents to see what the parameters return but would any of them help? `object` is the source object of the key path `keyPath` but would this not just give me JGManagedObject? `change` is a dictionary detailing the change to `keyPath` but would this not just show the change in string for the name property? And `context` is just what context was set when it was added. So how would these parameters help?
Joshua
Basically, I'm asking how can they help me?
Joshua
Anyone, Can anyone answer how they can help me?
Joshua
Don't panic, NSLog is your friend. Copy if ([keyPath isEqualToString:@"name"]) { [self performSelector:@selector(syncKVO:)]; }}Make sure your console is visible (menu Run >> Console) and then Build and Go.
Elise van Looij
Also, search on "addObserver context" for the correct way to use the context parameter.
Elise van Looij