Блокировка ExmoMode с наблюдателем значения ключа вызывает сбой
Я делаю некоторые обнаружения движения в области экрана. Перед началом обнаружения я хочу установить фокусировку и экспозицию и заблокировать их, чтобы они не вызывали ложного движения. Поэтому я отправляю AVCaptureFocusModeAutoFocus и AVCaptureExposureModeAutoExpose на устройство и добавляю KeyvalueObserver. Когда наблюдатель говорит, что он закончил фокусировку и изменяет экспозицию, он блокирует их (и начинает обнаружение движения). С фокусом все работает нормально, но блокировка экспозиции приводит к сбою приложения в течение нескольких секунд ", несмотря на наличие идентичного кода в обоих случаях.
static void * const MyAdjustingFocusObservationContext = (void*)&MyAdjustingFocusObservationContext;
static void * const MyAdjustingExposureObservationContext = (void*)&MyAdjustingExposureObservationContext;
-(void)focusAtPoint{
CGPoint point;
if(fromRight) point.x = 450.0/480.0;
else point.x = 30.0/480.0;
point.y = 245.0/320.0;
AVCaptureDevice *device =[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if(device != nil) {
NSError *error;
if([device lockForConfiguration:&error]){
if([device isExposureModeSupported:AVCaptureFocusModeContinuousAutoFocus] && [device isFocusPointOfInterestSupported]) {
[device setFocusPointOfInterest:point];
[device setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
[device addObserver:self forKeyPath:@"adjustingFocus" options:NSKeyValueObservingOptionNew context:MyAdjustingFocusObservationContext];
NSLog(@"focus now");
}
if([device isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure] && [device isExposurePointOfInterestSupported]) {
[device setExposurePointOfInterest:point];
[device setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
[device addObserver:self forKeyPath:@"adjustingExposure" options:NSKeyValueObservingOptionNew context:MyAdjustingExposureObservationContext];
NSLog(@"expose now");
}
[device unlockForConfiguration];
}else{
NSLog(@"Error in Focus Mode");
}
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error;
if([keyPath isEqualToString:@"adjustingFocus"]){
if(![object isAdjustingFocus]){
[device removeObserver:self forKeyPath:keyPath context:context];
if([device isFocusModeSupported:AVCaptureFocusModeLocked]) {
[device lockForConfiguration:&error];
device.focusMode = AVCaptureFocusModeLocked;
[device unlockForConfiguration];
NSLog(@" focus locked");
}
}
}
if([keyPath isEqualToString:@"adjustingExposure"]){
if(![object isAdjustingExposure]){
[device removeObserver:self forKeyPath:keyPath context:context];
if([device isExposureModeSupported:AVCaptureExposureModeLocked]) {
[device lockForConfiguration:&error];
device.exposureMode=AVCaptureExposureModeLocked; //causes the crash
[device unlockForConfiguration];
NSLog(@" exposure locked");
}
}
}
Если я закомментирую строку "device.exposureMode=AVCaptureExposureModeLocked", все работает нормально (за исключением того, что фокус не блокируется). Если я перемещаю линию к наблюдателю фокуса, все работает нормально (за исключением того, что экспозиция иногда блокируется, прежде чем она будет установлена правильно). Если я блокирую экспозицию другим способом, например, через таймер, это работает.
Журнал аварий не очень мне помогает (надеюсь, кто-то может его интерпретировать)
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x00000000
Crashed Thread: 0
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 Foundation 0x3209d5e2 NSKVOPendingNotificationRelease + 6
1 CoreFoundation 0x317b21c8 __CFArrayReleaseValues + 352
2 CoreFoundation 0x317419f8 _CFArrayReplaceValues + 308
3 CoreFoundation 0x3174391c CFArrayRemoveValueAtIndex + 80
4 Foundation 0x3209d6b6 NSKeyValuePopPendingNotificationPerThread + 38
5 Foundation 0x32090328 NSKeyValueDidChange + 356
6 Foundation 0x3206a6ce -[NSObject(NSKeyValueObserverNotification) didChangeValueForKey:] + 90
7 AVFoundation 0x30989fd0 -[AVCaptureFigVideoDevice handleNotification:payload:] + 1668
8 AVFoundation 0x30983f60 -[AVCaptureDeviceInput handleNotification:payload:] + 84
9 AVFoundation 0x3098fc64 avcaptureSessionFigRecorderNotification + 924
10 AVFoundation 0x309b1c64 AVCMNotificationDispatcherCallback + 188
11 CoreFoundation 0x317cee22 __CFNotificationCenterAddObserver_block_invoke_0 + 122
12 CoreFoundation 0x31753034 _CFXNotificationPost + 1424
13 CoreFoundation 0x3175460c CFNotificationCenterPostNotification + 100
14 CoreMedia 0x31d3db8e CMNotificationCenterPostNotification + 114
15 Celestial 0x34465aa4 FigRecorderRemoteCallbacksServer_NotificationIsPending + 628
16 Celestial 0x34465826 _XNotificationIsPending + 66
17 Celestial 0x344657dc figrecordercallbacks_server + 96
18 Celestial 0x34465028 remrec_ClientPortCallBack + 172
19 CoreFoundation 0x317cc5d8 __CFMachPortPerform + 116
20 CoreFoundation 0x317d7170 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 32
21 CoreFoundation 0x317d7112 __CFRunLoopDoSource1 + 134
22 CoreFoundation 0x317d5f94 __CFRunLoopRun + 1380
23 CoreFoundation 0x31748eb8 CFRunLoopRunSpecific + 352
24 CoreFoundation 0x31748d44 CFRunLoopRunInMode + 100
25 GraphicsServices 0x3530c2e6 GSEventRunModal + 70
26 UIKit 0x3365e2fc UIApplicationMain + 1116
27 ShootKing 0x000ed304 main (main.m:16)
28 ShootKing 0x000ed28c start + 36
1 ответ
Вы не найдете этого ни в одной документации (то есть у меня нет "доказательств"), но я могу сказать вам по болезненному, личному опыту, состоящему из многих дней (если не недель) отладки, что этот вид сбоя вызвано добавлением / удалением наблюдателей для свойства внутри обработчика уведомлений KVO для этого свойства. (Наличие NSKeyValuePopPendingNotificationPerThread
в моём опыте - "дымящая пушка".) Я также эмпирически заметил, что порядок уведомления наблюдателей определенного свойства недетерминирован, поэтому даже если добавление или удаление наблюдателей в обработчиках уведомлений работает времени он может произвольно потерпеть неудачу при других обстоятельствах. (Я предполагаю, что в кишечнике KVO есть неупорядоченная структура данных, которая может быть перечислена в разных порядках, возможно, на основе числового значения указателя или чего-то подобного.) В прошлом я обходил это путем публикации NSNotification непосредственно перед / после установки свойства, чтобы дать наблюдателям возможность добавлять / удалять себя. Это неуклюжий шаблон, но он лучше, чем сбой (и позволяет мне продолжать использовать другие вещи, которые зависят от KVO, такие как привязки).
Кроме того, как примечание, я заметил в коде, который вы разместили, что вы не используете контексты для идентификации своих наблюдений, и вы не вызываете super в своем observeValueForKeyPath:...
реализация. Обе эти вещи могут привести к тонким, трудно диагностируемым ошибкам. Более пуленепробиваемый шаблон для КВО выглядит так:
static void * const MyAdjustingFocusObservationContext = (void*)&MyAdjustingFocusObservationContext;
static void * const MyAdjustingExposureObservationContext = (void*)&MyAdjustingExposureObservationContext;
- (void)focusAtPoint
{
// ... other stuff ...
[device addObserver:self forKeyPath:@"adjustingFocus" options:NSKeyValueObservingOptionNew context:MyAdjustingFocusObservationContext];
[device addObserver:self forKeyPath:@"adjustingExposure" options:NSKeyValueObservingOptionNew context:MyAdjustingExposureObservationContext];
// ... other stuff ...
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == MyAdjustingFocusObservationContext)
{
// Do stuff
}
else if (context == MyAdjustingExposureObservationContext)
{
// Do other stuff
}
else
{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
РЕДАКТИРОВАТЬ: Я хотел последовать, чтобы увидеть, могу ли я помочь больше в этой конкретной ситуации. Из кода и ваших комментариев я понял, что вы ищете, чтобы эти наблюдения были эффективными. Я вижу два способа сделать это:
Более простой и пуленепробиваемый подход заключается в том, чтобы этот объект всегда наблюдал устройство захвата (т.е. addObserver:...
когда вы начинаете, removeObserver:...
когда вы имеете дело с), но затем "гейт" поведение, используя пару иваров называется waitingForFocus
а также waitingForExposure
, В -focusAtPoint
где ты сейчас addObserver:...
вместо этого установите ivars на YES
, Затем в observeValueForKeyPath:...
принимать меры только в том случае, если YES
а потом вместо removeObserver:...
просто установите ivars на NO
, Это должно иметь желаемый эффект без необходимости добавлять и удалять наблюдения каждый раз.
Другой подход, о котором я подумал, - это позвонить removeObserver:...
"позже" с использованием GCD. Так что вы бы изменили removeObserver:...
как это:
dispatch_async(dispatch_get_main_queue(), ^{ [device removeObserver:self forKeyPath:keyPath context:context]; });
Это приведет к тому, что этот вызов будет выполнен в другом месте цикла выполнения после завершения процесса уведомления. Это немного менее пуленепробиваемо, потому что нет ничего, что гарантировало бы, что уведомление не будет запущено во второй раз, пока не произойдет отложенный вызов удаления. В этом отношении первый подход является более строго "правильным" в достижении желаемого однократного поведения.
РЕДАКТИРОВАТЬ 2: Я просто не мог отпустить.:) Я понял, почему ты грохнулся. Я наблюдаю эту настройку exposureMode
в то время как в обработчике KVO для adjustingExposure
в конечном итоге вызывает другое уведомление для adjustingExposure
и поэтому стек взрывается, пока ваш процесс не будет уничтожен. Я смог заставить его работать, обернув часть observeValueForKeyPath:...
который обрабатывает изменения в adjustingExposure
в dispatch_async(dispatch_get_main_queue(), ^{...});
(включая возможный removeObserver:...
вызов). После этого это сработало для меня, и определенно блокировало экспозицию и фокус. Тем не менее, как я уже упоминал выше, это, вероятно, будет лучше обрабатываться с помощью иваров, чтобы предотвратить рекурсию, а не произвольно задерживаться dispatch_async()
,
Надеюсь, это поможет.