如何在长时间运行的循环中更新Cocoa中的进度条?

时间:2010-03-24 16:47:44

标签: objective-c cocoa macos

我有一个while循环,运行了很多秒,这就是为什么我想在该过程中更新进度条(NSProgressIndicator),但它在循环结束后只更新一次。顺便说一句,如果我想要更新标签文本,也会发生同样的情况。

我相信,我的循环会阻止该应用程序的其他事情发生。必须有另一种技术。这与线程有什么关系吗?我是在正确的轨道上吗?有人可以给我一个简单的例子,如何“优化”我的应用程序?

我的应用程序是一个Cocoa应用程序(Xcode 3.2.1),在我的Example_AppDelegate.m中有这两种方法:

// This method runs when a start button is clicked.
- (IBAction)startIt:(id)sender {
    [progressbar setDoubleValue:0.0];
    [progressbar startAnimation:sender];
    running = YES; // this is a instance variable

    int i = 0;
    while (running) {
        if (i++ >= processAmount) { // processAmount is something like 1000000
            running = NO;
            continue;
        }

        // Update progress bar
        double progr = (double)i / (double)processAmount;
        NSLog(@"progr: %f", progr); // Logs values between 0.0 and 1.0
        [progressbar setDoubleValue:progr];
        [progressbar needsDisplay]; // Do I need this?

        // Do some more hard work here...
    }
}

// This method runs when a stop button is clicked, but as long
// as -startIt is busy, a click on the stop button does nothing.
- (IBAction)stopIt:(id)sender {
    NSLog(@"Stop it!");
    running = NO;
    [progressbar stopAnimation:sender];
}

我是Objective-C,Cocoa和带UI的应用程序的新手。非常感谢您提供任何有用的答案。

4 个答案:

答案 0 :(得分:15)

如果您正在为Snow Leopard构建,我认为最简单的解决方案是使用块和Grand Central Dispatch。

以下代码显示了使用GCD时startIt:方法的外观。

您的stopIt:方法在编写时应该可以正常工作。它之前没有工作的原因是鼠标事件发生在主线程上,因此按钮没有响应你,因为你正在主线程上工作。现在应该已经解决了这个问题,因为现在使用GCD将工作放在不同的线程上。试试这些代码,如果它不起作用,请告诉我,我会看看是否有错误。

// This method runs when a start button is clicked.
- (IBAction)startIt:(id)sender {

    //Create the block that we wish to run on a different thread.
    void (^progressBlock)(void);
    progressBlock = ^{

    [progressbar setDoubleValue:0.0];
    [progressbar startAnimation:sender];
    running = YES; // this is a instance variable

    int i = 0;
    while (running) {
        if (i++ >= processAmount) { // processAmount is something like 1000000
            running = NO;
            continue;
        }

        // Update progress bar
        double progr = (double)i / (double)processAmount;
        NSLog(@"progr: %f", progr); // Logs values between 0.0 and 1.0

        //NOTE: It is important to let all UI updates occur on the main thread,
        //so we put the following UI updates on the main queue.
        dispatch_async(dispatch_get_main_queue(), ^{
            [progressbar setDoubleValue:progr];
            [progressbar setNeedsDisplay:YES];
        });

        // Do some more hard work here...
    }

    }; //end of progressBlock

    //Finally, run the block on a different thread.
    dispatch_queue_t queue = dispatch_get_global_queue(0,0);
    dispatch_async(queue,progressBlock);
}

答案 1 :(得分:3)

您可以尝试此代码..

[progressbar setUsesThreadedAnimation:YES];

答案 2 :(得分:2)

  

我相信,我的循环会阻止该应用程序的其他事情发生。

正确。你需要以某种方式解决这个问题。

一种方法是计时器,你可以在计时器回调中一次一点地缩小队列。另一种方法是将代码包装起来处理NSOperation子类中的一个项目,并创建该类的实例(操作)并将它们放入NSOperationQueue中。

  

这与线程有什么关系吗?

不一定。 NSOperations在线程上运行,但NSOperationQueue将处理为您生成线程。计时器是一种单线程解决方案:每个计时器都在您计划的线程上运行。这可能是一个优点或缺点 - 你决定。

有关详细信息,请参阅the threads section of my intro to Cocoa

答案 3 :(得分:2)

这对我有用,这是其他人的答案的组合,这些答案对我们来说似乎不起作用(至少对我而言):

dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
  //do something
  dispatch_async(dispatch_get_main_queue(), ^{
    progressBar.progress = (double)x / (double)[stockList count];            
  });  
  //do something else
});