Objective-C Implement a timeout in non-blocking way - objective-c

I am invoking an external async function that should invoke a callback once it completes.
However since the function is external, I do not control its implementation and I want to set a timeout for 5 seconds as an example and consider the operation of being timed out if the callback passed to that external async function wasn't invoked during those 5 seconds.
And the only way I currently found is to make the current thread sleep that actually blocks the thread.
Here is an example:
+(void)myFuncWithCompletion:(void (^ _Nonnull)(BOOL))completion{
BOOL timedOut = NO;
BOOL __block finishedAsyncCall = NO;
[someObj someAsyncMethod {
// completion callback
finishedAsyncCall = YES;
if (!timedOut) {
completion(YES);
}
}];
// This is the logic I want to fix. My goal is to make something similar but non-blocking.
long timeoutInSeconds = 5;
long startTime = [[NSDate date] timeIntervalSince1970];
long currTime = [[NSDate date] timeIntervalSince1970];
while (!finishedAsyncCall && startTime + timeoutInSeconds > currTime) {
[NSThread sleepForTimeInterval:0];
currTime = [[NSDate date] timeIntervalSince1970];
}
if (!finishedAsyncCall) {
timedOut = YES;
completion(NO);
}
}

You can use dispatch_after instead of -[NSThread sleepForTimeInterval:]
double delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); // 1
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ // 2
if (!finishedAsyncCall ) {
timedOut = YES;
completion(NO);
}
});

Related

How to implement a while loop with timeout?

