Adding Delay In A For Loop Without Blocking UI - ios

In my UI, when a button is tapped, it calls a for loop that executes several tasks sequentially.
// For Loop
for (int i = 1; i <= 3; i++)
{
// Perform Task[i]
}
// Results:
// Task 1
// Task 2
// Task 3
After each task, I would like add a user-defined delay. For example:
// For Loop
for (int i = 1; i <= 3; i++)
{
// Perform Task[i]
// Add Delay Here
}
// Results:
//
// Task 1
// Delay 2.5 seconds
//
// Task 2
// Delay 3 seconds
//
// Task 3
// Delay 2 seconds
In iOS, using Objective-C, is there a way to add such delays within a for loop, keeping in mind:
The UI should remain responsive.
The tasks must be performed in order, sequentially.
A code example within the context of a for loop would be most helpful. Thank you.

Use GCD dispatch_after.
You can search its usage on stackoverflow.
Nice article is here
Brief example in Swift for 1.5 seconds delay:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(Double(NSEC_PER_SEC) * 1.5)), dispatch_get_main_queue()) {
// your code here after 1.5 delay - pay attention it will be executed on the main thread
}
and objective-c:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^ {
// your code here after 1.5 delay - pay attention it will be executed on the main thread
});

This sounds like an ideal job for NSOperationQueue with the delay being implemented like this:
#interface DelayOperation : NSOperation
#property (NSTimeInterval) delay;
- (void)main
{
[NSThread sleepForTimeInterval:delay];
}
#end

Would this solution work? Instead of using dispatch_after, I use dispatch_async with a [NSThread sleepForTimeInterval] block, which allows me to put a delay anywhere that I need in my custom queue.
dispatch_queue_t myCustomQueue;
myCustomQueue = dispatch_queue_create("com.example.MyQueue", NULL);
dispatch_async(myCustomQueue, ^ {
NSLog(#“Task1”);
});
dispatch_async(myCustomQueue, ^ {
[NSThread sleepForTimeInterval:2.5];
});
dispatch_async(myCustomQueue, ^ {
NSLog(#“Task2”);
});
dispatch_async(myCustomQueue, ^ {
[NSThread sleepForTimeInterval:3.0];
});
dispatch_async(myCustomQueue, ^ {
NSLog(#“Task3”);
});
dispatch_async(myCustomQueue, ^ {
[NSThread sleepForTimeInterval:2.0];
});

Heres a Swift version:
func delay(seconds seconds: Double, after: ()->()) {
delay(seconds: seconds, queue: dispatch_get_main_queue(), after: after)
}
func delay(seconds seconds: Double, queue: dispatch_queue_t, after: ()->()) {
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC)))
dispatch_after(time, queue, after)
}
How you call it:
print("Something")
delay(seconds: 2, after: { () -> () in
print("Delayed print")
})
print("Anotherthing")

Related

Why using 2 different timers via grand-central-dispatch doesn't work?

I used before one timer in my app for periodically launched task (token refresh actually). I found code example on stackoverflow and it worked for me.
This is the code example definition (above implementation header):
dispatch_source_t CreateDispatchTimer(double interval, dispatch_queue_t queue, dispatch_block_t block)
{
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
if (timer)
{
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), interval * NSEC_PER_SEC, (1ull * NSEC_PER_SEC) / 10);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
}
return timer;
}
this is the variables definitions:
dispatch_source_t _timer;
static double SECONDS_TO_FIRE = 60.000f;
and then there is a method e.g. startTimer where I launched this timer:
- (void)startTimer {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
_timer = CreateDispatchTimer(SECONDS_TO_FIRE, queue, ^{
// NSLog(#"timer is fired");
// do smth I need
}
});
}
So this code worked for me very well.
Now I need another (second) timer for separate task that should be fired in own time interval.
So I copied code above in separate class, set up another time interval.
Faced that if I use the same name for dispatch_source_t - like CreateDispatchTimer above, application won't be compiled!
So for second timer in separate class I changed the dispatch_source_t name to another, like CreateTimerDispatch. So app was compiled successfully.
But, the problem is - only second timer works now! The first time is not fired at all!
How to fix this? Can now understand why only last timer is fired.
Found the reason - except renaming dispatch_source_t CreateDispatchTimer block with different name for these 2 timers,
I needed to rename timer variable for second timer too.
I caught that the first timer was completely overwritten by second timer (as it was created later). Thus the first timer didn't fire at all.
So the working code example for second timer is following:
dispatch_source_t CreateLocationTimerDispatch(double interval, dispatch_queue_t queue, dispatch_block_t block) {
dispatch_source_t timerForLocationRefresh = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
if (timerForLocationRefresh) {
dispatch_source_set_timer(timerForLocationRefresh, dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), interval * NSEC_PER_SEC, (1ull * NSEC_PER_SEC) / 10);
dispatch_source_set_event_handler(timerForLocationRefresh, block);
dispatch_resume(timerForLocationRefresh);
}
return timerForLocationRefresh;
}
variable:
dispatch_source_t _timerForLocationRefresh;
static double SECONDS_TO_FIRE = 1800.f; // time interval lengh in seconds 1800
calling:
- (void)startTimer {
// second timer
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
_timerForLocationRefresh = CreateLocationTimerDispatch(SECONDS_TO_FIRE, queue, ^{
// do smth
});
}
Summary:
CreateDispatchTimer block for second timer needed to be renamed, e.g. > CreateLocationTimerDispatch
timer variable for second timer needed to be renamed too, e.g. > timerForLocationRefresh

