Шрифт:
Интервал:
Закладка:
self.simpleOperation = [NSBlockOperation blockOperationWithBlock: ^{
NSLog(@"Main Thread = %@", [NSThread mainThread]);
NSLog(@"Current Thread = %@", [NSThread currentThread]);
NSUInteger counter = 0;
for (counter = 0;
counter < 1000;
counter++){
NSLog(@"Count = %lu", (unsigned long)counter);
}
}];
/* Запуск операции. */
[self.simpleOperation start];
/* Выводим что-нибудь на консоль, просто чтобы проверить,
должны мы дожидаться, пока выполнится блок кода, или нет.*/
NSLog(@"Main thread is here");
self.window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
Если запустить приложение, мы увидим, что на экране выводятся значения от 0 до 999, а за ними следует сообщение Main thread is here (Это главный поток):
Main Thread = <NSThread: 0x68 10280>{name = (null), num = 1}
Current Thread = <NSThread: 0x68 10280>{name = (null), num = 1}
…
Count = 991
Count = 992
Count = 993
Count = 994
Count = 995
Count = 996
Count = 997
Count = 998
Count = 999
Main thread is here
Итак, убеждаемся, что, поскольку блоковая операция была запущена в методе application: didFinishLaunchingWithOptions:, который сам работает в главном потоке, код внутри блока также выполняется в главном потоке. Основные сведения, которые мы получаем из этих регистрационных записей (логов), сводятся к следующему: операция блокировала главный поток, и потребовалось вернуться к выполнению кода основного потока после того, как была завершена работа блоковой операции. Это образец очень непрофессионального программирования. На самом деле программисты, работающие с iOS, должны идти на любые уловки и пользоваться любыми известными им приемами, чтобы обеспечивать отклик основного потока в любой момент и чтобы этот поток мог заниматься своим основным делом — обработкой пользовательского ввода. Вот что об этом пишет Apple.
«Необходимо внимательно отслеживать, какие задачи вы решаете в главном потоке вашего приложения. Именно в главном потоке ваша программа отрабатывает события касания и другой пользовательский ввод. Чтобы гарантировать, что приложение в любой момент будет откликаться на действия пользователя, никогда не следует загружать главный поток выполнением долговременных задач либо выполнением задач с потенциально неопределенным концом. Таковы, в частности, задачи, связанные с доступом к сети. Напротив, подобные задачи следует решать в фоновых потоках. Оптимальный способ решения таких задач — заключение их в объект операции и добавление этого объекта к операционной очереди. Но вы можете и сами создавать потоки вручную».
Подробнее эта тема рассматривается в документе Performance Tuning («Повышение производительности») в справочной библиотеке iOS. Документ расположен по адресу https://developer.apple.com/library/ios/documentation/iphone/conceptual/iphoneosprogrammingguide/PerformanceTuning/PerformanceTuning.html#//apple_ref/doc/uid/TP400 07072-CH8-SW1.
Кроме вызовов и блоковых операций, вы можете также создавать подклассы от NSOperation и выполнять вашу задачу в этом классе. Перед тем как переходить к работе, обратите внимание на некоторые нюансы, связанные с созданием подклассов от NSOperation.
Если вы не планируете пользоваться операционной очередью, то необходимо открепить от вашего потока новый поток. Это делается в методе start, относящемся к операции. Если вы не хотите пользоваться операционной очередью, а также не собираетесь выполнять данную операцию асинхронно с другими операциями, запускаемыми вручную, то можно просто вызвать метод main операции в методе start.
В реализации операции необходимо переопределить два важных метода экземпляра NSOperation — методы isExecuting и isFinished. Их может вызывать любой другой объект. В этих методах необходимо возвращать потоковобезопасное значение, которым можно будет управлять прямо из операции. Как только операция начинается, она должна посредством механизма «уведомления наблюдателей об изменениях в свойствах наблюдаемого объекта» (KVO) сообщать всем слушателям о том, что вы изменяете возвращаемые значения для двух этих методов. В примере с кодом мы рассмотрим, как это происходит на практике.
В методе main, относящемся к операции, необходимо создать собственный автоматически высвобождаемый пул на случай, если когда-нибудь в будущем операция будет добавлена в операционную очередь. Необходимо убедиться в том, что операции можно задействовать обоими способами — как при запуске вручную, так и при запуске в рамках операционной очереди.
У вас должен быть метод-инициализатор для ваших операций. Это обязательно должен быть специальный метод, выделенный под конкретную операцию. Все остальные методы-инициализаторы, в том числе применяемый по умолчанию метод init, должны вызывать вышеупомянутый специальный инициализатор, который содержит наибольшее количество параметров. Другие методы-инициализаторы должны гарантировать, что они передают подходящие параметры методу-инициализатору (если вообще передают).
Вот объявление объекта операции (.h-файл):
#import <Foundation/Foundation.h>
@interface CountingOperation: NSOperation
/* Выделенный инициализатор */
— (id) initWithStartingCount:(NSUInteger)paramStartingCount
endingCount:(NSUInteger)paramEndingCount;
@end
Реализация операции (записываемая в. m-файле) несколько длинновата, но, надеюсь, вполне понятна:
#import «CountingOperation.h»
@implementation CountingOperation
@property (nonatomic, unsafe_unretained) NSUInteger startingCount;
@property (nonatomic, unsafe_unretained) NSUInteger endingCount;
@property (nonatomic, unsafe_unretained, getter=isFinished) BOOL finished;
@property (nonatomic, unsafe_unretained, getter=isExecuting) BOOL executing;
@end
@implementation CountingOperation
— (instancetype) init {
return([self initWithStartingCount:0
endingCount:1000]);
}
— (instancetype) initWithStartingCount:(NSUInteger)paramStartingCount
endingCount:(NSUInteger)paramEndingCount{
self = [super init];
if (self!= nil){
/* Сохраните эти значения для главного метода. */
startingCount = paramStartingCount;
endingCount = paramEndingCount;
}
return(self);
}
— (void) main {
@try {
/* Это автоматически высвобождаемый пул. */
@autoreleasepool {
/* Сохраняем здесь локальную переменную, которая
должна быть установлена в YES всякий раз, когда
мы завершаем выполнение задачи. */
BOOL taskIsFinished = NO;
/* Создаем здесь цикл while, существующий лишь в том случае,
когда переменная taskIsFinished устанавливается в YES
или операция отменяется. */
while (taskIsFinished == NO &&
[self isCancelled] == NO){
/* Здесь выполняется задача. */
NSLog(@"Main Thread = %@", [NSThread mainThread]);
NSLog(@"Current Thread = %@", [NSThread currentThread]);
NSUInteger counter = startingCount;
for (counter = startingCount;
counter < endingCount;
counter++){
NSLog(@"Count = %lu", (unsigned long)counter);
}
/* Очень важно. Здесь мы можем выйти из цикла, по-прежнему
соблюдая правила, по которым отменяются операции. */
taskIsFinished = YES;
}
/* Соответствие KVO. Генерируем требуемые уведомления KVO. */
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
finished = YES;
executing = NO;
[self didChangeValueForKey:@"isFinished"];
[self didChangeValueForKey:@"isExecuting"];
}
}
@catch (NSException * e) {
NSLog(@"Exception %@", e);
}
}
@end
Операцию можно начать так:
@interface AppDelegate ()
@property (nonatomic, strong) CountingOperation *simpleOperation;
@end
@implementation AppDelegate
— (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
self.simpleOperation = [[CountingOperation alloc] initWithStartingCount:0
endingCount:1000];
[self.simpleOperation start];
NSLog(@"Main thread is here");
self.window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
@end
Запустив данный код, мы увидим в окне консоли следующие результаты, точно как при применении блоковой операции:
- QT 4: программирование GUI на С++ - Жасмин Бланшет - Программирование
- Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен - Программирование
- C# для профессионалов. Том II - Симон Робинсон - Программирование
- Стандарты программирования на С++. 101 правило и рекомендация - Герб Саттер - Программирование
- Программирование игр и головоломок - Жак Арсак - Программирование
- Генерация высококачественного кода для программ, написанных на СИ - Филипп Хислей - Программирование
- Программирование на Python с нуля - Максим Кононенко - Программирование
- C# 4.0: полное руководство - Герберт Шилдт - Программирование
- Microsoft Visual C++ и MFC. Программирование для Windows 95 и Windows NT. Часть 2 - Александр Фролов - Программирование
- От «Энигмы» до ChatGPT - Рустам Агамалиев - Программирование / Экономика