I want to implement a while loop that exits either when a particular condition is met, or when a timer times out.
If I just start the timer (to set an object variable on timeout), and then start the while loop (checking the object variable), it doesn't work, because the timer never times out.
I've tried 3 of the solutions suggested in How to wait for a thread to finish in Objective-C to make the loop run in a separate function on another thread, but they fail in various different ways. I have not yet managed to get a test run where the timer times out.
The simple implementation was
//object variable
BOOL m_readTimedOut;
- (void) someFunction
{
m_readTimedOut = NO;
float timeoutS = 0.1;
//Start the timer
SEL readTimeoutSelector = sel_registerName("onReadTimeout:");
[NSTimer scheduledTimerWithTimeInterval:timeoutS
target:self
selector:readTimeoutSelector
userInfo:nil
repeats:NO];
int numBytesToRead = 1;
BOOL exit = NO;
int counter = 0;
while ((counter < numBytesToRead) && (exit == NO))
{
#try
{
#synchronized (readBufferLock)
{
//m_readBuffer is an object variable that can be altered on another thread
if ([m_readBuffer length] > 0)
{
//Do stuff here
counter++;
}
} //end synchronised
}
#catch (NSException *exception)
{
//Log exception
}
if (counter == numBytesToRead || m_readTimedOut == YES)
{
exit = YES;
}
} //end while
}
- (void)onReadTimeout:(NSTimer *)timer
{
NSLog(#"Read timer timed out");
m_readTimedOut = YES;
}
Just a try on the timed exit only - how about
NSDate * start = [[NSDate alloc] init]; // When we started
while ( counter < something )
{
// do stuff ...
// Check time
NSDate * now = [[NSDate alloc] init];
// Been here more than 10s since start
if ( [now timeIntervalSinceDate:start] > 10 )
{
// Timed exit
break;
}
}

Implement a Job Scheduler using Dispatch Queues

Question: Implement a job scheduler which takes in function f and int n and calls f after n seconds.
Since im using Obj C to answer this question, I should probably use dispatch queues instead of selectors or function pointers.
Here's my attempt to implement the answer, but it's not returning a time stamp:
void jobSch(dispatch_queue_t queue, int64_t n, dispatch_block_t f)
{
NSLog(#"%d\n", n);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, n * NSEC_PER_SEC), dispatch_get_main_queue(), f);
}
void (^aBlock)(void) = ^(){
NSDateFormatter *dateFormatter=[[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
NSLog(#"%#",[dateFormatter stringFromDate:[NSDate date]]);
};
int main(int argc, const char * argv[])
{
#autoreleasepool {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
jobSch(queue, 3, aBlock);
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
}
return 0;
}
What could I be doing wrong?
Also my intended implementation would be able to call jobSch(queue, 10, aBlock) at runtime 0 and jobSch(queue, 3, aBlock) at runtime 2, so that would mean the second call would print before the first.
The problem is that main is exiting long before your enqueued block is run. And that is because your call to dispatch_group_wait isn't waiting. And that is because your dispatch group is empty.
In order for a dispatch group and a call to dispatch_group_wait to be useful, you need to make paired calls to dispatch_group_enter and dispatch_group_leave.
You need to call dispatch_group_enter before you start any async call and you need to call dispatch_group_leave after the block has finished (aBlock in this case).
Your code is not setup to make this easy. You would need to make group a global variable. Then you can call dispatch_group_enter and dispatch_group_leave where needed.
Something like this should work:
dispatch_group_t group;
void jobSch(dispatch_queue_t queue, int64_t n, dispatch_block_t f)
{
NSLog(#"%d\n", n);
dispatch_group_enter(group);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, n * NSEC_PER_SEC), queue, f);
}
void (^aBlock)(void) = ^(){
NSDateFormatter *dateFormatter=[[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
NSLog(#"%#",[dateFormatter stringFromDate:[NSDate date]]);
dispatch_group_leave(group);
};
int main(int argc, const char * argv[])
{
#autoreleasepool {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
group = dispatch_group_create();
// no need for dispatch_group_async here
jobSch(queue, 10, aBlock);
jobSch(queue, 3, aBlock);
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
}
return 0;
}
Also note that you never actually make use of queue inside jobSch.
You need to change:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, n * NSEC_PER_SEC), dispatch_get_main_queue(), f);
to:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, n * NSEC_PER_SEC), queue, f);

Block passing itself as argument to method causing compiler warning

I was trying to create a callback that retries the method after a delay on failure. I'm hitting this warning:
"Capturing failure block strongly in this block is likely to lead to a retain cycle."
typedef void (^MyCallbackBlock)(NSObject *);
...
__block MyObject *blockSelf = self;
__block MyCallbackBlock successBlock = ^(NSObject *someObject)
{
// To be completed
};
__block MyCallbackBlock failureBlock = ^(NSObject *someObject)
{
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){
[blockSelf doSomething:someObject onSuccess:successBlock onFailure:failureBlock]; // <-- Warning is here
});
};
[blockSelf doSomething:someObject onSuccess:successBlock onFailure:failureBlock];
...
- (void)doSomething:(NSObject *)someObject
onSuccess:(MyCallbackBlock)successBlock
onFailure:(MyCallbackBlock)failureBlock;
The question: How can I make this work properly?
(I've been reading through other SO questions -- haven't found a match yet, though wouldn't be surprised if one is out there.)
Yes, the block needs to capture itself (as well as self) as a weak reference.
If you're using ARC*, it should be like this:
MyObject *__weak blockSelf = self;
__block __weak MyCallbackBlock weakSuccessBlock;
MyCallbackBlock successBlock = weakSuccessBlock = ^(NSObject *someObject)
{
// To be completed
};
__block __weak MyCallbackBlock weakFailureBlock;
MyCallbackBlock failureBlock = weakFailureBlock = ^(NSObject *someObject)
{
MyCallbackBlock strongSuccessBlock = weakSuccessBlock;
MyCallbackBlock strongFailureBlock = weakFailureBlock;
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){
[blockSelf doSomething:someObject onSuccess:strongSuccessBlock onFailure:strongFailureBlock];
});
};
If you're not using ARC, replace the __block __weak and __weak above with just __block.
*: Objective-C Automatic Reference Counting
Adding __unsafe_unretained worked, as in:
__unsafe_unretained __block MyCallbackBlock successBlock = ^(NSObject *someObject)
{
// To be completed
};
While it seemed possible that __weak could work, in practice it caused my application to crash. It's not 100% clear that this answer explains the reason, but I'm imagining it's something along those lines.

Unit testing for code like dispatch_async.

