Статьи

Работа с классом NSOperationQueue

Многозадачность предотвращает зависание приложений. В большинстве языков программирования добиться этого немного сложно, но класс NSOperationQueue в iOS облегчает задачу!

Этот учебник покажет, как использовать класс NSOperationQueue . Объект NSOperationQueue — это очередь, которая обрабатывает объекты типа класса NSOperation . Проще говоря, объект NSOperation представляет одну задачу, включающую в себя как данные, так и код, связанный с задачей . NSOperationQueue обрабатывает и управляет выполнением всех объектов NSOperation (задач), которые были добавлены к нему. Выполнение происходит с основным потоком приложения. Когда объект NSOperation добавлен в очередь, он выполняется немедленно и не покидает очередь, пока не завершится. Задача может быть отменена, но она не удаляется из очереди, пока не будет завершена. Класс NSOperation является абстрактным, поэтому его нельзя использовать непосредственно в программе. Вместо этого есть два предоставленных подкласса, класс NSInvocationOperation и класс NSBlockOperation . Я буду использовать первый в этом уроке.


Вот цель этого урока: для каждого дополнительного потока мы хотим, чтобы наше приложение создавало объект NSInvocationOperation (NSOperation). Мы добавим каждый объект в NSOperationQueue, а затем мы закончим. Очередь берет на себя ответственность за все, и приложение работает без замораживания. Чтобы наглядно продемонстрировать использование классов, о которых я упоминал выше, мы создадим (простой) пример проекта, в котором, помимо основного потока приложения, у нас будет запущено еще два потока. В первом потоке цикл будет выполняться от 1 до 10 000 000, и каждые 100 шагов метка будет обновляться значением счетчика цикла. Во втором потоке фон метки будет заполнен собственным цветом. Этот процесс будет происходить внутри цикла и будет выполняться более одного раза. Так что у нас будет что-то вроде цветового ротатора. В то же время рядом с меткой будут отображаться значения RGB пользовательского цвета фона и значение счетчика цикла. Наконец, мы будем использовать три кнопки, чтобы изменить цвет фона представления в главном потоке. Эти задачи не могут быть выполнены одновременно без многозадачности. Вот посмотрите на конечный результат:

demo_app

Начнем с создания проекта. Откройте XCode и создайте новое приложение с одним представлением .

project_template

Нажмите Next и установите имя для проекта. Я назвал это ThreadingTestApp . Вы можете использовать то же или любое другое имя, которое вам нравится.

название проекта

Следующий. завершить создание проекта.

project_creation

Нажмите на файл ViewController.xib чтобы открыть Interface Builder. Добавьте следующие элементы управления, чтобы создать интерфейс, подобный следующему изображению:

ib_sample
  1. UINavigationBar
    • Кадр (x, y, W, H): 0, 0, 320, 44
    • Tintcolor: черный цвет
    • Название: «Простая многопоточная демонстрация»
  2. UILabel
    • Рамка (x, y, W, H): 20, 59, 280, 21
    • Текст: «Счетчик в теме # 1»
  3. UILabel
    • Рамка (x, y, W, H): 20, 88, 280, 50
    • Цвет фона: светло-серый цвет
    • Цвет текста: темно-серый
    • Текст: —
  4. UILabel
    • Рамка (x, y, W, H): 20, 154, 280, 21
    • Текст: «Случайный цветовой ротатор в теме № 2»
  5. UILabel
    • Рамка (x, y, W, H): 20, 183, 100, 80
    • Цвет фона: светло-серый цвет
    • Текст: —
  6. UILabel
    • Рамка (x, y, W, H): 128, 183, 150, 80
    • Текст: —
  7. UILabel
    • Рамка (x, y, W, H): 20, 374, 280, 21
    • Текст: «Цвет фона в главной теме»
  8. UIButton
    • Рамка (x, y, W, H): 20, 403, 73, 37
    • Название: «Цвет № 1»
  9. UIButton
    • Рамка (x, y, W, H): 124, 403, 73, 37
    • Название: «Цвет № 2»
  10. UIButton
    • Рамка (x, y, W, H): 228, 403, 73, 37
    • Название: «Цвет № 3»

Для последней UILabel и трех кнопок UIB установите для параметра Autosizing значение Left — Bottom, чтобы интерфейс на iPhone 4 / 4S и iPhone 5 выглядел красиво, как показано на следующем рисунке:

Autosizing

На следующем шаге мы создадим свойства IBOutlet и методы IBAction, которые необходимы для работы нашего примера приложения. Чтобы создать новые свойства и методы и подключить их к элементам управления, будучи Интерфейсным Разработчиком, нажмите среднюю кнопку кнопки «Редактор» на панели инструментов Xcode, чтобы открыть Ассистент Редактора :

assistant_editor

Не каждому элементу управления требуется свойство аутлета. Мы добавим только одну для UILabels 3, 5 и 6соответствии с порядком, в котором они были перечислены на шаге 2 ), с именами label1, label2 и label3 .

