Управление процессорной нагрузкой на iOS - PullRequest
4 голосов
/ 14 сентября 2011

Я опытный программист на C / C ++, который быстро работает над Objective C на iPhone.Я много занимался поиском, но не нашел удовлетворительного ответа на вопрос, который должен быть общим;Я прошу прощения, если на это ответят в другом месте, указатели будут оценены.

Мое приложение очень загружает процессор.Пользовательский интерфейс имеет простой дисплей, который показывает прогресс и кнопку пуска / остановки.Каков наилучший способ выделить наиболее возможные циклы ЦП для выполнения работы, при этом обеспечивая постоянное обновление дисплея и отзывчивость кнопки «Пуск / Стоп»?Я читал, что вы не должны выполнять работу в главном потоке, но кроме этого я не нашел много предложений.В свете этого я реализовал свою работу в очереди NSOperation.Я также поместил обновление экрана в свою очередь.Я также обильно посыпал код NSThread sleepForTimeIntervals.Я экспериментировал с разным временем сна от .001 до 1 (например, [NSThread sleepForTimeIntervals .1]).Несмотря на это, экран в лучшем случае медленный (10 секунд), и нажатие кнопки «Стоп» выделяет кнопку, но в течение 10 секунд ничего не происходит.

1.) Являются ли Очереди Операций разумным выбором?Если нет, то что еще?2.) Как мне минимизировать сон?(Очевидно, я хочу, чтобы работа выполняла как можно больше циклов, насколько это возможно / разумно, и я не уверен, что мои сны вообще что-то делают для обновления всего пользовательского интерфейса.) 3.) Есть ли лучший способ сохранить пользовательский интерфейсдо настоящего времени?Например, могу ли я использовать NSTimer или какой-либо другой метод для отправки сообщения в пользовательский интерфейс с указанием обновить и / или проверить состояние кнопок?

Спасибо за вашу поддержку.

Ответы [ 4 ]

5 голосов
/ 14 сентября 2011

1.) Является ли Очередь NSOperation разумным выбором?Если нет, то что еще?

NSOperationQueue звучит так, как будто это было бы разумно.

конечно, у вас есть выбор: pthreads, libdispatch (он же GCD), библиотеки потоков c ++, построенные сверхуpthreads и т. д. и т. п., если вы не порождаете много / много, то это просто сводится к модели, которую вы предпочитаете.

2.) Как минимизировать сон?(Очевидно, я хочу, чтобы работа выполняла как можно больше циклов, насколько это возможно / разумно, и я не уверен, что мои сны вообще что-то делают для обновления всего пользовательского интерфейса.)

неsleep =) вы можете использовать таймер для ваших элементов пользовательского интерфейса или явный обратный вызов или уведомление для уведомления о зависимостях.если зависимости обновляют пользовательский интерфейс, вы, скорее всего, добавите сообщение в очередь сообщений основного потока.

3.) Есть ли лучший способ поддерживать интерфейс в актуальном состоянии?Например, могу ли я использовать NSTimer или какой-либо другой метод для отправки сообщения в пользовательский интерфейс с указанием обновить и / или проверить состояние кнопок?

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

