I want to have a for loop with dispatch_after
statements in them. Problem is that the dispatch_after calls don't seem to go in line with the for loop. In other words, I want it to only start the next iteration of the for loop after the statements in the dispatch_after
block have executed.
How would I do this?
I want to present words on screen. Traditionally I show one word per second. But depending on the word length, I now want to show longer words for slightly longer, and shorter words for slightly less time. I want to present a word, wait a little while (depending on how long the word is) then present the next word, wait a little while, then the next, etc.
Prints 0,1,2,3,4,5,6,7,8,9, one digit per second.
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0UL);
for (int i=0; i<10; i++) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(queue,^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1LL * NSEC_PER_SEC), dispatch_get_current_queue(), ^{
NSLog(@"%d",i);
dispatch_sync(dispatch_get_main_queue(), ^{
// show label on screen
});
dispatch_semaphore_signal(semaphore);
});
});
}
If you state your use case, maybe there are other ways to accomplish what you are trying to do.
You could also accumulate the delay time in advance and send all blocks.
(1) __block double delay = 0;
(2) dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0UL);
(3) for (int i=0; i<10; i++) {
(4) delay += 1LL * NSEC_PER_SEC; // replace 1 second with something related to the length of your word
(5) dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay), queue, ^{
NSLog(@"%d",i);
(6) dispatch_sync(dispatch_get_main_queue(), ^{
// show label on screen
});
});
}
Here's one way to achieve this. Naturally, you will need to replace my NSLog with the code to show the word, and replace my simple 0.05 * word.length
function with whatever function you're using to determine delay, but this should do the trick, and do it without blocking the presenting thread.
- (void)presentWord: (NSString*)word
{
// Create a private, serial queue. This will preserve the ordering of the words
static dispatch_queue_t wordQueue = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
wordQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
});
dispatch_async(wordQueue, ^{
// Suspend the queue
dispatch_suspend(wordQueue);
// Show the word...
NSLog(@"Now showing word: %@", word);
// Calculate the delay until the next word should be shown...
const NSTimeInterval timeToShow = 0.05 * word.length; // Or whatever your delay function is...
// Have dispatch_after call us after that amount of time to resume the wordQueue.
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeToShow * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
dispatch_resume(wordQueue);
});
});
}
// There's nothing special here. Just split up a longer string into words, and pass them
// to presentWord: one at a time.
- (void)presentSentence: (NSString*)string
{
NSArray* components = [string componentsSeparatedByCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
[components enumerateObjectsUsingBlock:^(NSString* obj, NSUInteger idx, BOOL *stop) {
[self presentWord: obj];
}];
}
EDIT: The way this works is that I'm using a serial queue to maintain the ordering of the words. When you submit a word to -presentWords
it enqueues a block at the "back" of wordQueue
. When that block begins execution, you know that wordQueue
is not suspended (because you're in a block that's executing on wordQueue
) and the first thing we do is suspend wordQueue
. Since this block is already "in flight" it will run to completion, but no other blocks will be run from wordQueue
until someone resumes it. After suspending the queue, we display the word. It will remain displayed until something else is displayed. Then, we calculate the delay based on the length of the word we just started showing, and set up a dispatch_after
to resume wordQueue
after that time has passed. When the serial queue is resumed, the block for the next word begins executing, suspends the queue and the whole process repeats itself.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With