IBAction gets stuck in Loop



Ok, I have an IBAction that syncs with iCal and is also triggered by KVO of the 'name' property in my CD Model, so that when the property changes the Action is triggered. What happens is that once the IBAction reaches the end it skips to the KVO declaration which then triggers the Action again and again and again, this is where the loop occurs.

Here's some code. The IBAction …

- (IBAction)sync:(id)sender {
    [syncButton setTitle:@"Syncing..."];
    NSString *dateText = (@"Last Sync : %d", [NSDate date]);
    [syncDate setStringValue:dateText];
    NSManagedObjectContext *moc = [self managedObjectContext];
    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]) {
            // 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"
        [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");

Which when it reaches the end (somehow) triggers the KVO Declaration …

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    [self performSelector:@selector(sync:)];

Which then triggers the IBAction again. This is where the loop occurs as it gets stuck here and constantly triggers the Action.

Can anyone figure out what on earth is happening???

1 ответов


Вы запускаете селектор sync: для любого KVO-совместимого свойства.
Вы должны отфильтровать любой ключевой путь, который изменяется с помощью вашего метода синхронизации, чтобы избежать рекурсии.

Я бы сделал следующее:

  • Установите точку останова и проверьте значение keyPath, чтобы выяснить, какое изменение начинает рекурсию.
  • Отфильтровать изменения для путей, вызывающих рекурсию.


  • Отсоединить всех наблюдателей при запуске синхронизации: и повторно подключить их после завершения.
  • @Joshua - you may be calling self from within your JGManagedObject class file, but that actually means you’re calling it on an instance of the class. You can’t send instance methods such as removeObserver:forKeyPath: on a class object, you need to send it to an actual instance of the class.

    Abizern12 октября 2009, 18:01
  • You send the messages to the object you’re observing (i.e., the one you want to stop observing and then start observing again).

    Peter Hosey10 октября 2009, 18:16
  • +1 Not a bad idea to detach all observers at the top of sync and reattach them at the bottom. That way, changes made during sync aren’t observed, but changes to the model or the calendar store will still trigger a sync.

    Abizern10 октября 2009, 16:11
  • Вы знаете, почему это так?

    Joshua11 октября 2009, 14:34
  • Это объект, который я наблюдаю. Я наблюдаю JGManagedObject, а наблюдателем является делегат приложения.

    Joshua11 октября 2009, 06:42
  • Джошуа: Это не методы класса, поэтому отправить эти сообщения классу не получится.

    Peter Hosey10 октября 2009, 17:26
  • How do I send it to an instance of the class?

    Joshua13 октября 2009, 06:30
  • Нет, это класс, но я наблюдаю именно за ним, потому что в подклассе NSManagedObject под названием JGManagedObject у меня есть код - (void) awakeFromInsert { [self addObserver:[NSApp delegate] forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil]; }

    Joshua12 октября 2009, 15:17
  • I’ve tried adding [JGManagedObject removeObserver:self forKeyPath:@"name"]; at the start and [JGManagedObject addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil]; at the end, it works flawlessly the first time but then it says it can’t remove the observer because it isn’t observing even though I added it again at the end of the action.

    Joshua11 октября 2009, 11:33
  • How else would I do it? In the JGManagedObject I send the messages to self.

    Joshua10 октября 2009, 17:43
  • So, JGManagedObject is the name of a variable containing a pointer to an instance, not the name of a class? Normally, [adjective] is the name of a class. If it's the name of a variable, I suggest you rename it to avoid future confusion.

    Peter Hosey12 октября 2009, 09:44
  • So do you suggest that at the start of the action I add [JGManagedObject removeObserver:self forKeyPath:@"name"]; and at the end add [JGManagedObject addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];?

    Joshua10 октября 2009, 16:42