другая альтернатива более полезна для событий или этапов: она будет включать публикацию обновлений (например, уведомлений или обратных вызовов делегату) из вторичного потока по мере выполнения прогресса(дополнительная информация под # 2).

Обновление

Я не был уверен, что это подходит для модели iOS, но звучит так.

да, все в порядке - вы можете взять много аппарелей.«лучший» зависит от контекста.

В настоящее время я понимаю, что пользовательский интерфейс запускается в одном потоке (а не в главном!),

Вы действительно этого не делаете• явно запустить пользовательский интерфейс;основной поток (как правило) управляется передачей событий и сообщений в основной поток.основной поток использует цикл выполнения и обрабатывает поставленные в очередь сообщения / события на каждой итерации цикла выполнения.Вы также можете запланировать эти сообщения в будущем (подробнее об этом чуть позже).Сказав это, все ваши сообщения объектам UIKit и AppKit (если вы нацелены на osx) должны быть в главном потоке (как обобщение, которое вы в конечном итоге узнаете, есть исключения из этого).если у вас есть конкретная реализация, которая полностью отделена от методов объектов UIKit для обмена сообщениями и эта программа является поточно-ориентированной, то вы можете фактически выполнять эти сообщения из любого потока, поскольку это не влияет на состояние реализации UIKit.Простейший пример:

@interface MONView : UIView
@end

@implementation MONView
// ...
- (NSString *)iconImageName { return @"tortoise.png"; } // pure and threadsafe
@end

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

, чтобы сделать это, вы можете использовать подход, подобный следующему:

@interface MONView : UIView
{
    NSTimer * timer;
    MONAsyncWorker * worker; // << this would be your NSOperation subclass, if you use NSOperation.
}

@end

@implementation MONView

// callback for the operation 'worker' when it completes or is cancelled.
- (void)workerWillExit
{
    assert([NSThread isMainThread]); // call on main

    // end recurring updates
    [self.timer invalidate];
    self.timer = nil;

    // grab what we need from the worker
    self.worker = nil;
    // update ui
}

// timer callback
- (void)timerUpdateCallback
{
    assert([NSThread isMainThread]); // call on main
    assert(self.worker);

    double progress = self.worker.progress;

    [self updateProgressBar:progress];
}

// controller entry to initiate an operation
- (void)beginDownload:(NSURL *)url
{
    assert([NSThread isMainThread]); // call on main
    assert(nil == worker); // call only once in view's lifetime

    // create worker
    worker = [[MONAsyncWorker alloc] initWithURL:url];
    [self.operationQueue addOperation:worker];

    // configure timer
    const NSTimeInterval displayUpdateFrequencyInSeconds = 0.200;
    timer = [[NSTimer scheduledTimerWithTimeInterval:displayUpdateFrequencyInSeconds target:self selector:@selector(timerUpdateCallback) userInfo:nil repeats:YES] retain];
}

@end

Обратите внимание, что это очень примитивная демонстрация.также чаще помещают таймер, обработку обновлений и операции в контроллере представления, а не в представлении.

3 голосов
/ 14 сентября 2011

Вы обновляете пользовательский интерфейс в основном потоке? Это очень важно, потому что UIKit не является поточно-ориентированным, и использование его из вторичного потока может привести к вялому поведению (или к тому же сбоям). Обычно вам не нужно использовать sleep в фоновых потоках / очередях, чтобы пользовательский интерфейс оставался отзывчивым (если только сам пользовательский интерфейс не сильно загружен процессором, но здесь это не так).

Вы можете проверить любой из ваших методов, которые обновляют пользовательский интерфейс, если они работают в главном потоке с помощью чего-то вроде

NSAssert([NSThread isMainThread], @"UI update not running on main thread");

Простой и легкий способ синхронизировать обновления пользовательского интерфейса с основным потоком - использовать Grand Central Dispatch:

dispatch_async(dispatch_get_main_queue(), ^ { 
    //do your UI updates here... 
});
2 голосов
/ 14 сентября 2011

Вот мои ответы на ваши вопросы.

1) Поскольку вы являетесь опытным программистом C, вы будете чувствовать себя комфортно с Grand Central Dispatch (GCD), API на основе C для параллелизма.

2) С GCD вам вообще не нужно спать. Просто асинхронно отправьте работу, которую вам нужно выполнить в очереди, используя максимальный приоритет (DISPATCH_QUEUE_PRIORITY_HIGH).

3) Когда вам нужно обновить пользовательский интерфейс, просто отправьте в основную очередь (в том же блоке, выполняющем работу, используя dispatch_get_main_queue ()) обновление пользовательского интерфейса по мере необходимости.

Посмотрите соответствующую документацию GCD здесь .

1 голос
/ 14 сентября 2011

У меня был бы объект модели, который выполняет задачи процессора, который имеет обратный вызов делегата для изменения выходных данных и контроллер представления.В viewDidLoad вы устанавливаете контроллер представления как делегат вашей модели.Следовательно, модель может использовать потоки и отправлять сообщения обратно в основную очередь, когда вычисленные данные были обновлены.Если ваш случай не является особенно сложным, просто используйте Grand Central Dispatch и отправьте интенсивную задачу в другой поток.

Конечно, вам не следует нигде вызывать sleepForTimeInterval для достижения желаемого.

...