0

いくつかの操作キューを実行している NSObject の共有シングルトン クラスがあります。これでクラッシュします。

[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];
    }
}

クラッシュログ:

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
4

2 に答える 2

2

「Key-Value監視プログラミングガイド」の変更通知の受信では、observeValueForKeyPathの実装例がコメント付きで示されています。

スーパークラスを実装している場合は、必ずスーパークラスの実装を呼び出してください。NSObjectはメソッドを実装していません。

クラスはのサブクラスであると言ったNSObjectので、を呼び出さないでください[super observeValueForKeyPath:...]

synchronizeToDevice同じ共有インスタンスで複数回呼び出すと、別の問題が発生します。その場合、新しいものを作成し、queueそのためのオブザーバーを登録します。ただし、古いキューのオブザーバーは削除されません。

結果として、observeValueForKeyPath「古いキュー」に対して呼び出され、チェック if (object == queue)が失敗して、superへの不要な呼び出しが発生する可能性があります。

したがって、 synchronizeToDevice複数回呼び出すことができる場合は、最初に古いオブザーバーを削除する必要があります。

于 2013-03-20T21:47:30.643 に答える
2

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"];
  }
}
于 2013-03-20T22:06:21.877 に答える