【问题标题】:Avoid blocking UI when doing a lot of operations that are required to be on main thread在执行大量需要在主线程上执行的操作时避免阻塞 UI
【发布时间】:2012-06-27 18:24:47
【问题描述】:

我需要在主线程上执行一系列可能很长的调用(因为否则 UIKit 会退缩)。 “长”是指在 iPad 3 上 10,000 次操作每次持续 0.1 秒。

显然,一次遍历所有这些可能不是最好的主意。

我不知道如何在主线程上执行所有这些,同时留出足够的喘息空间来保持 UIKit 响应和看门狗睡着(即不会因为占用运行循环而终止)。

有人有想法吗?我的目标是 iOS 5。

具体来说,我正在尝试缓存UITextPositions,因为UITextView 显然采用非缓存的迭代方法来获取UITextPositions,这意味着它的执行速度非常非常慢positionFromPosition:textview.beginningOfDocument offset:600011,但获得positionFromPosition:aPositionAt600000 offset:11 的速度要快得多。事实上,在我的测试案例中,前者需要超过 100 秒(在主线程上!),而后者几乎是瞬时的。

【问题讨论】:

    标签: objective-c ios multithreading uikit uitextview


    【解决方案1】:

    为什么要在主线程上做呢?典型的答案是在后台线程上执行这些操作,并将 UI 更新发送回主线程。例如,您可以使用Grand Central Dispatch:

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // do my time consuming task and everytime it wants to update the UI, 
        // it should dispatch that back to the main queue, e.g.
    
        for (NSInteger i = 0; i < 10000; i++)
        {
            // do my background work
    
            // now update the UI
            dispatch_async(dispatch_get_main_queue(), ^{
                // update the UI accordingly
            });
        }
    });
    

    更新:

    听起来您必须在前台执行此操作,因此使用NSTimer 可能会更好。我不是 NSTimer 的大人物,但它可能看起来像下面这样。

    首先,确保你有一个类实例变量:

    NSTimer *_timer;
    

    接下来,您可以使用以下命令对其进行初始化:

    - (void)startTimer
    {
        _timer = [NSTimer timerWithTimeInterval:0.0 target:self selector:@selector(timerCallback:) userInfo:nil repeats:YES];
        NSRunLoop *runloop = [NSRunLoop currentRunLoop];
        [runloop addTimer:_timer forMode:NSDefaultRunLoopMode];
    }
    

    这将调用 timerCallback,可能在每次调用时处理一个 UITextPosition:

    - (void)timerCallback:(NSTimer*)theTimer
    {
        BOOL moreTextPositionsToCalculate = ...;
    
        if (moreTextPositionsToCalculate)
        {
             // calculate the next UITextPosition
        }
        else
        {
             [self stopTimer];
        }
    }
    

    完成后,您可以像这样停止计时器:

    - (void)stopTimer
    {
        [_timer invalidate];    
        _timer = nil;
    }
    

    【讨论】:

    • 我知道 - 这就是为什么我指定我正在尝试做的事情 - 耗时的工作 UIKit 调用。 UITextView 不喜欢从后台调用它的positionFromPosition:offset: 方法,但是对于非常非常长的字符串,一次调用可能需要长达两分钟。
    • @fzwo 如果是这样的话,你不会把它包含在你分派到主队列的部分中(因此后台队列的主要任务是分派这些单独的调用到主队列)。而且,如果是这种情况,您可以将 dispatch_async(dispatch_get_main_queue(), ...) 更改为 dispatch_sync(dispatch_get_main_queue(), ...),这样您就不会用 10000 个任务埋葬主队列。但是我会认为一次将这些单独的调用分派到主队列也可以使它有机会执行其他操作。有趣的问题。
    • @fzwo 回想起来,我认为 NSTimer 在这里很有意义,而不是 GCD 后台线程。
    • 谢谢你,这行得通! UI 感觉很迟钝,但至少它完全有反应。希望有更好的方法,可以安全地重新实现 UITextView(我可能实际上需要这样做......)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-25
    • 1970-01-01
    相关资源
    最近更新 更多