处理 UIActivityIndicator 和多线程的最佳方法是什么?
我已经尝试解决这个问题很长时间了,但似乎找不到最好的方法。我很困惑,因为对于如何完成这个看似简单的任务似乎有不同的答案/意见。
我希望能够有一个名为 ActivityIndicatorController 的可重用类。该控制器有两个主要方法:activateIndicator 和 deactivateIndicator。它采用 UIView 作为参数/属性以及 NSString 作为标签。激活后,它将关闭 UIView 中的用户交互,并添加一个矩形子视图(带有 alpha 和圆角)、一个 UIActivityIndicator 控件和一个用于状态文本的 UILabel。这是可取的,因为这样我就不必在每个视图控制器中都有自定义 UIActivityIndicatorView 代码,也不必在每个 NIB 中设置 ActivityIndicator。
我从根本上遇到的问题是如何启动添加 ActivityIndicator 并对其进行动画处理的过程。我尝试过的一些方法根本不显示新视图。其他的可以工作,但 ActivityIndicator 没有动画。
我尝试在 activateIndicator 方法中使用 [NSThread detachNewThreadSelector:@selector(startAnimating) toTarget:activityIndicator withObject:nil] ,但这不会显示新的 UIView。
我尝试在调用方法中使用 [NSThread detachNewThreadSelector:@selector(activateIndicator) toTarget:activityIndicatorController withObject:nil] ,但这会将新 UIView 的整个创建放在一个单独的线程中。
现在回答问题:
第 1 部分:我理解所有 UI 都应该在主线程上处理,这是正确的吗?
第 2 部分:使用 [NSThread detachThreadSelector] 与 NSOperation 的区别/优点/缺点是什么?
第 3 部分:是否更好:
(a) 将冗长的操作发送到新的后台线程并回调主线程,或者
(b) 将 UIActivityIndicatorView 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;
I have been trying to play around with this for a long time and I can't seem to find the best approach. I am getting confused because there seem to be different answers/opinions on how to accomplish this seemingly simple task.
I want to be able to have a reusable class called ActivityIndicatorController. This controller has two main methods: activateIndicator and deactivateIndicator. It takes a UIView as an argument/property as well as an NSString for a label. Upon activation, it will turn off user interaction in the UIView and add a rectangle subview (with alpha and rounded corners), a UIActivityIndicator control and a UILabel for the status text. This is desirable because that way I don't have to have custom UIActivityIndicatorView code in each view controller or to have to set up an ActivityIndicator in each NIB.
The problem I am fundamentally having is how to kick off this process of adding and animating the ActivityIndicator. Some methods I have tried don't display the new view at all. Others work, but the ActivityIndicator doesn't animate.
I have tried using [NSThread detachNewThreadSelector:@selector(startAnimating) toTarget:activityIndicator withObject:nil] inside the activateIndicator method, but that doesn't display the new UIView.
I have tried using [NSThread detachNewThreadSelector:@selector(activateIndicator) toTarget:activityIndicatorController withObject:nil] from a calling method, but this would put the whole creation of the new UIView in a separate thread.
Now to the question:
Part 1: I understand that all UI should be handled on the main thread, is that correct?
Part 2: What is the difference/advantage/disadvantage of using [NSThread detachThreadSelector] versus NSOperation?
Part 3: Is it better to:
(a) send the lengthy operation to a new background thread with a callback to the main thread OR
(b) send the UIActivityIndicatorView startAnimating method to a separate thread and run the lengthy process on the main thread
AND why?
Here is my current code:
ActivityViewController class:
-(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];
}
Calling Code from multiple places in the app:
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;
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
正确的。
NSOperation
是一个更高级别的接口,允许您对操作进行排队、创建多个相互依赖的操作等。在后台处理任务的其他选项有performSelectorOnMainThread:...
/performSelectorInBackground:...
和 Grand Central Dispatch。因为在问题 1 的答案中,(a) 是您唯一的选择。
Correct.
NSOperation
is a higher-level interface that allows you to queue operations, create several operations that depend on each other, etc. Other options to work with tasks in the background areperformSelectorOnMainThread:...
/performSelectorInBackground:...
and Grand Central Dispatch.Because of the answer to question 1, (a) is your only option.
将您的冗长工作放在一个单独的线程中,这样,如果您确实需要一些交互(例如取消操作),它就不会完全阻塞 UI。然后,您的 ActivityIndicatorController 应该调用主线程来执行所有 UI 操作,例如:
Put your lengthy work in a separate thread, that way it doesn't completely block out the UI in case you do need some interaction (say to cancel the operation). Then, your ActivityIndicatorController should call out to the main thread to do all the UI stuff, e.g.:
一旦显示并动画化,即使主线程被阻塞,活动指示器也会继续动画化。
然而,视图没有机会出现,因为运行循环尚未执行,因为等待冗长的操作。
所以我认为您需要的只是延迟为 0 的
performSelector:withObject:afterDelay
,这样您的冗长操作将在指标变得可见后排队并执行。Once displayed and animated, the activity indicator will continue animating even the main thread is blocked.
However, the view has no chance to come up because the run loop has yet to executed because pending lengthy operation.
So I think what you need is just
performSelector:withObject:afterDelay
with delay 0, so your lengthy operation will be queued and performed after your indicator become visible.