Вандад Нахавандипур - iOS. Приемы программирования
Main Thread = <NSThread: 0x68 10260>{name = (null), num = 1}
Current Thread = <NSThread: 0x68 10260>{name = (null), num = 1}
…
Count = 993
Count = 994
Count = 995
Count = 996
Count = 997
Count = 998
Count = 999
Main thread is here
См. также
Раздел 7.12.
7.12. Асинхронное выполнение задач с помощью операций
Постановка задачи
Требуется параллельно выполнять операции.
Решение
Воспользуйтесь операционными очередями. В качестве альтернативы можно создавать подклассы от NSOperation и откреплять новый поток в методе main.
Обсуждение
Как говорилось в разделе 7.11, операции по умолчанию работают в том потоке, который вызывает метод start. Обычно операции запускаются в основном потоке, но в то же время мы ожидаем, что операции будут выполняться в собственных потоках и, соответственно, не будут тратить процессорное время, уделяемое главному потоку. Наилучшим решением для обеспечения такой работы будет применение операционных очередей. Однако если вы хотите управлять своими операциями вручную, чего бы я не рекомендовал, то можно было бы создавать подклассы от NSOperation и откреплять новый поток в главном методе. Подробнее об открепленных потоках поговорим в разделе 7.15.
Идем дальше. Попробуем воспользоваться операционной очередью и добавим к ней две простые инициирующие операции (подробнее об инициирующих операциях рассказано в разделе 7.0). Дополнительные примеры кода, описывающие инициирующие операции, имеются в разделе 7.11. Вот объявление (.hm-файл) делегата приложения, в котором используются операционная очередь и две инициирующие операции:
@interface AppDelegate ()
@property (nonatomic, strong) NSOperationQueue *operationQueue;
@property (nonatomic, strong) NSInvocationOperation *firstOperation;
@property (nonatomic, strong) NSInvocationOperation *secondOperation;
@end
@implementation AppDelegate
А вот и внутренняя часть файла реализации делегата приложения:
— (void) firstOperationEntry:(id)paramObject{
NSLog(@"%s", __FUNCTION__);
NSLog(@"Parameter Object = %@", paramObject);
NSLog(@"Main Thread = %@", [NSThread mainThread]);
NSLog(@"Current Thread = %@", [NSThread currentThread]);
}
— (void) secondOperationEntry:(id)paramObject{
NSLog(@"%s", __FUNCTION__);
NSLog(@"Parameter Object = %@", paramObject);
NSLog(@"Main Thread = %@", [NSThread mainThread]);
NSLog(@"Current Thread = %@", [NSThread currentThread]);
}
— (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
NSNumber *firstNumber = @111;
NSNumber *secondNumber = @222;
self.firstOperation =[[NSInvocationOperation alloc]
initWithTarget: self
selector:@selector(firstOperationEntry:)
object: firstNumber];
self.secondOperation = [[NSInvocationOperation alloc]
initWithTarget: self
selector:@selector(secondOperationEntry:)
object: secondNumber];
self.operationQueue = [[NSOperationQueue alloc] init];
/* Добавляем операции в очередь. */
[self.operationQueue addOperation: self.firstOperation];
[self.operationQueue addOperation: self.secondOperation];
NSLog(@"Main thread is here");
self.window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
Вот что происходит в реализации данного кода.
У нас есть два метода, firstOperationEntry: и secondOperationEntry:. Каждый из этих методов принимает в качестве параметра объект и выводит в окне консоли информацию об актуальном потоке, главном потоке и этом параметре. Это входные методы инициирующих операций, которые будут добавляться в операционную очередь.
Мы инициализируем два метода типа NSInvocationOperation и задаем целевой селектор в точке входа каждой операции (эти точки входа были описаны выше).
Затем инициализируем объект типа NSOperationQueue. (Он может создаваться и до того, как созданы методы входа.) Объект очереди будет обеспечивать параллелизм в работе операционных объектов. На данном этапе операционная очередь может немедленно начать (а может и не начать) запускать инициирующие операции, пользуясь их методами start. При этом очень важно помнить, что после добавления операции в операционную очередь от вас не требуется запускать операции вручную. Обеспечением запуска занимается операционная очередь.
Итак, еще раз запустим код примера и посмотрим, что же у нас на консоли:
[Running_Tasks_Asynchronously_with_OperationsAppDelegate firstOperationEntry: ]
Main thread is here
Parameter Object = 111
[Running_Tasks_Asynchronously_with_OperationsAppDelegate secondOperationEntry: ]
Main Thread = <NSThread: 0x68 10260>{name = (null), num = 1}
Parameter Object = 222
Current Thread = <NSThread: 0x6805c20>{name = (null), num = 3}
Main Thread = <NSThread: 0x68 10260>{name = (null), num = 1}
Current Thread = <NSThread: 0x6b2d1d0>{name = (null), num = 4}
Блестяще! Это доказывает, что инициирующие операции параллельно выполняются каждая в своем потоке и в то же время параллельно главному потоку, вообще не блокируя его. Теперь еще пару раз прогоним этот же код и посмотрим, какой вывод будет появляться в окне консоли. В таком случае вы можете получить совершенно иной результат, например:
Main thread is here
[Running_Tasks_Asynchronously_with_OperationsAppDelegate firstOperationEntry: ]
[Running_Tasks_Asynchronously_with_OperationsAppDelegate secondOperationEntry: ]
Parameter Object = 111
Main Thread = <NSThread: 0x68 10260>{name = (null), num = 1}
Current Thread = <NSThread: 0x68247c0>{name = (null), num = 3}
Parameter Object = 222
Main Thread = <NSThread: 0x68 10260>{name = (null), num = 1}
Current Thread = <NSThread: 0x6819b00>{name = (null), num = 4}
Очевидно, что главный поток не блокируется и что обе инициирующие операции работают параллельно с главным потоком. Это доказывает, что в операционной очереди сохраняется параллелизм даже тогда, когда в нее добавляются две непараллельные операции. Операционная очередь управляет потоками, необходимыми для осуществления операций.
Если бы вы создавали подклассы от NSOperation и добавляли в операционную очередь экземпляры нового класса, то ситуация складывалась бы несколько иначе. Не забывайте о некоторых моментах.
Если обычные операции, являющиеся подклассами от NSOperation, добавлять в операционную очередь, то они будут работать асинхронно. Поэтому необходимо переопределить метод экземпляра isConcurrent, относящийся к классу NSOperation, и возвратить значение YES.
Необходимо подготовить операцию к отмене, периодически проверяя значение метода isCancelled при осуществлении основной задачи операции, а также в методе start еще до запуска самой операции. В таком случае метод start вызывается операционной очередью после того, как операция будет добавлена в очередь. В этом методе проверяется, не отменена ли операция. Это делается с помощью метода isCancelled. Если операция отменена, просто верните такое значение от метода start. В противном случае вызовите метод main из метода start.
Переопределите метод main собственной реализацией основной задачи, которую должна выполнять операция. Обязательно выделите и инициализируйте в этом методе ваш собственный автоматически высвобождаемый пул и высвободите его непосредственно перед актом возврата.
Переопределите методы isFinished и isExecuting операции и верните соответствующие логические (BOOL) значения, показывающие, завершена операция или продолжается в настоящий момент.
Вот объявление операции (.h-файл):
#import <Foundation/Foundation.h>
@interface SimpleOperation: NSOperation
/* Выделенный инициализатор */
— (id) initWithObject:(NSObject *)paramObject;
@end
Реализация операции такова:
#import «SimpleOperation.h»
@implementation SimpleOperation
— (instancetype) init {
return([self initWithObject:@123]);
}
— (instancetype) initWithObject:(NSObject *)paramObject{
self = [super init];
if (self!= nil){
/* Сохраните эти значения для главного метода. */
_givenObject = paramObject;
}
return(self);
}
— (void) main {
@try {
@autoreleasepool {
/* Сохраняем здесь локальную переменную, которая должна быть
установлена в YES всякий раз, когда мы завершаем
выполнение задачи. */
BOOL taskIsFinished = NO;
/* Создаем здесь цикл while, существующий лишь в том случае,
когда переменная taskIsFinished устанавливается в YES