Break point in XCTestCase can't be reached

I have a method that requests data from network , and I want to unit test it. But when I set a break point in the test case , the break point won't get there.
The method to be tested:
- (void)requestSuperDecisionDataWithCompletionHandler:(void (^)(NSArray *result))callBack {
static vector<vector<string>> arr;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//request data from network
IceNetwork::Init()->GetSuperDecision(arr);
if (arr.size() != kSuperDecisionDataCount) {
callBack(nil);
} else {
NSArray *convertedData = [self convertCArrayToNSArrayWithCArray:arr];
dispatch_async(dispatch_get_main_queue(), ^{
callBack(convertedData);
});
}
});
}
Test case:
- (void)testThatRequestSuperDecisionDataShouldReturnZeroOr14Items
{
//super decision
[_requestManager requestSuperDecisionDataWithCompletionHandler:^(NSArray *result) {
//set a break point here
int dataCount = result.count;
XCTAssert(0 == dataCount || 16 == dataCount, #"should have 0 or 16 items");
}];
}
In that case, because that is an asynchronous operation, the test is completing before the completion handler is called. With that in mind, there are two methods you can use to do this, both involving essentially waiting for the completion handler to be called before allowing your tests to complete.
The first method utilizes new APIs available in the Xcode 6 beta.
Method #1 - New XCTestExpectation APIs
- (void)testThatRequestSuperDecisionDataShouldReturnZeroOr14Items {
// set an expectation for your test, in this case we expect the completion handler to be
// called within a reasonable amount of time
XCTestExpectation *expectation = [self expectationWithDescription:#"The completion handler should be called."];
// start whatever asyncronous task you want to do
[_requestManager requestSuperDecisionDataWithCompletionHandler:^(NSArray *result) {
// here we handle the actual meat of the tests
int dataCount = result.count;
XCTAssert(0 == dataCount || 16 == dataCount, #"should have 0 or 16 items");
// now we can tell the test that our expectation has been fulfilled, which will
// allow the test to complete
[expectation fulfill];
}];
// we want the test to wait for the expectations for some period of time, but we also
// want to establish some sort of timeout interval where the test will basically be
// terminated and the result will be a timeout failure, you can set the timeout to whatever
// interval you want for each case, and optionally provide a handler to clean up anything
[self waitForExpectationsWithTimeout:2 handler:nil];
}
A second option is available if you are running Xcode 5 and thus don't have access to the new APIs, but is essentially the same process, just slightly more involved.
Method #2 - Do-it-yourself Async Testing
- (void)testThatRequestSuperDecisionDataShouldReturnZeroOr14Items {
// create a BOOL flag that will keep track of whether or not we're still
// waiting for the completion block to be called
__block BOOL waitingForBlock = YES;
// start whatever asyncronous task you want to do
[_requestManager requestSuperDecisionDataWithCompletionHandler:^(NSArray *result) {
// here we handle the actual meat of the tests
int dataCount = result.count;
XCTAssert(0 == dataCount || 16 == dataCount, #"should have 0 or 16 items");
// now we can update the waitingForBlock variable to NO as we are no
// longer waiting for it to complete
waitingForBlock = NO;
}];
// kill the test after some specified delay if we haven't completed yet - just in case something
// happens with the test where the handler will never be called, at least the test will complete
// and we will know we timed-out.
NSTimeInterval seconds = 3;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, seconds * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void) {
XCTFail(#"'testThatRequestSuperDecisionDataShouldReturnZeroOr14Items' failed due to timeout.");
waitingForBlock = NO;
});
// loop while we are waiting for the completion handler to finish
while (waitingForBlock) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
}
}

Using dispatch_after to do something relative to when something else finishes

Here is what I need to do:
Something like:
for int i = 0; i < 3; i++)
call method #selector(spin) with delay 0.1
//once this is done (e.g., in 0.3 seconds)
call method #selector(move) with delay 1 second.
What I need is to pile on events, and have other events start relative to when the previous one finishes.
So something like:
wait 100ms //total time = 100ms
spin
wait 100ms //total time = 200ms
spin
wait 100ms //total time = 300ms
spin
wait 1000ms //total time = 1300ms
move
Is something like this possible with dispatch_after? If so could someone give me an example? I cannot seem to find one for this case.
Note, none of this should cause the UI thread to wait / block.
Thanks
You can use NSThread's sleepForTimeInterval method. The following code will block the thread sleepForTimeInterval is running on.
dispatch_queue_t yourThread = dispatch_queue_create("com.xxx.queue", nil);
dispatch_async(yourThread, ^
{
[NSThread sleepForTimeInterval:10.0];
dispatch_async(dispatch_get_main_queue(), ^
{
});
});
This should point you to the right direction.
I personally think that you're trying to achive the goal in wrong way. When you deal with asynchronous process you should use blocks
Based on what your request :
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self performSelector:#selector(move:) withObject:[NSNumber numberWithInt:0] afterDelay:2];
}
-(void) move:(NSNumber *)i {
int val = [i intValue] + 1;
//do what you need here
if (val < 3) [self performSelector:#selector(move:) withObject:[NSNumber numberWithInt:val] afterDelay:2];
}
But Of course if you need to process a lot of data, use other thread instead...
dispatch_group_t group = dispatch_group_create();
dispatch_time_t timeNow = DISPATCH_TIME_NOW;
for (int i = 0; i < 3; ++i)
{
dispatch_group_enter(group);
dispatch_after(dispatch_time(timeNow, i*100), queue, ^(){
[self spin];
dispatch_group_leave(group);
});
}
dispatch_group_notify(group, queue, ^(){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1000), queue, ^(){
[self move];
});
});

