2013-03-15 9 views
0

У меня есть общий одноэлементный класс NSObject, что у меня есть несколько очередей операции, работающих в я получаю аварию на это:iOS - Как удалить наблюдателя из Singleton NSObject для KVO? .

[super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 

Кажется, что мне нужно использовать «removeObserver:», чтобы не допустить этого, но как правильно сделать это на общему объекту?

КОД:

-(void)synchronizeToDevice{ 
    queue = [NSOperationQueue new]; 
    queue.name = @"SynchronizeToDeviceQueue"; 
    //Sync Active User 
    NSInvocationOperation *operationUser = [[NSInvocationOperation alloc] initWithTarget:self 
                       selector:@selector(downloadUserData:) 
                       object:[self activeUserID]]; 

    [queue addOperation:operationUser]; 

    //Sync Video Data 
    NSInvocationOperation *operationVideos = [[NSInvocationOperation alloc] initWithTarget:self 
                      selector:@selector(downloadVideoData) 
                       object:nil]; 
    [queue addOperation:operationVideos]; 


    [queue addObserver:self forKeyPath:@"operations" options:0 context:NULL]; 
} 
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 
{ 
    if (object == queue && [keyPath isEqualToString:@"operations"]) { 
     //Synchronization Queue 
     if ([queue.name isEqualToString:@"SynchronizeToDeviceQueue"] && [queue.operations count] == 0) { 
      //Queue Completed 
      //Notify View Synchronization Completed 
      [self performSelectorOnMainThread:@selector(postNotificationDidFinishSynchronizationToDevice) withObject:nil waitUntilDone:NO]; 
     } 
     //Video Download Queue 
     if ([queue.name isEqualToString:@"VideoFileDownloadQueue"] && [queue.operations count] == 0) { 
      //Notify View Video File Download Completed 
      [self performSelectorOnMainThread:@selector(postNotificationDidFinishDownloadingVideo) withObject:nil waitUntilDone:NO]; 
     } 
     //Active User Sync Queue 
     if ([queue.name isEqualToString:@"SynchronizeActiveUserToDeviceQueue"] && [queue.operations count] == 0) { 
      //Queue Completed 
      //Notify View Synchronization Completed 
      [self performSelectorOnMainThread:@selector(postNotificationDidFinishActiveUserSynchronizationToDevice) withObject:nil waitUntilDone:NO]; 
     } 
    } 
    else { 
     [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 
    } 
} 

CRASH LOG:

2013-03-14 21:48:42.167 COMPANY[1946:1103] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '<DataManager: 0x1c54a420>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled. 
Key path: operations 
Observed object: <NSOperationQueue: 0x1c5d3360>{name = 'SynchronizeActiveUserToDeviceQueue'} 
Change: { 
    kind = 1; 
} 
Context: 0x0' 
*** First throw call stack: 
(0x336262a3 0x3b4b197f 0x336261c5 0x33f1a56d 0x21bd1 0x33eb46b9 0x33eb4313 0x33eb3a25 0x33eb3817 0x33f2b689 0x3b8ccb97 0x3b8cf139 0x3b8cd91d 0x3b8cdac1 0x3b8fda11 0x3b8fd8a4) 
libc++abi.dylib: terminate called throwing an exception 
+0

Вы можете разместить краш журнал –

+0

Что кода, в каком классе, происходит сбой? Что заставляет вас думать, что удаление наблюдателя остановит его? К сожалению, сейчас ваш вопрос не совсем ясен. –

+0

К сожалению, добавлен код и журнал сбоев. Спасибо за помощь! – JimmyJammed

ответ

2

В Receiving Notification of a Change от "Наблюдающие программирования Key-Value Guide", в качестве примера реализации observeValueForKeyPath дается, с комментарий:

Обязательно вызовите реализацию суперкласса , если он ее реализует. NSObject не реализует метод.

Вы сказали, что ваш класс является подклассом NSObject, поэтому вы не должны вызывать [super observeValueForKeyPath:...].

Другая проблема возникает, если вы вызываете synchronizeToDevice более одного раза на один и тот же общий экземпляр. В этом случае вы создаете новый queue и регистрируете наблюдателя для этого. Но наблюдатель для старой очереди не удаляется.

Как следствие, observeValueForKeyPath может быть вызван для «старой очереди», а проверка if (object == queue) не удалась, что привело к нежелательному вызову super.

Так что если synchronizeToDevice можно назвать несколько раз, вы должны сначала удалить старого наблюдателя.

+1

Хотя это правда и полезно, я не думаю, что это настоящая проблема. То, что нам говорит об ошибке, заключается в том, что обратный вызов KVO вызывался, когда никто его не ожидал (никто его не обрабатывал). Просто удаление вызова 'super' замаскировало бы эту проблему, но проблема осталась бы. Вызов 'super' по существу действует как' NSAssert (I_SHOULD_HAVE_HANDLED_THAT) '. –

+0

@RobNapier: Да, это звучит разумно. –

+0

@RobNapier: Я не заметил, что вы отправили ответ, пока я обновлял мой. –

1

Я подозреваю, что ваш звонок synchronizeToDevice вызывается более одного раза. Если это так, вы продолжаете наблюдать старую очередь, а также новую очередь. Когда observeValueForKeyPath:... срабатывает, это, скорее всего, передаст вам старую очередь, которую вы затем проигнорируете, вызовите super, что выдает исключение, потому что вы не справлялись с наблюдаемым вами запросом.

Ваша настоящая проблема заключается в том, что вы не используете аксессоры. Это сделало бы это намного яснее. Например, это, как вы бы реализовать setQueue:

-(void)setQueue:(NSOperationQueue *)queue { 
    if (_queue) { 
    [_queue removeObserver:self forKeyPath:@"operations"]; 
    } 

    _queue = queue; 

    if (_queue) { 
    [_queue addObserver:self forKeyPath:@"operations" options:0 context:NULL]; 
    } 
} 

Теперь, когда вы звоните self.queue = [NSOperationQueue new];, все работает автоматически. Вы перестаете наблюдать за старой очередью и начинаете наблюдать за новой очередью. Если вы звоните self.queue = nil, он автоматически отменяет вас.

Вам все еще нужно, чтобы убедиться в разрегистрировать в dealloc:

- (void)dealloc { 
    if (_queue) { 
    [_queue removeObserver:self forKeyPath:@"operations"]; 
    } 
} 
+0

Итак, вы правы, каждый раз, когда popover открывается, он вызывает synchronizeToDevice. Таким образом, быстрое открытие/закрытие popover приводит к сбою. Тем не менее, я добавил собственный метод setQueue, который вы предоставили, но он по-прежнему падает. Я установил точки останова, чтобы убедиться, что он вызван, и он не уверен, почему он все еще падает. Какие-либо предложения? – JimmyJammed