Как мне обновить индикатор выполнения в Какао во время длинного цикла?

У меня есть while цикл, который выполняется в течение многих секунд, и поэтому я хочу обновить индикатор выполнения (NSProgressIndicator) во время этого процесса, но он обновляется только один раз после завершения цикла. Кстати, то же самое происходит, если я хочу обновить текст метки.

Полагаю, мой цикл предотвращает появление других вещей в этом приложении. Должна быть другая техника. Это связано с темами или чем-то? Я на правильном пути? Может кто-нибудь дать мне простой пример, как "оптимизировать" мое приложение?

Мое приложение - Приложение Какао (Xcode 3.2.1) с этими двумя методами в моем Example_AppDelegate.m:

// Этот метод запускается при нажатии кнопки запуска.
- (IBAction)startIt:(id) отправитель {
    [progressbar setDoubleValue:0.0];
    [прогрессбар startAnimation: отправитель]; бег = ДА; // это переменная экземпляра int i = 0;
    while (running) {
        if (i++ >= processAmount) { // processAmount - это что-то вроде 1000000
            running = NO; Продолжить;
        }

        // Обновить индикатор выполнения double progr = (double)i / (double)processAmount;
        NSLog(@"progr: %f", progr); // Записывает значения от 0,0 до 1,0
        [progressbar setDoubleValue:progr];
        [progressbar needsDisplay]; // Мне это нужно?

        // Выполните здесь более тяжелую работу...
    }
}

// Этот метод запускается, когда нажимается кнопка остановки, но пока //startIt занят, нажатие кнопки остановки ничего не делает.
- (IBAction)stopIt:(id)sender {
    NSLog(@"Stop it!"); работает = НЕТ; [прогрессбар stopAnimation: отправитель];
}

Я действительно новичок в Objective-C, какао и приложениях с пользовательским интерфейсом. Большое спасибо за любой полезный ответ.

4 ответа

Решение

Если вы строите для Snow Leopard, на мой взгляд, самое простое решение - использовать блоки и Grand Central Dispatch.

Следующий код показывает вам, как ваш startIt: Метод будет выглядеть при использовании GCD.

Ваш 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);
}

Вы можете попробовать этот код..

[progressbar setUsesThreadedAnimation:YES];

Это сработало для меня, что является комбинацией ответов от других, которые, кажется, не работают (по крайней мере для меня) сами по себе:

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
});

Полагаю, мой цикл предотвращает появление других вещей в этом приложении.

Правильный. Вам нужно как-то это разбить.

Одним из способов может быть таймер, когда вы постепенно убираете очередь в обратном вызове таймера. Другим было бы заключить в оболочку код для обработки одного элемента в подклассе NSOperation, а также создать экземпляры этого класса (операций) и поместить их в NSOperationQueue.

Это связано с темами или чем-то?

Не обязательно. NSOperations выполняется на потоках, но NSOperationQueue будет обрабатывать порождение потока для вас. Таймер - это однопоточное решение: каждый таймер работает в потоке, на который вы его запланировали. Это может быть преимуществом или недостатком - вы решаете.

Смотрите раздел темы моего вступления к Какао для более подробной информации.

Другие вопросы по тегам