How to pause a dispatch_queue_t and the queues created by it

I'm creating a program that can, amoung other things, fade in and out music. The problem is that other threads/queues can pause the music, meaning that the fade in and out also need to not only pause, but hold off. I need to be able to pause the "timer" on a dispatch_after (because this is called when the music starts playing in order to tell it when to start fading out, which would need to be delayed if it was paused) and pause a queue itself (in order to pause a fade in or fade out WHILE they're fading in or out)
Here's the code (fadeIn and delayFadeOut are both called at the start of the program):
- (void) doFadeIn: (float) incriment to: (int) volume with: (AVAudioPlayer*) thisplayer on: (dispatch_queue_t) queue{
dispatch_async(queue, ^{
double delayInSeconds = .1;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, queue, ^(void){
thisplayer.volume = (thisplayer.volume + incriment) < volume ? thisplayer.volume + incriment : volume;
NSLog([[[NSNumber alloc] initWithFloat:thisplayer.volume] stringValue]);
if (thisplayer.volume < volume) {
[self doFadeIn:incriment to:volume with:thisplayer on:queue];
}
});
});
}
-(void) doDelayFadeOut: (float) incriment with: (AVAudioPlayer*) thisplayer on: (dispatch_queue_t) queue
{
dispatch_async(queue, ^{
double delayInSeconds = .1;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, queue, ^(void){
thisplayer.volume = (thisplayer.volume - incriment) > 0 ? thisplayer.volume - incriment : 0;
NSLog([[[NSNumber alloc] initWithFloat:thisplayer.volume] stringValue]);
if (thisplayer.volume > 0.0) {
[self doDelayFadeOut:incriment with:thisplayer on:queue];
}
});
});
}
-(void) fadeIn: (AVAudioPlayer*) dFade {
if (dFade == nil) {
return;
}
dispatch_queue_t queue = dispatch_queue_create("com.cue.MainFade", NULL);
dispatch_async( queue, ^(void){
if(dFade !=nil){
double incriment = ([self relativeVolume] / [self fadeIn]) / 10; //incriment per .1 seconds.
[self doFadeIn: incriment to: [self relativeVolume] with:dFade on:dispatch_queue_create("com.cue.MainFade", 0)];
}
});
}
- (void) delayFadeOut: (AVAudioPlayer*) dFade { //d-fade should be independent of other threads
if (dFade == nil) {
return;
}
int timeRun = self.duration - self.fadeOut;
dispatch_queue_t queue = dispatch_queue_create("com.cue.MainFade", NULL);
dispatch_time_t mainPopTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeRun * NSEC_PER_SEC));
printf("test");
dispatch_after(mainPopTime, queue, ^(void){
if(dFade !=nil){
double incriment = ([dFade volume] / [self fadeOut])/10; //incriment per .1 seconds.
[self doDelayFadeOut:incriment with:dFade on:dispatch_queue_create("com.cue.MainFade", 0)];
}
});
if (self.cueType == 2) {
[self callNext];
}
}
To your general question, the call is dispatch_suspend() (along with dispatch_resume()). This will prevent any new blocks on a particular queue from being scheduled. It will not have any impact on already-running blocks. If you want to pause a block already scheduled and running, it is up to your code to check some conditional and pause.
The key to understand when using this, though, is that there is nothing that can "pause the "timer" on a dispatch_after." If you say you want something to be dispatched after 1 second, it absolutely will be dispatched after 1 second. But "dispatch" does not mean "run." It means "put on a queue." If that queue is suspended, then the block will hang out until the queue is resumed. The thing to be careful about is that you don't want a bunch of fade blocks to accumulate on the queue. If they did, when you resume the queue they would all be scheduled back-to-back. Looking at your code, that probably won't happen, so this could work for you. Just keep in your mind that dispatch means "put on a queue." And non-suspended queues have their blocks scheduled in order.
For suspending queues/cancelling operations, you should use NSOperation and NSOperationQueue instead of GCD. See here.

