处理 UIActivityIndi​​cator 和多线程的最佳方法是啥?

Posted

技术标签:

【中文标题】处理 UIActivityIndi​​cator 和多线程的最佳方法是啥?【英文标题】:What is the best way to deal with UIActivityIndicator and multiple threads?处理 UIActivityIndi​​cator 和多线程的最佳方法是什么? 【发布时间】:2010-11-22 16:13:11 【问题描述】:

我已经尝试了很长时间,但似乎找不到最佳方法。我很困惑,因为对于如何完成这个看似简单的任务似乎有不同的答案/意见。

我希望能够拥有一个名为 ActivityIndi​​catorController 的可重用类。该控制器有两个主要方法:activateIndicator 和 deactivateIndicator。它需要一个 UIView 作为参数/属性以及一个标签的 NSString。激活后,它将关闭 UIView 中的用户交互并添加一个矩形子视图(带有 alpha 和圆角)、一个 UIActivityIndi​​cator 控件和一个用于状态文本的 UILabel。这是可取的,因为这样我不必在每个视图控制器中都有自定义 UIActivityIndi​​catorView 代码,也不必在每个 NIB 中设置 ActivityIndi​​cator。

我遇到的根本问题是如何启动添加和动画化 ActivityIndi​​cator 的过程。我尝试过的一些方法根本不显示新视图。其他人工作,但 ActivityIndi​​cator 没有动画。

我曾尝试在 activateIndicator 方法中使用 [NSThread detachNewThreadSelector:@selector(startAnimating) toTarget:activityIndi​​cator withObject:nil],但这不会显示新的 UIView。

我曾尝试在调用方法中使用 [NSThread detachNewThreadSelector:@selector(activateIndicator) toTarget:activityIndi​​catorController withObject:nil],但这会将新 UIView 的整个创建放在一个单独的线程中。

现在问题来了:

第 1 部分:我知道所有 UI 都应该在主线程上处理,对吗?

第 2 部分:使用 [NSThread detachThreadSelector] 与 NSOperation 有什么区别/优点/缺点?

第 3 部分:是否更好:

(a) 将冗长的操作发送到一个新的后台线程,并回调主线程或

(b) 将 UIActivityIndi​​catorView 的 startAnimating 方法发送到单独的线程并在主线程上运行冗长的进程

为什么?

这是我当前的代码:

ActivityViewController 类:

-(void)activateIndicator 
NSLog(@"activateIndicator called");
if (isActivated || !delegateView)
    return;
NSLog(@"activateIndicator started");

[delegateView.view setUserInteractionEnabled:NO];
[delegateView.navigationController.view setUserInteractionEnabled:NO];
[delegateView.tabBarController.view setUserInteractionEnabled:NO];

float w = [[UIScreen mainScreen] bounds].size.width;
float h = [[UIScreen mainScreen] bounds].size.height;

NSLog(@"Width = %f\nHeight = %f", w, h);

if (!disabledView) 
    disabledView = [[[UIView alloc] initWithFrame:CGRectMake((w - kNormalWidth) / 2.0, (h - kNormalHeight) / 2.0, kNormalWidth, kNormalHeight)] autorelease];
    disabledView.center = [[[delegateView.view superview] superview] center];
    [disabledView setBackgroundColor:[UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.85]];

CALayer *layer = [disabledView layer];
NSLog(@"layer=%@",layer);
NSLog(@"delegate=%@",[layer delegate]);
layer.cornerRadius = 12.0f;


if (!activityIndicator) 
    activityIndicator = [[[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(kNormalWidth / 2, 10.0f, 40.0f, 40.0f)] autorelease];
    [activityIndicator setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleWhiteLarge];
    activityIndicator.center = disabledView.center;


if (!activityLabel) 
    activityLabel = [[[UILabel alloc] initWithFrame:CGRectMake(10.0f, 100.0f, kNormalWidth - 20, 38)] autorelease];
    activityLabel.text = labelText;
    activityLabel.textAlignment = UITextAlignmentCenter;
    activityLabel.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.0f];
    activityLabel.textColor = [UIColor colorWithWhite:1.0f alpha:1.0f];
    activityLabel.center = disabledView.center;


[[[delegateView.view superview] superview] addSubview:disabledView];

[[[delegateView.view superview] superview] addSubview:activityIndicator];
[[[delegateView.view superview] superview] addSubview:activityLabel];


