ios – 使用键值观察器锁定exposureMode会导致崩溃

我正在屏幕区域进行一些运动检测.在开始检测之前,我想设置焦距和曝光并锁定它们,这样它们就不会触发假动作.因此,我将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

解决方法

你不会在任何文档中找到这个(即我没有“证据”),但是我可以告诉你痛苦的个人经历,包括很多天(如果不是几周)的调试,这种崩溃是由为该属性的KVO通知处理程序内的属性添加/删除观察者引起的. (根据我的经验,在堆栈跟踪中存在NSKeyValuePopPendingNotificationPerThread是“冒烟的枪”.)我还经验性地观察到给定属性的观察者被通知的顺序是非确定性的,因此即使添加或删除观察者内部通知处理程序在某些时候工作,它可以在不同情况下任意失败. (我假设在KVO的某个地方有一个无序的数据结构可以在不同的顺序中枚举,可能基于指针的数值或任意类似的东西.)在过去,我已经解决过这可以通过在设置属性之前/之后立即发布NSNotification来为观察者提供添加/删除自己的机会.这是一个笨重的模式,但它比崩溃更好(并允许我继续使用依赖于KVO的其他东西,比如绑定.)

另外,请注意,在您发布的代码中,您没有使用上下文来识别您的观察结果,并且您没有在observeValueForKeyPath:…实现中调用super.这两件事都可能导致细微的,难以诊断的错误. KVO的防弹模式看起来像这样:

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:…当你dealloc时),然后使用一些名为“门”的行为“ waitingForFocus和waitingForExposure.在-focusAtPoint当前addObserver:…而是将ivars设置为YES.然后在observeValueForKeyPath:…只有当这些ivars为YES然后而不是removeObserver时才采取行动:…只需将ivars设置为NO.这应该具有所需的效果,而不需要每次添加和删除观察.

我想到的另一种方法是使用GCD调用removeObserver:…“以后”.所以你会改变removeObserver:…像这样:

dispatch_async(dispatch_get_main_queue(),^{ [device removeObserver:self forKeyPath:keyPath context:context]; });

这将导致在通知过程完成后在运行循环中的其他位置进行调用.这稍微不那么防弹,因为没有什么可以保证在发生延迟删除调用之前第二次不会触发通知.在这方面,第一种方法在实现期望的一次性行为方面更严格地“正确”.

编辑2:我不能放手. :)我弄清楚你为什么会崩溃.我观察到,在用于adjustExposure的KVO处理程序中设置exposureMode最终会导致另一个adjustExposure通知,因此堆栈会一直爆炸,直到您的进程被终止.我能够通过包装observeValueForKeyPath的部分来实现它的工作:…处理dispatch_async(dispatch_get_main_queue(),^ {…})中adjustExposure的更改; (包括最终的removeObserver:…调用).在此之后它对我有用,并且肯定锁定曝光和焦点.也就是说,就像我上面提到的那样,这可以用ivars更好地处理以防止递归而不是任意延迟的dispatch_async().

希望有所帮助.

相关文章

当我们远离最新的 iOS 16 更新版本时,我们听到了困扰 Apple...
欧版/美版 特别说一下,美版选错了 可能会永久丧失4G,不过只...
一般在接外包的时候, 通常第三方需要安装你的app进行测...
前言为了让更多的人永远记住12月13日,各大厂都在这一天将应...