dispatch_after looped / repeated

I am trying to create a loop like this:
while (TRUE){
dispatch_after(...{
<some action>
});
}
After a viewDidLoad. The idea is to repeat the dispatch_after repeatedly. The dispatch_after waits two seconds before doing the action.
This does not work - the screen just blanks? Is it stuck in looping or ...?
Yes, you can do that with gcd. You need two additional c-functions though.
static void dispatch_async_repeated_internal(dispatch_time_t firstPopTime, double intervalInSeconds, dispatch_queue_t queue, void(^work)(BOOL *stop)) {
__block BOOL shouldStop = NO;
dispatch_time_t nextPopTime = dispatch_time(firstPopTime, (int64_t)(intervalInSeconds * NSEC_PER_SEC));
dispatch_after(nextPopTime, queue, ^{
work(&shouldStop);
if(!shouldStop) {
dispatch_async_repeated_internal(nextPopTime, intervalInSeconds, queue, work);
}
});
}
void dispatch_async_repeated(double intervalInSeconds, dispatch_queue_t queue, void(^work)(BOOL *stop)) {
dispatch_time_t firstPopTime = dispatch_time(DISPATCH_TIME_NOW, intervalInSeconds * NSEC_PER_SEC);
dispatch_async_repeated_internal(firstPopTime, intervalInSeconds, queue, work);
}
Tested! Works as intended.
https://gist.github.com/4676773
The dispatch_after(...) call returns immediately no matter when it is scheduled to run. This means that your loop is not waiting two seconds between dispatching them. Instead you are building an infinite queue of things that will happen two seconds from now, not two seconds between each other.
So yes, you are stuck in an infinite loop of adding more and more blocks to be executed. If you want something to happen every two second then you could use a repeating NSTimer or have the block dispatch_after inside itself (so that the second block runs two seconds after the first).
GCD already got this built in
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
if (timer) {
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), interval * NSEC_PER_SEC, (1ull * NSEC_PER_SEC) / 10);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
}
https://gist.github.com/maicki/7622108
If you'd like an async task to run after a delay to check for example if a tag has been updated, then finish, you could use the code below:
typedef void (^RepeatCompletionHandler)(BOOL isRepeat);
typedef void (^RepeatBlock)(RepeatCompletionHandler completionHandler);
- (void)dispatchRepeat:(int)seconds withBlock:(RepeatBlock)block {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, seconds * NSEC_PER_SEC),
dispatch_get_main_queue(), ^() {
block(^(BOOL isRepeat) {
if (isRepeat) {
return [self dispatchRepeat:seconds withBlock:block];
}
});
});
}
For example:
[self dispatchRepeat:5 withBlock:^(RepeatCompletionHandler completionHandler) {
[tagsService getTagValueForTagName:TagName value:^(NSString *tagValue) {
if (![TagValue isEqualToString:tagValue]) {
return completionHandler(YES);
}
completionHandler(NO);
}];
}];

Resources