后台线程忙时UITextView不会滚动

时间:2013-07-11 23:55:17

标签: ios multithreading uiscrollview uitextview

我在后台线程中运行数学计算。试图在UITextView中实时发布结果。但是,在后台线程完成之前,结果不会显示。为什么不呢?

我在后台启动了一个方法,

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^() {
    [self v2];
});

后台线程方法的格式为

- (void) v2 {
    NSString *result;
    // ... loop a bunch of times generating lots of results
    for (bunch of stuff to compute) {
      // If using dispatch_async, nothing is displayed until this method finishes
      // If dispatch_sync then it does display and update
      result = [self computeNextValue];
      dispatch_async(dispatch_get_main_queue(), ^() {
        textView.text = result;
      });
    } // end computation
}

在我开始尝试滚动视图之前,这实际上并没有太大问题。痛苦地慢。所以我创建了一个NSTimer来定期滚动UITextView。但是,即使弹出计时器并运行该方法来请求滚动,UITextView也不会滚动,直到后台方法完成。

3 个答案:

答案 0 :(得分:0)

首先,确保你在块内使用weakSelf而不是self。

__weak MyClass weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^() {
    [weakSelf v2]; 
});

...

__weak MyClass weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^() {
  weakSelf.textView.text = result;
});

但这不会导致延迟更新。我非常怀疑队列的优先级,DISPATCH_QUEUE_PRIORITY_LOW。请尝试使用DISPATCH_QUEUE_PRIORITY_DEFAULT。

答案 1 :(得分:0)

我想我已经弄明白了,至少是凭经验。

看起来像在UITextView上设置文本或以编程方式启动滚动设置在另一个线程中运行的动画序列。似乎正在发生的事情是,设置UITextView文本的请求比UITextView设置动画的能力要快。当属性再次更改时,它显然取消了在另一个线程上设置的早期动画。当我继续用变更请求充斥它时,它永远无法在'text'的值再次改变之前完成它想要做的事情。

我的解决方案涉及多个步骤。

在我原来的方法中,我将textView.text非常快速地设置为一些新值。我设置了一个计时器来定期请求UITextView滚动。 在我的修改中,我计算结果字符串的新值,但不要将其设置为UITextView。而是基于计时器定期在textview上设置文本。这允许文本视图赶上。

然而,我注意到这仍然不可靠。如果我还在滚动时再次设置文本,则会出现奇怪的效果,例如非常慢的滚动。似乎滚动动画和文本的重复设置仍然会导致问题。

所以解决这个问题,我创建了一个属性来指示视图是否滚动。将视图控制器设置为UITextField委托。当我请求视图滚动时,我设置标志以指示其滚动。如果内容尚未滚动,则仅更新内容并请求滚动。结束工作很棒。无论我设置计时器的速度有多快,它最终都会正确等待。

// ViewController.h
@property BOOL isViewScrolling;

// ViewController.m
// initialize property in viewDidLoad
- (void)viewDidLoad {
  self.isViewScrolling = FALSE;
  textView.delegate = self;
  self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(scrollIt) userInfo:nil repeats:TRUE];
}

- (void)scrollIt {
  NSLog(@"scrollit thread=%d", [[NSThread currentThread]isMainThread]);
  if (!self.isViewScrolling) {
    textView.text = self.result;
    NSRange range = NSMakeRange(textView.text.length - 1, 1);
    self.isViewScrolling = TRUE;
    [textView scrollRangeToVisible:range];
  }
}

// UITextView delegate
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
  NSLog(@"Animation stopped");
  self.isViewScrolling = FALSE;
}

答案 2 :(得分:0)

尝试将预定义的调度队列字符串更改为以背景结束。

__weak MyClass weakSelf = self;
    dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^() {
      weakSelf.textView.text = result;
    });

此外,您应该添加一个UIActivityIndi​​cator并在操作开始时开始动画,然后在更新textview.text字段后停止动画。

这是一个很好的功能,必须向用户显示当前正在进行的过程

此外,我会远离NSThread,因为我已经阅读了一些强调使用GCD和阻止操作的苹果论坛和文档。