3

XCode 4.5、iPad 开发、iOS6

嗨,我希望你能帮助一个新手开发者!如果已经回答了这个问题,但我在搜索过程中找不到,请提前道歉!

我正在开发一个需要将大量数据导入核心数据的应用程序。导入例程工作正常(警报显示“请稍候”和活动监视器,而例程在后台运行)但我想向用户提供有关导入进度的更详细的反馈(例如“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?

4

2 回答 2

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),或者在一个线程中改变枚举另一个而不会崩溃,所以你'

dispatch_async(), 不像dispatch_sync(), 在继续执行之前不会等待分派的代码完成,这很好,因为您的后台线程不需要关心 UI 中的事情是否已经完成。

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

- (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.
    }
}

如果你已经进入文档,现在你都喜欢“哦,我的天哪,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);
}

请注意,我检查以确保进度块不为零。如果你试图执行一个 NULL 块,你会崩溃和烧毁。

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

于 2012-10-18T14:40:27.723 回答
1

您可以使用:

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

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

于 2012-10-18T14:48:48.070 回答