Метод stopRunning AVCaptureSession создает ужасное зависание

Используя программу чтения QRCode от Ray Wenderlich из главы 22 Руководства по iOS7, я успешно читаю QRCodes для моего текущего приложения. Сейчас я расширяю его, что после успешного чтения QRCode, я хочу сохранить stringValue из AVMetadataMachineReadableCodeObject это было прочитано, перейти к новому представлению и использовать эти данные в новом представлении, более или менее точно так, как большинство приложений для считывания QRCode (таких как RedLaser и т. д.) обрабатывают штрих-коды и QRCodes.

Тем не менее, я звоню [captureSession stopRunning] (чтобы он больше не считывал QRCodes и не вызывал дополнительные сегменты), и было 10-секундное зависание. Я пытался реализовать async звоните по этому такому вопросу, однако безрезультатно. Я также рассмотрел эти вопросы SO, и они, кажется, не подходят для этой цели.

У кого-нибудь есть идеи как убрать эту зависание?

Вот код:

#import "BMQRCodeReaderViewController.h"
#import "NSString+containsString.h"
#import "BMManualExperimentDataEntryViewController.h"
@import AVFoundation;

@interface BMQRCodeReaderViewController ()
    <AVCaptureMetadataOutputObjectsDelegate>
@end

@implementation BMQRCodeReaderViewController {
    AVCaptureSession *_captureSession;
    AVCaptureDevice *_videoDevice;
    AVCaptureDeviceInput *_videoInput;
    AVCaptureVideoPreviewLayer *_previewLayer;
    BOOL _running;
    AVCaptureMetadataOutput *_metadataOutput;
}

- (void)setupCaptureSession { // 1
    if (_captureSession) return;
    // 2
    _videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    if (!_videoDevice) {
        NSLog(@"No video camera on this device!"); return;
    }
    // 3
    _captureSession = [[AVCaptureSession alloc] init];
    // 4
    _videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:_videoDevice error:nil];
    // 5
    if ([_captureSession canAddInput:_videoInput]) { [_captureSession addInput:_videoInput];
    }
    // 6
    _previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession];
    _previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;

    _metadataOutput = [[AVCaptureMetadataOutput alloc] init];
    dispatch_queue_t metadataQueue = dispatch_queue_create("com.razeware.ColloQR.metadata", 0);

    [_metadataOutput setMetadataObjectsDelegate:self queue:metadataQueue];
    if ([_captureSession canAddOutput:_metadataOutput]) { [_captureSession addOutput:_metadataOutput];
    }
}

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects
       fromConnection:(AVCaptureConnection *)connection {

    // This fancy BOOL is just helping me fire the segue when the correct string is found
     __block NSNumber *didFind = [NSNumber numberWithBool:NO];

    [metadataObjects enumerateObjectsUsingBlock:^(AVMetadataObject *obj, NSUInteger idx, BOOL *stop) {

        AVMetadataMachineReadableCodeObject *readableObject = (AVMetadataMachineReadableCodeObject *)obj;

        NSLog(@"Metadata: %@", readableObject);

        // [ containsString is a category I extended for NSString, just FYI
        if ([readableObject.stringValue containsString:@"CorrectString"]) {

            didFind = [NSNumber numberWithBool:YES];
             NSLog(@"Found it");
             _testName = @"NameOfTest";
             *stop = YES;
             return;
         }
    }];

    if ([didFind boolValue]) {
        NSLog(@"Confirming we found it");
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [self stopRunning];
        });
        _labelTestName.text = _testName;
        [self performSegueWithIdentifier:@"segueFromFoundQRCode" sender:self];
    }
    else {
        NSLog(@"Did not find it");
    }
}

- (void)startRunning {
    if (_running)
        return;

    [_captureSession startRunning];
    _metadataOutput.metadataObjectTypes = _metadataOutput.availableMetadataObjectTypes;
    _running = YES;
}
- (void)stopRunning {
    if (!_running) return;

    [_captureSession stopRunning];
    _running = NO;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    [self setupCaptureSession];
    [self setupNavBar];
    [self startRunning];
    _previewLayer.frame = _previewView.bounds;
    [_previewView.layer addSublayer:_previewLayer];
}

1 ответ

Решение

Я последовательно решил проблему. Проблема заключалась в том, что вызов метода делегата

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects
       fromConnection:(AVCaptureConnection *)connection

работает в фоновом режиме. Это было определено с [NSThread isMainThread] вызов, который не удается.

Решение состоит в том, чтобы найти правильное значение stringValue из QRCode, остановить сеанс captureSession в фоновом режиме, а затем вызвать сеанс в главном потоке. Решение выглядит так:

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects
       fromConnection:(AVCaptureConnection *)connection {

    // This fancy BOOL is just helping me fire the segue when the correct string is found
     __block NSNumber *didFind = [NSNumber numberWithBool:NO];

    [metadataObjects enumerateObjectsUsingBlock:^(AVMetadataObject *obj, NSUInteger idx, BOOL *stop) {

        AVMetadataMachineReadableCodeObject *readableObject = (AVMetadataMachineReadableCodeObject *)obj;

        NSLog(@"Metadata: %@", readableObject);
        if ([NSThread isMainThread]) {
            NSLog(@"Yes Main Thread");
        }
        else {
            NSLog(@"Not main thread");
        }
        // [ containsString is a category I extended for NSString, just FYI
        if ([readableObject.stringValue containsString:@"Biomeme"]) {
            //NSLog(@"this is a test: %@", getTestName);
            didFind = [NSNumber numberWithBool:YES];
             NSLog(@"Found it");
             _testName = readableObject.stringValue;
             *stop = YES;
             return;
         }
    }];


    if ([didFind boolValue]) {
        NSLog(@"Confirming we found it");
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSDate *start = [NSDate date];

            [self stopRunning];
            NSLog(@"time took: %f", -[start timeIntervalSinceNow]);

            // *** Here is the key, make your segue on the main thread
            dispatch_async(dispatch_get_main_queue(), ^{
                [self performSegueWithIdentifier:@"segueFromFoundQRCode" sender:self];
                _labelTestName.text = _testName;
            });

        });



    }
    else {
        NSLog(@"Did not find it");
    }
}
Другие вопросы по тегам