5

dispatch_afterステートメントを含む for ループが必要です。問題は、dispatch_after 呼び出しが for ループと一致していないように見えることです。dispatch_afterつまり、ブロック内のステートメントが実行された後にのみ、for ループの次の繰り返しを開始したいのです。

どうすればいいですか?

使用事例

画面に単語を表示したい。伝統的に、私は毎秒 1 つの単語を表示します。しかし、単語の長さに応じて、長い単語を少し長く表示し、短い単語を少し短い時間で表示したいと考えています。単語を提示し、しばらく待ってから (単語の長さによって異なります)、次の単語を提示し、少し待ってから次の単語を提示したいなどです。

4

3 に答える 3

7

0、1、2、3、4、5、6、7、8、9 を 1 秒あたり 1 桁で出力します。

    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. システムの事前定義されたキューの 1 つを取得します。キューには、順番に実行される多数の匿名コード (「ブロック」と呼ばれる) が保持されます。
  3. ループ。
  4. 1 秒追加します。その 1 を別のものに置き換える必要があります。たとえば、単語が 10 文字の場合、.10*[単語の長さ] は 1 秒間の休止になります。
  5. 次のコード (別名「ブロック」) を、指定された「遅延」秒後に「キュー」に入れます。キューは、配置したコード ブロックを自動的に実行します。この行は、「後でこれを実行してください」と言っているようなものです。
  6. UIKit はスレッドセーフではないため、バックグラウンドで (「キュー」はおそらくバックグラウンド スレッドで実行されているため、ここのように) ユーザー インターフェイスを変更する場合は常に、メイン キューでコードを実行する必要があります。ライブラリ (つまり、別のスレッドから呼び出した場合に正しく実行されるようには設計されていません)。
于 2013-09-30T14:28:45.897 に答える
4

これを実現する 1 つの方法を次に示します。当然、私の 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)、最初に行うことは suspendwordQueueです。このブロックはすでに「実行中」であるため、完了するまで実行されますが、他のブロックは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()ますdispatch_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 に答える