Чтобы вставить новое свойство розетки, удерживайте клавишу «Control» + щелкните (щелкните правой кнопкой мыши) на ярлыке> щелкните «Новый источник ссылок»> «Перетащите и отпустите» в помощнике редактора . После этого укажите имя для нового свойства, как на следующих изображениях:

outlet1 Вставка нового свойства IBOutlet

outlet2
Установка имени свойства IBOutlet

Повторите описанный выше процесс три раза, чтобы подключить три UILabel к свойствам. Внутри вашего файла ViewController.h объявлены следующие свойства:

1
2
3
@property (retain, nonatomic) IBOutlet UILabel *label1;
@property (retain, nonatomic) IBOutlet UILabel *label2;
@property (retain, nonatomic) IBOutlet UILabel *label3;

Теперь добавьте методы IBAction для трех кнопок UIB. Каждая кнопка изменит цвет фона представления. Чтобы вставить новый метод IBAction, удерживая нажатой клавишу «Control» + «Клик» (щелчок правой кнопкой мыши) на кнопке UIB> щелкните «Подправить внутри»> «Перетащите и отпустите» в помощнике редактора . После этого укажите имя для нового метода. Посмотрите на следующие изображения и следующий фрагмент для имен методов:

ibaction1
Вставка нового метода IBAction
ibaction2
Установка имени метода IBAction

Повторите описанный выше процесс три раза, чтобы подключить каждую кнопку UIB к методу действия. Файл ViewController.h должен теперь содержать эти:

1
2
3
— (IBAction)applyBackgroundColor1;
— (IBAction)applyBackgroundColor2;
— (IBAction)applyBackgroundColor3;

Свойства IBOutlet и методы IBAction теперь готовы. Теперь мы можем начать кодирование.


Одна из наиболее важных задач, которые мы должны выполнить, — это NSOperationQueue объекта NSOperationQueue (наша очередь операций), который будет использоваться для выполнения наших задач во вторичных потоках. Откройте файл ViewController.h и добавьте следующее содержимое сразу после заголовка @interface ( не забудьте ViewController.h фигурные скобки ):

1
2
3
@interface ViewController : UIViewController{
    NSOperationQueue *operationQueue;
}

Кроме того, каждая задача должна иметь хотя бы один метод, который содержит код, который будет выполняться одновременно с основным потоком. Согласно counterTask описанию, первая задача метода будет называться counterTask а вторая — colorRotatorTask :

1
2
-(void)counterTask;
-(void)colorRotatorTask;

Это все, что нам нужно. Наш файл ViewController.h должен выглядеть так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
@interface ViewController : UIViewController{
    NSOperationQueue *operationQueue;
}
 
@property (retain, nonatomic) IBOutlet UILabel *label1;
@property (retain, nonatomic) IBOutlet UILabel *label2;
@property (retain, nonatomic) IBOutlet UILabel *label3;
 
— (IBAction)applyBackgroundColor1;
— (IBAction)applyBackgroundColor2;
— (IBAction)applyBackgroundColor3;
 
-(void)counterTask;
-(void)colorRotatorTask;
 
@end

Давайте перейдем к реализации.


Мы почти закончили. Мы настроили наш интерфейс, сделали все необходимые подключения, объявили все необходимые IBAction и другие методы и создали нашу базу. Теперь пришло время опираться на них.

Откройте файл ViewController.m и перейдите к методу viewDidLoad . Самая важная часть этого урока будет проходить здесь. Мы создадим новый экземпляр NSOperationQueue и два NSOperation (NSInvocationOperation) . Эти объекты будут инкапсулировать код двух методов, которые мы ранее объявили, и затем они будут выполнены самостоятельно с помощью NSOperationQueue . Вот код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
— (void)viewDidLoad
{
    [super viewDidLoad];
 
    // Create a new NSOperationQueue instance.
    operationQueue = [NSOperationQueue new];
 
    // Create a new NSOperation object using the NSInvocationOperation subclass.
    // Tell it to run the counterTask method.
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
                                                                selector:@selector(counterTask)
                                                                object:nil];
    // Add the operation to the queue and let it to be executed.
    [operationQueue addOperation:operation];
    [operation release];
 
    // The same story as above, just tell here to execute the colorRotatorTask method.
    operation = [[NSInvocationOperation alloc] initWithTarget:self
                                                    selector:@selector(colorRotatorTask)
                                                    object:nil];
    [operationQueue addOperation:operation];
    [operation release];
}

Весь этот процесс действительно прост. После создания экземпляра NSOperationQueue мы создаем объект NSInvocationOperation (операция). Мы устанавливаем его метод выбора (код, который мы хотим выполнить в отдельном потоке), а затем добавляем его в очередь. Как только он входит в очередь, он сразу же начинает работать. После этого объект операции может быть освобожден, поскольку теперь за его обработку отвечает очередь. В этом случае мы создаем другой объект и будем использовать его таким же образом для второй задачи (colorRotatorTask).

