5

我想要一个带有dispatch_after语句的 for 循环。问题是 dispatch_after 调用似乎不符合 for 循环。换句话说,我希望它只在dispatch_after块中的语句执行后才开始 for 循环的下一次迭代。

我该怎么做?

用例

我想在屏幕上呈现文字。传统上我每秒显示一个单词。但根据字长,我现在想用稍长的时间显示较长的词,用更短的时间显示较短的词。我想呈现一个单词,稍等片刻(取决于单词的长度)然后呈现下一个单词,稍等片刻,然后是下一个,依此类推。

4

3 回答 3

7

每秒打印 0,1,2,3,4,5,6,7,8,9,一位数。

    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);
            });
        });
    }

如果您陈述您的用例,也许还有其他方法可以完成您正在尝试做的事情。


您也可以提前累积延迟时间并发送所有块。

    (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
            });
        });
    }
  1. 标记变量 __block 以便它可以在块内修改
  2. 获取系统的预定义队列之一。队列包含许多轮流执行的匿名代码(称为“块”)。
  3. 环形。
  4. 加一秒。你应该用其他东西替换那个 1 。例如,如果单词有 10 个字母,.10*[word length] 将导致 1 秒的暂停。
  5. 在给定的“延迟”秒数之后,将以下代码(也称为“块”)放入“队列”中。队列将自动运行您放置在其上的代码块。这条线就像在说,“延迟运行”。
  6. 任何时候你在后台(就像这里,因为“队列”可能在后台线程上运行)并且你想改变用户界面,你必须在主队列中运行代码,因为 UIKit 不是线程安全的库(意思是,如果您从不同的线程调用它,则不能正确运行)。
于 2013-09-30T14:28:45.897 回答
4

这是实现这一目标的一种方法。自然,您需要用代码替换我的 NSLog 以显示单词,并将我的简单0.05 * word.length函数替换为您用来确定延迟的任何函数,但这应该可以解决问题,并且不会阻塞呈现线程。

- (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];
    }];
}

编辑:这种工作方式是我使用串行队列来维护单词的顺序。当您向-presentWords它提交一个单词时,它会在wordQueue. 当那个块开始执行时,你知道它wordQueue没有被挂起(因为你在一个正在执行的块中wordQueue),我们做的第一件事就是挂起wordQueue。由于此块已经“运行”,它将运行到完成,但在wordQueue有人恢复它之前不会运行其他块。暂停队列后,我们显示单词。它将一直显示,直到显示其他内容。然后,我们根据刚开始显示的单词的长度计算延迟,并设置一个dispatch_after恢复wordQueue在那个时间过去之后。当串行队列恢复时,下一个字的块开始执行,挂起队列,整个过程重复。

于 2013-09-30T15:34:32.663 回答
0

更新 2

这是一个基于 GCD 的版本:

@interface Test : NSObject
@property ( nonatomic, copy ) NSArray * wordlist ;
@property ( nonatomic ) volatile BOOL cancelled ;

-(void)run ;
-(void)cancel ;

@end

@implementation Test

-(void)displayWords:(NSArray*)words
{
    NSLog(@"word=%@\n", words[0] ) ;
    if ( words.count > 1 && !self.cancelled )
    {
        words = [ words subarrayWithRange:(NSRange){ 1, words.count - 1 } ] ;

        double              delayInSeconds = 1.0;       // calculate delay until next word here
        dispatch_time_t     popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));

        dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
            [ self performSelectorOnMainThread:@selector( displayWords: ) withObject:words waitUntilDone:NO ] ;
        });
    }
}

-(void)run
{
    [ self performSelectorOnMainThread:@selector( displayWords: ) withObject:self.wordlist waitUntilDone:NO ] ;
}

-(void)cancel
{
    self.cancelled = YES ;
}

@end

这将在主线程上执行显示——如果您使用 UIKit 来绘制文本,这是一个要求。

这与其他答案类似,但仅使用默认队列而不使用dispatch_resume()anddispatch_suspend()


你可以使用NSThread. 我觉得这很好理解:

@interface Thread : NSThread
@property ( nonatomic, copy ) NSArray * wordlist ;
@end

@implementation Thread

// runs on main thread
-(void)displayWord:(NSString*)word
{
    // code to display word goes here
}

-(void)main
{
    for( NSString * word in self.wordlist )
    {
        if ( self.isCancelled ) { return ; }
        [ self performSelectorOnMainThread:@selector( displayWord: ) withObject:word waitUntilDone:NO ] ;
        [ NSThread sleepForTimeInterval:1.0 ] ; // replace with desired pause... (could be based on word length)
    }
}

@end

要使用:

Thread * thread = [[ Thread alloc ] init] ;
thread.wordlist = @[ @"one", @"two", @"three" ] ;
[ thread start ] ;
于 2013-09-30T17:59:54.453 回答