Need to do unit testing for the following code, dispatch_async means code won't be executed by app logic sequence, any idea on how to make it run timely?
Thank you.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[AdTracker dosomething];
});
See http://www.mikeash.com/pyblog/friday-qa-2011-07-22-writing-unit-tests.html
+ (BOOL)waitFor2:(finishBlock)block {
NSTimeInterval timeoutInSeconds = 10.0;
NSDate* giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds];
while (!block() && ([giveUpDate timeIntervalSinceNow] > 0)) {
NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:1.0];
[[NSRunLoop currentRunLoop] runUntilDate:stopDate]; // un-blocking.
DLog(#"+++++ %#", [NSDate date]);
}
return block();
}

Stopwatch using NSTimer incorrectly includes paused time in display

This is my code for an iPhone stopwatch. It works as expected and stops and resumes when the buttons are clicked.
When I hit "Stop", however, the timer won't stop running in the background, and when I hit "Start" to resume it, it will update the time and skip to where it is currently instead of resuming from the stopped time.
How can I stop the NSTimer? What is causing this to occur?
#implementation FirstViewController;
#synthesize stopWatchLabel;
NSDate *startDate;
NSTimer *stopWatchTimer;
int touchCount;
-(void)showActivity {
NSDate *currentDate = [NSDate date];
NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:startDate];
NSDate *timerDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"mm:ss.SS"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
NSString *timeString=[dateFormatter stringFromDate:timerDate];
stopWatchLabel.text = timeString;
[dateFormatter release];
}
- (IBAction)onStartPressed:(id)sender {
stopWatchTimer = [NSTimer scheduledTimerWithTimeInterval:1/10 target:self selector:#selector(showActivity) userInfo:nil repeats:YES];
touchCount += 1;
if (touchCount > 1)
{
[stopWatchTimer fire];
}
else
{
startDate = [[NSDate date]retain];
[stopWatchTimer fire];
}
}
- (IBAction)onStopPressed:(id)sender {
[stopWatchTimer invalidate];
stopWatchTimer = nil;
[self showActivity];
}
- (IBAction)reset:(id)sender; {
touchCount = 0;
stopWatchLabel.text = #"00:00.00";
}
Your calculation of the current display always uses the original start time of the timer, so the display after pausing includes the interval that the timer was paused.
The easiest thing to do would be to store another NSTimeInterval, say secondsAlreadyRun, when the timer is paused, and add that to the time interval you calculate when you resume. You'll want to update the timer's startDate every time the timer starts counting. In reset:, you would also clear out that secondsAlreadyRun interval.
-(void)showActivity:(NSTimer *)tim {
NSDate *currentDate = [NSDate date];
NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:startDate];
// Add the saved interval
timeInterval += secondsAlreadyRun;
NSDate *timerDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"mm:ss.SS"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
NSString *timeString=[dateFormatter stringFromDate:timerDate];
stopWatchLabel.text = timeString;
[dateFormatter release];
}
- (IBAction)onStartPressed:(id)sender {
stopWatchTimer = [NSTimer scheduledTimerWithTimeInterval:1/10
target:self
selector:#selector(showActivity:)
userInfo:nil
repeats:YES];
// Save the new start date every time
startDate = [[NSDate alloc] init]; // equivalent to [[NSDate date] retain];
[stopWatchTimer fire];
}
- (IBAction)onStopPressed:(id)sender {
// _Increment_ secondsAlreadyRun to allow for multiple pauses and restarts
secondsAlreadyRun += [[NSDate date] timeIntervalSinceDate:startDate];
[stopWatchTimer invalidate];
stopWatchTimer = nil;
[startDate release];
[self showActivity];
}
- (IBAction)reset:(id)sender; {
secondsAlreadyRun = 0;
stopWatchLabel.text = #"00:00.00";
}
Don't forget to release that startDate somewhere appropriate! Also keep in mind that the documented NSTimer interface is for the method you give it to accept one argument, which will be the timer itself. It seems to work without that, but why tempt fate?
Finally, since you're using that NSDateFormatter so much, you might want to consider making it an ivar or put it in static storage in showActivity:, like so:
static NSDateFormatter * dateFormatter = nil;
if( !dateFormatter ){
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"mm:ss.SS"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
}
So, when the user presses stop, and then start again, you aren't resetting the start time. But when you update the label, you are basing that on the total elapsed time from the original start time to the current time.
So if you run the timer for 10 seconds, stop, wait 10 seconds, and then start again, the timer will show 00:20.00 and start counting again from there.
What you want to do is reset the start time each time the user starts the clock, but then add the elapsed times of all previous runs as well. Or something similar.
BTW, you are leaking the start time every time you reset it now. Minor bug.
EDIT: looks like #Josh Caswell was thinking the same thing, but he types a LOT faster. :)
Are you using ARC or not?
If you are using ARC, it looks like you arent using a _strong reference. If you aren't using ARC, it doesn't looking you are retaining a reference to the timer.
I'm posting this from mobile so might be missing something.
EDIT: just noticed you were using release elsewhere, so I'll assume no ARC. You need to retain the timer after setting it to be able to access it later and invalidate.
You can use NSTimeInterval instead of timer. I have a functional code to pause and stop the timer.
#interface PerformBenchmarksViewController () {
int currMinute;
int currSecond;
int currHour;
int mins;
NSDate *startDate;
NSTimeInterval secondsAlreadyRun;
}
#end
- (void)viewDidLoad
{
[super viewDidLoad];
running = false;
}
- (IBAction)StartTimer:(id)sender {
if(running == false) {
//start timer
running = true;
startDate = [[NSDate alloc] init];
startTime = [NSDate timeIntervalSinceReferenceDate];
[sender setTitle:#"Pause" forState:UIControlStateNormal];
[self updateTime];
}
else {
//pause timer
secondsAlreadyRun += [[NSDate date] timeIntervalSinceDate:startDate];
startDate = [[NSDate alloc] init];
[sender setTitle:#"Start" forState:UIControlStateNormal];
running = false;
}
}
- (void)updateTime {
if(running == false) return;
//calculate elapsed time
NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate];
NSTimeInterval elapsed = secondsAlreadyRun + currentTime - startTime;
// extract out the minutes, seconds, and hours of seconds from elapsed time:
int hours = (int)(mins / 60.0);
elapsed -= hours * 60;
mins = (int)(elapsed / 60.0);
elapsed -= mins * 60;
int secs = (int) (elapsed);
//update our lable using the format of 00:00:00
timerLabel.text = [NSString stringWithFormat:#"%02u:%02u:%02u", hours, mins, secs];
//call uptadeTime again after 1 second
[self performSelector:#selector(updateTime) withObject:self afterDelay:1];
}
Hope this will help. Thanks
A timer class I created in Swift for a timer program in which a counter is updated every second from a set time. Answered to illustrate the Swift solution and the NSTimer function.
The timer can be stopped and restarted; it will resume from where it stopped. Events can be intercepted by the delegate for start, stop, reset, end and second events. Just check the code.
import Foundation
protocol TimerDelegate {
func didStart()
func didStop()
func didReset()
func didEnd()
func updateSecond(timeToGo: NSTimeInterval)
}
// Inherit from NSObject to workaround Selector bug
class Timer : NSObject {
var delegate: TimerDelegate?
var defaultDuration: NSTimeInterval?
var isRunning: Bool {
get {
return self.timer != nil && timer!.valid
}
}
private var secondsToGo: NSTimeInterval = 0
private var timer: NSTimer?
init(defaultDuration: NSTimeInterval, delegate: TimerDelegate? = nil) {
self.defaultDuration = defaultDuration
self.delegate = delegate
super.init()
}
func start() {
self.timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "updateTimer", userInfo: nil, repeats: true)
self.timer!.tolerance = 0.05
if delegate != nil { delegate!.didStart() }
}
func stop () {
self.timer?.invalidate()
self.timer = nil
if delegate != nil { delegate!.didStop() }
}
func reset() {
self.secondsToGo = self.defaultDuration!
if delegate != nil { delegate!.didReset() }
}
func updateTimer() {
--self.secondsToGo
if delegate != nil { delegate!.updateSecond(self.secondsToGo) }
if self.secondsToGo == 0 {
self.stop()
if delegate != nil { delegate!.didEnd() }
}
}
}