Наша следующая задача — реализовать два метода селектора. Давайте начнем с написания метода counterTask . Он будет содержать цикл for который будет выполняться для большого числа итераций, и каждые 100 шагов текст label1 будет обновляться значением счетчика текущей итерации ( i ). Код прост, так что здесь все:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
-(void)counterTask{
    // Make a BIG loop and every 100 steps let it update the label1 UILabel with the counter’s value.
    for (int i=0; i<10000000; i++) {
        if (i % 100 == 0) {
            // Notice that we use the performSelectorOnMainThread method here instead of setting the label’s value directly.
            // We do that to let the main thread to take care of showing the text on the label
            // and to avoid display problems due to the loop speed.
            [label1 performSelectorOnMainThread:@selector(setText:)
                                        withObject:[NSString stringWithFormat:@»%d», i]
                                        waitUntilDone:YES];
        }
    }
 
    // When the loop gets finished then just display a message.
    [label1 performSelectorOnMainThread:@selector(setText:) withObject:@»Thread #1 has finished.»
}

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

Теперь давайте реализуем метод colorRotatorTask :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    -(void)colorRotatorTask{
    // We need a custom color to work with.
    UIColor *customColor;
 
    // Run a loop with 500 iterations.
    for (int i=0; i<500; i++) {
        // Create three float random numbers with values from 0.0 to 1.0.
        float redColorValue = (arc4random() % 100) * 1.0 / 100;
        float greenColorValue = (arc4random() % 100) * 1.0 / 100;
        float blueColorValue = (arc4random() % 100) * 1.0 / 100;
 
        // Create our custom color.
        customColor = [UIColor colorWithRed:redColorValue green:greenColorValue blue:blueColorValue alpha:1.0];
 
        // Change the label2 UILabel’s background color.
        [label2 performSelectorOnMainThread:@selector(setBackgroundColor:) withObject:customColor waitUntilDone:YES];
 
        // Set the r, g, b and iteration number values on label3.
        [label3 performSelectorOnMainThread:@selector(setText:)
                                    withObject:[NSString stringWithFormat:@»Red: %.2f\nGreen: %.2f\nBlue: %.2f\Iteration #: %d», redColorValue, greenColorValue, blueColorValue, i]
                                    waitUntilDone:YES];
 
        // Put the thread to sleep for a while to let us see the color rotation easily.
        [NSThread sleepForTimeInterval:0.4];
    }
 
    // Show a message when the loop is over.
    [label3 performSelectorOnMainThread:@selector(setText:) withObject:@»Thread #2 has finished.»
}

Вы можете видеть, что мы использовали performSelectorOnMainThread метод performSelectorOnMainThread . Следующим шагом является [NSThread sleepForTimeInterval:0.4]; команда, которая используется, чтобы вызвать небольшую задержку (0,4 секунды) при каждом выполнении цикла. Даже если нет необходимости использовать этот метод, предпочтительнее использовать его здесь, чтобы замедлить скорость изменения цвета фона label2 label2 (наш ротатор цвета). Дополнительно в каждом цикле мы создаем случайные значения для красного, зеленого и синего. Затем мы устанавливаем эти значения для создания собственного цвета и устанавливаем его в качестве цвета фона в label2 UILabel.

На этом этапе две задачи, которые будут выполнены одновременно с основным потоком, готовы. Давайте реализуем три (очень простых) метода IBAction, и тогда мы готовы к работе. Как я уже упоминал, три кнопки UIB изменят цвет фона представления с конечной целью показать, как основной поток может работать вместе с двумя другими задачами. Вот они:

01
02
03
04
05
06
07
08
09
10
11
— (IBAction)applyBackgroundColor1 {
    [self.view setBackgroundColor:[UIColor colorWithRed:255.0/255.0 green:204.0/255.0 blue:102.0/255.0 alpha:1.0]];
}
 
— (IBAction)applyBackgroundColor2 {
    [self.view setBackgroundColor:[UIColor colorWithRed:204.0/255.0 green:255.0/255.0 blue:102.0/255.0 alpha:1.0]];
}
 
— (IBAction)applyBackgroundColor3 {
    [self.view setBackgroundColor:[UIColor whiteColor]];
}

Это оно! Теперь вы можете запустить приложение и посмотреть, как три разные задачи могут выполняться одновременно. Помните, что когда выполнение объектов NSOperation заканчивается, оно автоматически покидает очередь.


Многие из вас, возможно, уже обнаружили, что для реального кода для запуска многозадачного приложения требуется всего несколько строк кода. Кажется, что самая большая рабочая нагрузка — реализация необходимых методов, которые работают с каждой задачей. Тем не менее, этот метод является простым способом разработки многопоточных приложений в iOS.