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);
}];
}];
Related
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
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")
How to call a specific method asynchronously with delay repeatedly in an efficient way, so that there is minimal amount of memory usage without affecting app performance.
I know we can directly do that in dispatch_async but not sure how it works or thus is it an efficent way of approach.
Any suggestion or help will be appreciated.
Thanks
You should use dispatch timers. Here's a trivial example that calls a block every 2 seconds:
#interface AppDelegate ()
{
dispatch_source_t timer;
}
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
NSTimeInterval delayInSeconds = 2.0;
NSTimeInterval leeway = delayInSeconds * 0.1;
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC, leeway * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
NSLog(#"Timer called");
});
dispatch_resume(timer);
}
#end
As for memory usage, that's up to the method you call. The memory required to set up the timer itself is negligible.
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.
I have a need to delay for a certain amount of time and yet allow other things on the same runloop to keep running. I have been using the following code to do this:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
This seems to do exactly what I want, except that sometimes the function returns immediately without waiting the desired time (1 second).
Can anyone let me know what could cause this? And what is the proper way to wait while allowing the run loop to run?
NOTE: I want to delay in a manner similar to sleep(), such that after the delay I am back in the same execution stream as before.
You should use GCD and dispatch_after for that. It is much more recent and efficient (and thread-safe and all), and very easy to use.
There is even a code snippet embedded in Xcode, so that if you start typing dispatch_after it will suggest the snippet and if you validate it will write the prepared 2-3 lines for you in your code :)
int64_t delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
<#code to be executed on the main queue after delay#>
});
Use an NSTimer to fire off a call to some method after a certain delay.
Have you tried performSelector:withObject:afterDelay:?
From the Apple documentation
Invokes a method of the receiver on the current thread using the default mode after a delay.
I had a similar issue and this is my solution. Hope it works for others as well.
__block bool dispatched = false;
while ( put your loop condition here )
{
if (dispatched)
{
// We want to relinquish control if we are already dispatched on this iteration.
[ [ NSRunLoop currentRunLoop ] runMode: NSDefaultRunLoopMode beforeDate:[ NSDate date ] ];
continue;
}
// mark that a dispatch is being scheduled
dispatched = true;
int64_t delayInNanoSeconds = (int64_t) (0.1 * (float) NSEC_PER_SEC);
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, delayInNanoSeconds);
dispatch_after(delayTime, dispatch_get_main_queue(), ^() {
// Do your loop stuff here
// and now ready for the next dispatch
dispatched = false;
} );
} // end of while