iOS - 如何在设定的时间后中断异步后台操作

时间:2014-03-17 14:02:15

标签: ios objective-c multithreading cocoa-touch grand-central-dispatch

注意:请不要评论这是否是一个好主意;)我只是在尝试,所以想看看它是如何实现的......

我有一个UITextView和一些inputText,我将其设置为UITextView的文字。

我想尝试将inputText视觉上滴加到文本视图中,例如,一次一个字母或一次一个字。

例如(不考虑滴灌延迟或执行线程):

for (NSInteger i = 0; i < inputText.length; i++) {
    NSString* charAtIndex = [text substringWithRange:NSMakeRange(i, 1)];
    _textView.text = [NSString stringWithFormat:@"%@%@", _textView.text, charAtIndex];
}

所以,为了建立延迟并看到一次添加一个字符(或单词),我可以执行以下操作:

dispatch_queue_t backgroundQueue = dispatch_queue_create("background_queue", 0);
for (NSInteger i = 0; i < inputText.length; i++) {
    dispatch_async(backgroundQueue, ^{
        [NSThread sleepForTimeInterval:0.01f];
        NSString* charAtIndex = [inputText substringWithRange:NSMakeRange(i, 1)];
        dispatch_async(dispatch_get_main_queue(), ^{
            _textView.text = [NSString stringWithFormat:@"%@%@", _textView.text, charAtIndex];
        });    
    });
}

工作正常,正如预期的那样,for循环在后台队列上排队了一堆异步操作。

但我希望上述内容可以作为更大的单个同步可视化的一部分进行。在将此代码添加到点播UITextView之前,我只需在UITextView中设置文字,然后在屏幕外设置当前视图(包括UITextView)的动画。一切都在主线上。用户可以点击一个按钮,看到整个文本出现,然后视图立即开始动画离开屏幕。然后,用户将继续执行工作流程中的下一步。

我正在尝试使用滴灌可视化来给人一种文字视图一次填充一个字符(或单词)的印象,但我不希望用户必须等到每个最后一个字符/单词已被添加。所以,我希望看到UITextView在填充视图(包括UITextView)屏幕外的动画之前填充0.3或0.5秒,然后才开始播放...

所以它有点像这样:

dispatch_queue_t backgroundQueue = dispatch_queue_create("background_queue", 0);
for (NSInteger i = 0; i < inputText.length; i++) {
    dispatch_async(backgroundQueue, ^{
        [NSThread sleepForTimeInterval:0.01f];
        NSString* charAtIndex = [inputText substringWithRange:NSMakeRange(i, 1)];
        dispatch_async(dispatch_get_main_queue(), ^{
            _textView.text = [NSString stringWithFormat:@"%@%@", _textView.text, charAtIndex];
        });    
    });
}

// ...
// Animate the view (including the UITextView) off screen
// ...
// User continues with the main workflow

现在,由于所有的滴灌都是在后台异步发生的,一旦我添加了代码来为视图设置动画,就会错过视觉滴灌。主线程一直运行到屏幕上的视图动画。

我不确定如何实现我的目标?

我是否以某种方式中断上述循环?检查从另一个线程更新的标志?

我无法将等待放在主线程上 - 因为它会阻止对UITextView进行滴漏更新...

有什么建议吗?

3 个答案:

答案 0 :(得分:3)

您可以延迟动画以“给”滴水的时间,如下所示:

double delayInSeconds = 0.5f;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    // ...
    // Animate the view (including the UITextView) off screen
    // ...              
});

这样用户将看到滴水动画动画0.5秒。这是GCD版本,但您也可以使用该方法:

[UIView animateWithDuration:0.3f // Set your duration here
                      delay:0.5f
                    options:UIViewAnimationOptionCurveEaseOut // Choose the right option here
                 animations:^{
                     // Do your animations here.
                 }
                 completion:^(BOOL finished){
                     if (finished) {
                         // Do your method here after your animation.
                     }
                 }];

答案 1 :(得分:1)

最好不要排除大量任务,然后设置另一个延迟动画。是的,它会起作用,但它不是那么可维护,并且不适合将来的其他情况。

相反,请考虑使用NSTimer和几个实例变量(可以将其包含在另一个类中以保持一切干净整洁)。实例变量基本上是当前进度(来自循环的i)。每次计时器触发时,请检查i - 如果文本动画未完成,请使用i子串并更新UI。如果文本动画完成,则使计时器无效并启动最终视图动画。

通过这种方式,逻辑可以组织,易于理解,可重复使用和取消。

答案 2 :(得分:0)

根据J. Costa的建议,我一直坚持使用Grand Central Dispatch方法。我缺少的那篇文章(我不确定我是否很好地解释了这个要求)是对共享资源的访问。

在下面的代码中,我的结构是这样的:

  • BOOL共享资源以指示滴灌是否应继续
  • 创建一个用于读取/写入该共享资源的串行队列
  • 滴灌在后台队列上发生,另外还使用串行队列来检查是否应该继续
  • 使用dispatch_after设置延迟视图动画,当它发生时,它使用串行队列来发出滴水信号应该停止

代码:

NSString* inputText = @"Some meaningful text...";
dispatch_queue_t serialQueue = dispatch_queue_create("serialqueue", DISPATCH_QUEUE_SERIAL);

// the shared resource
_continueDripFeed = YES;

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    for (NSInteger i = 0; i < inputText.length; i++) {
        __block BOOL keepGoing = NO;
        dispatch_sync(serialQueue, ^{
            // read from the shared resource
            keepGoing = _continueDripFeed;
        });

        if (keepGoing) {
            [NSThread sleepForTimeInterval:0.02f];
            NSString* charAtIndex = [inputText substringWithRange:NSMakeRange(i, 1)];
            dispatch_async(dispatch_get_main_queue(), ^{
                _textView.text = [NSString stringWithFormat:@"%@%@", _textView.text, charAtIndex];
            });
        }
        else {
            dispatch_async(dispatch_get_main_queue(), ^{
                _textView.text = inputText;
            });
            break;
        }
    }
});

double delayInSeconds = 0.5f;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    dispatch_sync(serialQueue, ^{
        // update the shared resource
        _continueDripFeed = NO;
    });
    [self animateTextViewToFrame:_offScreenFrame];
    // Continue with workflow...
});