[NSThread detachNewThreadSelector:@selector(startAnimating) toTarget:activityIndicator withObject:nil];

从应用中的多个位置调用代码:

    ActivityIndicatorController *aic = [[ActivityIndicatorController alloc] init];
aic.delegateView = self;
aic.labelText = @"Test...";
[aic activateIndicator];

//DO LENGTHY WORK ON MAIN THREAD

[aic deactivateIndicator];
[aic release], aic = nil;

【问题讨论】:

您可能想要实际添加您的代码;) 是的,我添加了它。我不得不从我的另一台机器上抓取它。 【参考方案1】:

第 1 部分:我知道所有 UI 都应该在主线程上处理,对吗?

正确。

第 2 部分:使用 [NSThread detachThreadSelector] 与 NSOperation 有什么区别/优点/缺点?

NSOperation 是一个更高级别的接口,允许您对操作进行排队、创建多个相互依赖的操作等。在后台处理任务的其他选项是performSelectorOnMainThread:.../performSelectorInBackground:... 和 Grand Central调度。

第 3 部分:是否更好:

(a) 将冗长的操作发送到一个新的后台线程,并回调主线程或

(b) 将 UIActivityIndi​​catorView 的 startAnimating 方法发送到单独的线程并在主线程上运行冗长的进程

由于问题 1 的答案,(a) 是您唯一的选择。

【讨论】:

那么我上面代码中的 startAnimating 方法呢?我是保持原样还是将其更改为这个? [activityIndi​​cator startAnimating];知道调用方法将负责将所有冗长的工作转移到另一个线程吗? detachNewThreadSelector 绝对是错误的。您必须确保基本上整个 -activateIndicator 方法在主线程上执行,方法是让您的控制器将其推送到主线程上,或者在任何其他线程上引发调用代码调用的异常。 [NSThread isMainThread] 告诉你是否在主线程上。 所以,为了清楚起见,从您的回答和 Daniel Dickson 的回答看来,我应该执行以下操作: 1. 从主线程调用 activateIndicator。 2. 我可以使用 [NSThread isMainThread] 来确保仅从主线程调用 activateIndicator。 3. 然后,我可以使用 NSOperation 来启动这个漫长的过程。 4. 一旦这个冗长的过程完成,我可以使用 performSelectorOnMainThread 让它在主线程上使用回调。 5. 该回调将进行任何清理,然后调用 deactivateIndicator。对吗? 我会回答我自己的评论。这种方式看起来确实很好用。【参考方案2】:

将您的冗长工作放在一个单独的线程中,这样它就不会完全屏蔽 UI,以防您确实需要一些交互(比如取消操作)。然后,您的 ActivityIndi​​catorController 应该调用主线程来完成所有 UI 工作,例如:

@implementation ActivityIndicatorController

    - (void)activateIndicator
    
        [self performSelectorOnMainThread:@selector(activateOnMainThread)
                               withObject:nil
                            waitUntilDone:YES];
    

    - (void)activateOnMainThread
    
        // Do your actual UI stuff here.
    

    // And similarly for the deactivate method.

【讨论】:

感谢您的回复。你说“在这里做你实际的 UI 工作”是指我现在在 activateIndicator 中的大部分代码应该移动到你的新方法 activateOnMainThread 吗? 另外,“startAnimating”是否会以与上述相同的方式或 [activityIndi​​cator startAnimating] 的方式运行? 是的,将任何创建或修改视图的内容移至activateOnMainThread。由于该方法将在主线程上运行,因此您可以直接调用 UIKit 方法,例如[activityIndicator startAnimating]【参考方案3】:

一旦显示和动画,即使主线程被阻塞,活动指示器也会继续动画。

但是,视图没有机会出现,因为运行循环尚未执行,因为等待冗长的操作。

所以我认为您需要的只是延迟为 0 的 performSelector:withObject:afterDelay,因此您的冗长操作将在您的指示器可见后排队并执行。

【讨论】:

以上是关于处理 UIActivityIndi​​cator 和多线程的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

很难将 UIActivityIndi​​cator 放置在 UIButton 中

重新加载 UICollectionView 时 UIActivityIndi​​cator 不会停止

UIActivityIndi​​cator 在构建 UIView 层次结构时旋转?

UICollectionView 单元格中的 UIActivityIndi​​cator 随机不会隐藏

UIActivityIndi cator添加AppDelegate

我可以更改 UIActivityIndi​​cator 的大小吗?