Метод 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");
}
}