使用后台线程的进度更新UIAlertView

时间:2012-10-18 14:34:36

标签: objective-c

XCode 4.5,iPad开发,iOS6

嗨,我希望你能帮助新手开发者!如果已经回答但我在搜索期间找不到,请提前道歉!

我正在开发一个需要将大量数据导入Core Data的应用程序。导入例程工作正常(警报显示'请等待'与活动监视器,而例程在后台工作)但我想向用户提供有关导入进度的更详细的反馈(例如'XX%导入')。以下代码将关闭该过程并启用 -

- (IBAction)import:(id)sender{

[self showWaiting];

[self performSelectorInBackground:(@selector(callGrouper)) withObject:nil];


}

-(void)showWaiting{

alertMsg = @"Please Wait....";
waitAlert = [[UIAlertView alloc] initWithTitle:alertMsg message:nil delegate:self  cancelButtonTitle:nil otherButtonTitles: nil];


[waitAlert show];     

UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];

indicator.center = CGPointMake(waitAlert.bounds.size.width / 2, waitAlert.bounds.size.height - 50); 

[indicator startAnimating];
[waitAlert addSubview:indicator];    

}


-(void)callGrouper{

ImportRoutine *firstTest = [[ImportRoutine alloc] init];

[firstTest runImport:managedObjectContext];


 [waitAlert dismissWithClickedButtonIndex:0 animated:TRUE];

UIAlertView *alert = [[UIAlertView alloc]initWithTitle: @"iPad Application"
                                               message: @"Import complete!"
                                              delegate: self
                                     cancelButtonTitle:@"Ok"
                                     otherButtonTitles:nil];

[alert show];

}

在ImportRoutine(单独的类)中,我有代码收集导入百分比的数据但是如何将此消息传递回主线程,以便我可以更新'alertMsg'然后更新UIAlertView?

2 个答案:

答案 0 :(得分:2)

您可以使用GCD(大中央调度)将代码块分发回主线程:

dispatch_async(dispatch_get_main_queue(), ^{

    // code here to update UI
});

包含调度调用的方法范围内的任何对象都会被保留,这样可以很容易地将对象传回主线程,而无需担心在您有机会之前将后台线程与其对象一起解除分配处理数据。复制本地作用域中的原始值(也就是int,float,double等),因此如果将int设置为5,则调度一个块来打印int的值,然后在将int设置为10之后立即执行,即使在将int设置为10之后块执行它仍然会打印5.注意,你不能同时在两个线程中改变相同的可变对象(例如`NSMutableArray或NSMutableDictionary)或者在一个中变异在没有崩溃的情况下列举在另一个中,所以你要小心做类似的事情(感谢@andrewmadsen提醒我警告你。)

dispatch_async()不同,

dispatch_sync()不会等待在继续执行之前完成调度的代码,这很好,因为后台线程不需要关心UI中的内容是否已经完成。

只要你的UIAlertView可以在你的视图控制器类之外寻址,你就可以在ImportRoutine类的方法中粘贴调度调用来计算进度。或者,如果您想更密切地遵循模型 - 视图 - 控制器设计原则,您可以在视图控制器中创建这样的方法:

- (void)updateProgressToPercentComplete:(double)percent {
    if ([NSThread currentThread] != [NSThread mainThread]) {
        dispatch_async(dispatch_get_main_queue(), ^{
            // update code or call to method that is guaranteed to be on the main thread.
        }
    }
    else {
        // update code or call to method that is guaranteed to be on the main thread.
    }
}

如果你已经进入了文档,现在你就像“哦,我的gosh Objective-C块是有史以来最酷的东西”你可以修改上面的方法,所以你不需要编写相同的更新代码两次:

- (void)updateProgressToPercentComplete:(double)percent {
    void (^updateProgressBlock)(void) = ^{
        // update code
    };
    if ([NSThread currentThread] != [NSThread mainThread]) {
        dispatch_async(dispatch_get_main_queue(), updateProgressBlock());
    }
    else {
        updateProgressBlock();
    }
}

顺便提一下,我在你的-callGrouper代码中注意到你正在使用现有的managedObjectContext,我假设你是在后台线程中的主线程上创建的......大部分核心数据都不是线程安全的,所以你需要非常小心,否则你会在整个地方崩溃。您可能最好在后台线程上创建辅助托管对象上下文,然后将更改合并到主线程上的上下文中(或保存在后台线程上并在主线程上重新获取)。

修改
基本流程:从视图控制器开始后台进程并传入进度块。 - >后台线程中的导入类定期执行进度块 - >在进度块内部,您将调度回主线程以更新UI。

在ImportRoutine类中添加如下所示的属性声明:

@property (nonatomic, strong) void (^progressBlock)(NSUInteger);

这意味着名为progressBlock的属性采用无符号整数(0-100)并且不返回任何内容(void)。您应该使用类扩展名将此属性设为私有。

然后你需要在导入类中创建一个方法,如下所示:

- (void)callGrouper:(void (^)(NSUInteger))progress {
    [self setProgressBlock:progress];
    // Your import code
}

在接收进度更新的方法中,调用progressBlock并将进度作为0到100之间的数字传递:

if ([self progressBlock] != nil) {
    [self progressBlock](progressValue);
}

请注意,我检查以确保进度块不是nil。如果您尝试执行NULL块,则会崩溃并刻录。

然后,您可以传入一个块作为您在视图控制器中已经拥有的导入例程调用中的对象,并在块调度内部返回主队列并更新您的进度。

答案 1 :(得分:1)

您可以使用:

[self performSelectorOnMainThread:@selector(yourSelector) withObject:anObjectIfYouNeedToSendOne waitUntilDone:YES/NO];

UI在主线程上运行,因此您可以再次访问UIAlertView或其他UI对象。