I’ve not had much experience with semaphores, nor with blocks. I’ve seen various suggestions for how to turn an asynchronous call into a synchronous one. In this case I just want to wait to be sure the lens of the iPhone has changed focus before I snap another picture.
I’ve added a completion block (with a little routine to prove that I’m seeing it). But how to block the rest of my code (running on the main thread) until I get the completion callback?
- (void) changeFocusSettings
{
if ([SettingsController settings].useFocusSweep)
{
// increment the focus setting
float tmp = [SettingsController settings].fsLensPosition;
float fstmp =[[SettingsController settings] nextLensPosition: [SettingsController settings].fsLensPosition]; // get next lensposition
[SettingsController settings].fsLensPosition = fstmp ;
tmp = [SettingsController settings].fsLensPosition;
if ([self.captureDevice lockForConfiguration: nil] == YES)
{
__weak typeof(self) weakSelf = self;
[self.captureDevice setFocusModeLockedWithLensPosition:tmp
completionHandler:^(CMTime syncTime) {
NSLog(#"focus over..time = %f", CMTimeGetSeconds(syncTime));
[weakSelf focusCompletionHandler : syncTime];
}];
}
}
}
- (bool) focusCompletionHandler : (CMTime)syncTime
{
NSLog(#"focus done, time = %f", CMTimeGetSeconds(syncTime));
return true;
}
changeFocusSettings is called from another routine entirely. I image some kind of semaphore set just inside changeFocusSettings and then the focuscompletionHandler resets it. But the details are beyond me.
Thank you.
I worked through it myself, it wasn't hard at all and it appears to be working. Here's the code in case it helps someone else. And if you happen to spot an error, please let me know.
dispatch_semaphore_t focusSemaphore;
...
- (bool) focusCompletionHandler : (CMTime)syncTime
{
dispatch_semaphore_signal(focusSemaphore);
return true;
}
- (void) changeFocusSettings
{
focusSemaphore = dispatch_semaphore_create(0); // create semaphone to wait for focuschange to complete
if ([SettingsController settings].useFocusSweep)
{
// increment the fsLensposition
float tmp = [SettingsController settings].fsLensPosition;
float fstmp =[[SettingsController settings] nextLensPosition: [SettingsController settings].fsLensPosition]; // get next lensposition
[SettingsController settings].fsLensPosition = fstmp ;
tmp = [SettingsController settings].fsLensPosition;
NSLog(#"focus setting = %f and = %f", tmp, fstmp);
if ([self.captureDevice lockForConfiguration: nil] == YES)
{
__weak typeof(self) weakSelf = self;
[self.captureDevice setFocusModeLockedWithLensPosition:tmp
completionHandler:^(CMTime syncTime) {
[weakSelf focusCompletionHandler : syncTime];
}];
dispatch_semaphore_wait(focusSemaphore, DISPATCH_TIME_FOREVER);
}
}
}
Related
I'm trying to replicate a fitness app similar to Runtastic's Fitness Apps.
Sit-Ups
This our first app that uses the phone’s built-in accelerometer to detect movement. You need to hold the phone against your chest then sit up quickly enough and high enough for the accelerometer to register the movement and the app to count 1 sit-up. Be sure to do a proper sit-up by going high enough!
I did a prototype app similar to this question here and tried to implement a way to count sit-ups.
- (void)viewDidLoad {
[super viewDidLoad];
int count = 0;
motionManager = [[CMMotionManager alloc]init];
if (motionManager.deviceMotionAvailable)
{
motionManager.deviceMotionUpdateInterval = 0.1;
[motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue currentQueue] withHandler:^(CMDeviceMotion *motion, NSError *error) {
// Get the attitude of the device
CMAttitude *attitude = motion.attitude;
// Get the pitch (in radians) and convert to degrees.
double degree = attitude.pitch * 180.0/M_PI;
NSLog(#"%f", degree);
dispatch_async(dispatch_get_main_queue(), ^{
// Update some UI
if (degree >=75.0)
{
//it keeps counting if the condition is true!
count++;
self.lblCount.text = [NSString stringWithFormat:#"%i", count];
}
});
}];
NSLog(#"Device motion started");
}
else
{
NSLog(#"Device motion unavailable");
}
}
The if condition statement works, as if I place the device on my chest and do a proper sit-up, but the problem about this if statement is that it will just continue counting and I would want it to only count when the device has gone back to it's original position.
Can anyone come up with a logical implementation for this?
A simple boolean flag did the trick:
__block BOOL situp = NO;
if (!situp)
{
if (degree >=75.0)
{
count++;
self.lblCount.text = [NSString stringWithFormat:#"%i", count];
situp = YES;
}
}
else
{
if (degree <=10.0)
{
situp = NO;
}
}
Not the best logical implementation here, but it gets the job done...
I'm currently trying make a queueHandler that takes an object array as input for executing drive commands on a simple Double robot. I'm currently trying to use GCD in order to serially execute my functions, but when I'm using dispatch_sync on my queue in won't wait until the NSTimer has run its course, but will continue to try and execute the commands from the next object in my array.
I have 3 functions, one which simply initializes an NSMutableArray(loadCommands) with 2 objects and runs the queueHandler, this is called when I toggle a switch. Then the queueHandler reads the variables from the objects(type, timing, queueNr) to determine what type of drive function will be executed and for how long. This I thought could be done in a switch statement, and I figured it would be great if the app could execute the function on the main thread(that is ok!) but it should wait until the NSTimer has run its course. I thought encapsulating the switch case with a dispatch_sync would solve this but it promptly skips to the next iteration in the loop and tries to execute the next function instead, which is drive backwards for 3 seconds.
When I test this with a single object in the array the command will be executed without trouble. I suppose I'm locking up the main thread somehow. Would perhaps waiting for a return value from the function in the #selector in the NSTimer statement help?
I've only played with Objective C for about 10 days, I'd appreciate any help I could get with this bit!
- (void)loadCommands {
//create an objectArray and put 2 objects inside it.
NSMutableArray *driveCommandsArray = [[NSMutableArray alloc] initWithCapacity:4];
//Command 1
DRCommands *C1 = [[DRCommands alloc] init];
C1.timing = 3;
C1.type = 1;
C1.queueNr = 1;
[driveCommandsArray addObject:C1];
//Command 2
DRCommands *C2 = [[DRCommands alloc] init];
C2.timing = 3;
C2.type = 2;
C2.queueNr = 2;
[driveCommandsArray addObject:C2];
//call queueHandler
[self queueHandler:driveCommandsArray];
}
Queue handler:
- (void)queueHandler: (NSMutableArray*) commandArray {
//Now, I'm not sure what I'm doing here, I watched a tutorial that
//solved a vaguely similar problem and he put a dispatch_async before the
//dispatch_sync. I can't run the dispatch_sync clause inside the case
//statement without this.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"Inside handler!");
unsigned long count;
count = [commandArray count]; //retrieve length/number of objects from the array.
unsigned long a;
for (a = 0; a < count;) {
//run the loop until all objects has been managed.
DRCommands* myObj = (DRCommands*)[commandArray objectAtIndex:a];
//create 2 serial queues.
dispatch_queue_t myQ1;
myQ1 = dispatch_queue_create("myQ1", NULL);
dispatch_queue_t myQ2;
myQ2 = dispatch_queue_create("myQ2", NULL);
int queueID = myObj.queueNr; //retrieve place in queue (not really used yet)
int timeID = myObj.timing; //retrieve the amount of time the command shall be run through the NSTimer
int typeID = myObj.type; //type of command
NSLog(#"Inside for loop!");
if (queueID == a+1) {
a++;
switch (typeID) {
{
case 1:
NSLog(#"inside case 1");
dispatch_sync(myQ1, ^{ //doesn't wait for NSTimer to finish,
//letting the Double drive forward for 3 seconds,
//before resuming operations.
counter_ = timeID;
seconds.text = [NSString stringWithFormat:#"%d", counter_];
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(jDriveForward) userInfo:nil repeats:YES];
});
break;
}
{
case 2:
NSLog(#"inside case 2");
dispatch_sync(myQ2, ^{
counter_ = timeID;
seconds.text = [NSString stringWithFormat:#"%d", counter_];
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(jDriveBackward) userInfo:nil repeats:YES];
});
break;
}
//add more cases
{
default:
break;
}
}
}
NSLog(#"Exited for loop, and count is %lu", a);
}
});
}
Drive commands:
//Go forward X seconds.
- (void)jDriveForward {
shouldDriveForward_ = YES; //sets a condition which is recognized by a callback function to run the device forward.
counter_ -= 1;
seconds.text = [NSString stringWithFormat:#"%d", counter_];
if (counter_ <= 0) {
[timer invalidate];
shouldDriveForward_ = NO;
}
}
//Go backwards X seconds.
- (void)jDriveBackward {
shouldDriveBackward_ = YES;
counter_ -= 1;
seconds.text = [NSString stringWithFormat:#"%d", counter_];
if (counter_ <= 0) {
[timer invalidate];
shouldDriveBackward_ = NO;
}
}
Provided drive function from the experimental API I'm using
I'm using a "token" such as "shouldDriveForward_" inside the function driveDoubleShouldUpdate which is TRUE for the duration of an NSTimer. I must call my drive methods inside that function for the robot not to default to idle mode. So whenever it is true for X duration, the function for driving forwards or backwards is active.
- (void)doubleDriveShouldUpdate:(DRDouble *)theDouble {
float drive = (driveForwardButton.highlighted) ? kDRDriveDirectionForward : ((driveBackwardButton.highlighted) ? kDRDriveDirectionBackward : kDRDriveDirectionStop);
float turn = (driveRightButton.highlighted) ? 1.0 : ((driveLeftButton.highlighted) ? -1.0 : 0.0);
[theDouble drive:drive turn:turn];
//below are custom functions
//The NSTimer I'm using keep the BOOL values below TRUE for X seconds,
//making the robot go forward/backward through this callback
//method, which I must use
if(shouldDriveForward_ == YES) {
[theDouble variableDrive:(float)1.0 turn:(float)0.0];
}
if(shouldDriveBackward_ == YES) {
[theDouble variableDrive:(float)-1.0 turn:(float)0.0];
}
}
You're kind of jumbled up here with the combination of GCD and NSTimer. There's nothing to say they can't be intermixed, but an all-GCD approach might be easier to get your head around. I think I've discerned the gist of what you're trying to do here, and hacked something together that might be helpful. I've put the whole project up on GitHub, but here's the meat of it:
#import "ViewController.h"
typedef NS_ENUM(NSUInteger, DRCommandType) {
DRCommandUnknown = 0,
DRCommandTypeForward = 1,
DRCommandTypeBackward = 2,
};
#interface DRCommand : NSObject
#property DRCommandType type;
#property NSTimeInterval duration;
#end
#implementation DRCommand
#end
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UILabel *commandNameLabel;
#property (weak, nonatomic) IBOutlet UILabel *secondsRemainingLabel;
#property (strong, atomic) DRCommand* currentlyExecutingCommand;
#property (copy, atomic) NSNumber* currentCommandStarted;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do an initial UI update
[self updateUI];
}
- (IBAction)loadCommands:(id)sender
{
DRCommand *C1 = [[DRCommand alloc] init];
C1.duration = 3.0;
C1.type = DRCommandTypeForward;
DRCommand *C2 = [[DRCommand alloc] init];
C2.duration = 3.0;
C2.type = DRCommandTypeBackward;
[self handleCommands: #[ C1, C2 ]];
}
- (void)handleCommands: (NSArray*)commands
{
// For safety... it could be a mutable array that the caller could continue to mutate
commands = [commands copy];
// This queue will do all our actual work
dispatch_queue_t execQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
// We'll target the main queue because it simplifies the updating of the UI
dispatch_set_target_queue(execQueue, dispatch_get_main_queue());
// We'll use this queue to serve commands one at a time...
dispatch_queue_t latchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
// Have it target the execQueue; Not strictly necessary but codifies the relationship
dispatch_set_target_queue(latchQueue, execQueue);
// This timer will update our UI at 60FPS give or take, on the main thread.
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, (1.0/60.0) * NSEC_PER_SEC, (1.0/30.0) * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{ [self updateUI]; });
// Suspend the latch queue until we're ready to go
dispatch_suspend(latchQueue);
// The first thing to do for this command stream is to start UI updates
dispatch_async(latchQueue, ^{ dispatch_resume(timer); });
// Next enqueue each command in the array
for (DRCommand* cmd in commands)
{
dispatch_async(latchQueue, ^{
// Stop the queue from processing other commands.
dispatch_suspend(latchQueue);
// Update the "machine state"
self.currentlyExecutingCommand = cmd;
self.currentCommandStarted = #([NSDate timeIntervalSinceReferenceDate]);
// Set up the event that'll mark the end of the command.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(cmd.duration * NSEC_PER_SEC)), execQueue, ^{
// Clear out the machine state for the next command
self.currentlyExecutingCommand = nil;
self.currentCommandStarted = nil;
// Resume the latch queue so that the next command starts
dispatch_resume(latchQueue);
});
});
}
// After all the commands have finished, add a cleanup block to stop the timer, and
// make sure the UI doesn't have stale text in it.
dispatch_async(latchQueue, ^{
dispatch_source_cancel(timer);
[self updateUI];
});
// Everything is queued up, so start the command queue
dispatch_resume(latchQueue);
}
- (void)updateUI
{
// Make sure we only ever update the UI on the main thread.
if (![NSThread isMainThread])
{
dispatch_async(dispatch_get_main_queue(), ^{ [self updateUI]; });
return;
}
DRCommand* currentCmd = self.currentlyExecutingCommand;
switch (currentCmd.type)
{
case DRCommandUnknown:
self.commandNameLabel.text = #"None";
break;
case DRCommandTypeForward:
self.commandNameLabel.text = #"Forward";
break;
case DRCommandTypeBackward:
self.commandNameLabel.text = #"Backward";
break;
}
NSNumber* startTime = self.currentCommandStarted;
if (!startTime || !currentCmd)
{
self.secondsRemainingLabel.text = #"";
}
else
{
const NSTimeInterval startTimeDbl = startTime.doubleValue;
const NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate];
const NSTimeInterval duration = currentCmd.duration;
const NSTimeInterval remaining = MAX(0, startTimeDbl + duration - currentTime);
self.secondsRemainingLabel.text = [NSString stringWithFormat: #"%1.3g", remaining];
}
}
#end
Let me know in a comment if there's any part you'd like more explanation on.
Note: The other answer here has the command doing a sleep; my approach is fully asynchronous. Which approach is right for you will depend on what your commands are actually doing which wasn't clear from the question.
You only need a single serial dispatch queue to which you will add your tasks.
I would start by defining a task class which implements your various commands - you can subclass if required.
DRCommand.h
#import <Foundation/Foundation.h>
#interface DRCommand : NSObject
#property uint duration;
-(void) dispatch;
#end
DRCommand.m
#import "DRCommand.h"
#implementation DRCommand
-(void)dispatch {
[self startCommand];
sleep(self.duration);
[self stopCommand];
}
-(void) startCommand {
NSLog(#"Override this method to actually do something");
}
-(void) stopCommand {
NSLog(#"Override this method to stop doing something");
}
#end
Then your run queue code will be something like
-(void) runQueue {
DRCommand *c1=[DRCommand new];
c1.duration=5;
DRCommand *c2=[DRCommand new];
c2.duration=7;
DRCommand *c3=[DRCommand new];
c3.duration=3;
NSArray *taskArray=#[c1,c2,c3];
dispatch_queue_t queue;
queue = dispatch_queue_create("com.example.MyQueue", NULL);
for (DRCommand *command in taskArray) {
dispatch_async(queue, ^{
[command dispatch];
});
}
}
Note that you would have subclasses of DRCommand such as DRForwardCommand, DRBackwardCommand and so on, each with appropriate startCommand and stopCommand methods.
I have a block where I am checking a user's status property from firebase. If the status property is 'free' I want to return from the block, otherwise I want to search for another user and check their status and do so until a 'free' user has been found:
void( ^ myResponseBlock)(BOOL finished) = ^ void(BOOL finished) {
if (finished) {
if ([self.freedom isEqualToString: #"free"]) {
NSLog(#"free!");
return;
} else if ([self.freedom isEqualToString: #"matched"]) {
NSLog(#"get another user");
//get another user
do {
//picking another random user from array
rando = arc4random_uniform(arraycount);
}
while (rando == randomIndex && rando == [self.randString intValue]);
self.randString = [NSString stringWithFormat: #"%u", rando];
[users removeAllObjects];
[users addObject:usersArray[rando]];
self.freeUser = users.firstObject;
NSLog(#"set up another check");
//error is called after this block is called here, again
[self checkIfFree: myResponseBlock];
} else {
NSLog(#"error!");
}
} else {
NSLog(#"not finished the checking yet");
}
};
[self checkIfFree: myResponseBlock];
As shown, I'm getting an error of 'BAD ACCESS' when the block is called for a second time on the 'compblock(YES)' line below:
-(void)checkIfFree:(myCompletion) compblock{
self.freeUserFB = [[Firebase alloc] initWithUrl:[NSString stringWithFormat: #"https://skipchat.firebaseio.com/users/%#", self.freeUser.objectId]];
[self.freeUserFB observeEventType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot)
{
self.otherStatus = snapshot.value[#"status"];
NSLog(#"snapshot info %#", snapshot.value);
if ([self.otherStatus isEqualToString:#"free"]) {
self.userIsFree = YES;
self.freedom = #"free";
NSLog(#"user is free in the check method %#", self.freedom);
}
else{
self.userIsFree = NO;
self.freedom = #"matched";
NSLog(#"user is matched in the check method %#", self.freedom);
}
compblock(YES);
}];
}
Everything is fine if the block does not have to be recalled and the first user that's checked is already 'free'. I'm stuck as to why I'm getting this error/crash and wondering how I can solve it!
Thanks!
A block captures all variables passed in including itself, however the variable myResponseBlock has not been initialized yet inside the block. Because of this, you are calling checkIfFree method with a nil value which in turn causing app to crash.
One thing you can do to overcome this would be declaring your block as a __block variable.
__block __weak void(^weakResponseBlock)(BOOL);
void(^myResponseBlock)(BOOL);
weakResponseBlock = myResponseBlock = ^void(BOOL finished) {
...
if (weakResponseBlock) {
[self checkIfFree:weakResponseBlock];
}
}
Additionally, please note that blocks retain all variables passed into them. In this case, you are retaining self inside the block, so it will never get deallocated as long as block executes. So unless required otherwise, always pass a weak reference to blocks.
__weak UIViewController *weakSelf = self;
weakResponseBlock = myResponseBlock = ^void(BOOL finished) {
...
if (weakResponseBlock) {
[weakSelf checkIfFree:weakResponseBlock];
}
}
I think still you might have an error because all your blocks are being created on the stack. So if anything async should happen the myResponseBlock will go away.
What I'd recommend is your copy (using the a property with copy attribute) your myResponse block into a property and reuse it from there. That way your block lives on the heap and goes away when self is set to nil.
OK, here is the code.
NSObject* (^executableBlock)(void) = ^NSObject*() {
__block NSObject *refObj = nil;
[Utility performAction:^() {
if (conditionA)
refObj = fooA;
else
refObj = fooB;
};
return refObj;
};
NSObject *result = executableBlock(); // result is nil
After executing the executableBlock, the result is nil and performAction block didn't be executed immediately and returned my expected value.
I know performAction block is executed within another thread and using the shared nil pointer refObj. Refer to Working with Blocks.
Here is my through, if I use GCD to call the performAction block and wait for its finish, how to rewrite it? Thanks!
By using semaphore, you can wait until a block is done.
But completion-block may be suitable in your case, i think.
NSObject* (^executableBlock)(void) = ^NSObject*() {
__block NSObject *refObj = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self performAction:^() {
if (conditionA)
refObj = fooA;
else
refObj = fooB;
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return refObj;
};
I would considering the following re-structure:
__block NSObject * result;
void (^executableBlock)(NSObject *) = ^void (NSObject * obj)
{
[self performAnActionWithBlock:^(BOOL success)
{
if (success) {
result = obj;
NSLog(#"results: %#", result);
}else{
result = nil;
}
}];
};
executableBlock(#"Hello");
//where:
-(void)performAnActionWithBlock:(void(^)(BOOL))block{
block(YES);
}
You should then be able to call result from elsewhere
void (^executableBlock)(NSObject*) = ^(NSObject *arg) {
[Utility performAction:^(NSObject *arg) {
if (conditionA)
arg = fooA;
else
arg = fooB;
}];
};
__block NSObject *result = nil;
executableBlock(result); // result will be changed in block
Currently I am working on an app to capture images of different exposurePointOfInterest. Basically the steps to that are:
Set focus on point A
Capture
Set focus on point B
Capture
I had to put a redundant for loop between step 1 & 2 and step 3 & 4 to allow some time for the lens to actually focus on the intended points, otherwise both captures at step 2 & 4 would result in the same picture. This works perfectly. But, I believe this is not the best way to solve this problem.
I have tried putting this code instead of the for loop:
[self performSelector:#selector(captureStillImage) withObject:#"Grand Central Dispatch" afterDelay:1.0]
But when I ran it, it ran as if the selector captureStillImage is never executed. Is there anything that I did wrong? Or is there a better solution that anyone can advise me?
The function I call to capture multiple images looks like this:
-(void)captureMultipleImg
{
//CAPTURE FIRST IMAGE WITH EXPOSURE POINT(0,0)
[self continuousExposeAtPoint:CGPointMake(0.0f, 0.0f)];
NSLog(#"Looping..");
for(int i=0; i<100000000;i++){
}
NSLog(#"Finish Looping");
[self captureStillImage];
//CAPTURE FIRST IMAGE WITH EXPOSURE POINT(0,0)
[self continuousExposeAtPoint:CGPointMake(0.5f, 0.5f)];
NSLog(#"Looping..");
for(int i=0; i<100000000;i++){
}
NSLog(#"Finish Looping");
[self captureStillImage];
}
And the code for captureStillImage looks like this:
-(void)captureStillImage
{
AVCaptureConnection *connection = [stillImage connectionWithMediaType:AVMediaTypeVideo];
typedef void(^MyBufBlock)(CMSampleBufferRef, NSError*);
MyBufBlock h = ^(CMSampleBufferRef buf, NSError *err){
NSData *data = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:buf];
[self setToSaveImage:[UIImage imageWithData:data]];
NSLog(#"Saving to Camera Roll..");
//Saving photo to camera roll
UIImageWriteToSavedPhotosAlbum(toSaveImage, self, #selector(image:didFinishSavingWithError:contextInfo:), nil);
toSaveImage = NULL;
};
[stillImage captureStillImageAsynchronouslyFromConnection:connection completionHandler:h];
}
The code for continuousExposeAtPoint: function:
-(void)continuousExposeAtPoint:(CGPoint)point
{
if([device isExposurePointOfInterestSupported] && [device isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]){
if([device lockForConfiguration:NULL]){
[device setExposurePointOfInterest:point];
[device setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
[device unlockForConfiguration];
NSLog(#"Exposure point of intereset has been set to (%f,%f)",point.x, point.y);
}
}
}
Thanks in advance!
Instead of the dummy loop you can use performSelector:withObject:afterDelay:
I'm going out of a limb here, since I would like to suggest a different approach which completely avoids "busy waiting" or "run loop waiting".
If I understood the camera correctly, it may take a certain duration until after the exposure point has been set by the camera. There is the property adjustingFocus which reflects this state of the camera. This property is KVO compliant and we can use KVO to observe its value.
So, the idea is to set the exposure point, and then observe the property adjustingFocus. When it's value changes to NO, the camera is finished setting the exposure point.
Now, we can leverage KVO to call a completion hander immediately after the setting is complete. Your method to setup the exposure point becomes asynchronous with a completion handler:
typedef void (^completion_t) ();
-(void)continuousExposeAtPoint:(CGPoint)point
completion:(completion_t)completionHandler;
Assuming you have properly implemented KVO in the method above you can use it as follows:
-(void)captureMultipleImg
{
[self continuousExposeAtPoint:CGPointMake(0.0f, 0.0f) completion:^{
[self captureStillImage];
[self continuousExposeAtPoint:CGPointMake(0.5f, 0.5f) completion:^{
[self captureStillImage];
}];
}];
}
Edit:
Now, method captureMultipleImg became asynchronous as well.
Note:
A method invoking an asynchronous method becomes itself asynchronous.
Thus, in order to let the call-site know when its underlying asynchronous task is finished, we may provide a completion handler:
typedef void (^completion_t)();
-(void)captureMultipleImagesWithCompletion:(completion_t)completionHandler
{
[self continuousExposeAtPoint:CGPointMake(0.0f, 0.0f) completion:^{
[self captureStillImage];
[self continuousExposeAtPoint:CGPointMake(0.5f, 0.5f) completion:^{
[self captureStillImage];
if (completionHandler) {
completionHandler();
}
}];
}];
}
A button action may be implemented as follows:
- (void)captureImages {
[self showLabel];
self.captureImagesButton.enabled = NO;
[manager captureMultipleImagesWithCompletion:^{
dispatch_async(dispatch_get_main_queue(), ^{
[self hideLabel];
self.captureImagesButton.enabled = NO;
});
}];
}
Edit:
For a jump start, you may implement the KVO and your method as shown below. Caution: not tested!
-(void)continuousExposeAtPoint:(CGPoint)point
completion:(completion_t)completionHandler
{
AVCaptureDevice* device; // ...;
if([device isExposurePointOfInterestSupported] && [device isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]){
if([device lockForConfiguration:NULL]){
[device addObserver:self forKeyPath:#"adjustingExposure"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:(__bridge_retained void*)([completionHandler copy])];
[device setExposurePointOfInterest:point];
[device setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
}
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object change:(NSDictionary *)change
context:(void *)context
{
AVCaptureDevice* device; // = ...;
if ([keyPath isEqual:#"adjustingExposure"]) {
if ([[change objectForKey:NSKeyValueChangeNewKey] boolValue] == NO) {
CGPoint point = device.exposurePointOfInterest;
NSLog(#"Exposure point of intereset has been set to (%f,%f)",point.x, point.y);
[device removeObserver:self forKeyPath:#"adjustingExposure"];
[device unlockForConfiguration];
completion_t block = CFBridgingRelease(context);
if (block) {
block();
}
}
}
// Be sure to call the superclass's implementation *if it implements it.
// NSObject does not implement the method.
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
The caveat here is, that KVO is difficult to setup. But once you managed to wrap it into a method with a completion handler it looks much nicer ;)
maybe is your code running when the run loop is in a mode other than the default mode? try this:
[self performSelector:#selector(mywork:) withObject:nil
afterDelay:delay
inModes:#[[[NSRunLoop currentRunLoop] currentMode]]];
Use dispatch after:
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// Code
});
I personally tend to use delays in blocks on the main thread like this:
double delayInSeconds = 0.5;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
//Do your thing here
});
have you tried using a timer?
if performSelector:withObject:afterDelay: not work you can try:
[NSTimer scheduledTimerWithTimeInterval:1.5 target:self selector:#selector(captureStillImage) userInfo:nil repeats:NO];
Try this with less amount of time like 2 seconds
[self performSelector:#selector(yourMethod:) withObject:yourObject afterDelay:0.2];
You should know performSelector is work on the calling thread, if the calling thread is not executing, the selector is not called.
So I think the reason why performSelector:withObject:afterDelay: does not work is your thread execute captureMultipleImg method is not work after the delay time.
If you call captureMultipleImg with dispatch_async , same reason.
Let's say you call the method in dispatch_async
- (void)testCode
{
[self performSelector:#selector(mywork:) withObject:nil afterDelay:0.1] ;
[self endWork] ;
}
after the endWork is executed, the calling thread may be released , so - (void)mywork:(id)objis never called.