ПРОГРАММИРОВАНИЕ ПОД IPHONE, IPAD OBJECTIVE-C Часть 1

Потоки

Существует много задач, на выполнение которых требуются весьма значительные процессорные ресурсы, соответственно все это может занять определенное время, загрузив тем самым ваше приложение. 
Для таких случаев в программировании ввели понятие многопоточности. Создается отдельный поток и необходимые задачи выполняются в нем, не загружая основной поток. Таким образом ваше приложение не виснет, и пользователи не томятся в ожидании – когда же приложение подаст признаки жизни, обработа очередную порцию данных. По умолчанию, любое приложение в iOS имеет один поток (основной/родительский). По своей сути, родительский поток немного отличается от создаваемых, дочерних потоков. Так он выполняет функцию приложения main, реагирует на  события со стороны пользователя и обновление интерфейса.
В языке программирования Objective-C предусмотренно несколько вариантов многопоточности. Разберем все по порядку.
Pthreads – многие книги и ресурсы современных авторов не указывают об этом методе, так как он безнадежно устарел и сложен в реализации. Я тоже не буду углубляться, но упомянуть о нем все же стоит.
— (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg. Наверное самый просто и самый распространенный метод обработки информации вне родительского потока. При реализации необходимо указать лишь селектор и аргументы.
GCD (Grand Central Dispatch) – последнее слово в многопоточном программирование. Возможность использования появилась в Xcode 4.0. и представляет собой механизм распаралеливания задач. Говоря простым и понятным языком – суть в том, что все хлопоты по созданию и управлению потоками на себя берет GCD. Если опуститься на более низкий уровень, то можно сказать что задачи GCD более легковесны и менее требовательны к памяти, чем создание потоков напрямую.
NSOperationQueue – относительно новая технология. Суть заключается в создании NSOperation, которая и будет обрабатывать нужную функцию, а после помещать в очередь NSOperationQueue, которая в последствии выполнит все позиции в очереди согласно заданным приоритетам.
Последний способ разберем подробнее.
Чаще всего многопоточность используется для чтения данных. Причем, не важно откуда эти данные читаются с удаленного сервера или Flash-памяти. На последнее следует обратить особое внимание при чтении больших файлов (изображений высокого разрешения) не с оперативной памяти устройства. Особенно, если эти изображения вставляются в ячейки таблицы. В таком случае, при скроле этой самой таблицы Вы можете заметить ее подтормаживание. Это происходит потому, что файл читается в основном потоке, блокируя интерфейс до завершения чтения. Но в этом уроке мы не будем решать подобные проблемы хотя бы потому, что на сервер нужно загружать большие картинки. Более простой задачей является загрузка данных в фоновом потоке с сервера. Создадим новый проект на основе шаблона View Based Application и назовем его Threads.
Для начала перейдем в интерфейс класса (ViewController.h) и приведем его вот к такому виду:
<code data-result="[object Object]">@interface ViewController : UIViewController

@property (nonatomic, retain) IBOutlet UIImageView *imageView;

- (IBAction)downloadImg;

@end</code>
Ничего нового в этом коде Вы не увидели. Он очень похож на пример Закачка данных. А самое интересно начинается в реализации нашего контроллера представлений (ViewController.m), нам нужно добавить в него два метода. Первый — это реализация объявленного downloadImg:
<code data-result="[object Object]">- (IBAction)downloadImg {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    NSURL *url = [NSURL URLWithString:@"http://mycrealife.ru/wp-content/uploads/2014/01/logo.png"];
    NSOperation *loadImgOp = [[NSInvocationOperation alloc]
                              initWithTarget:self
                              selector:@selector(loadImageInBackground:)
                              object:url];
    [queue addOperation:loadImgOp];
}</code>
В этом методе мы создаем объект очереди потокв, обект ссылки, по которой будем загружать данные и непосредственно саму операцию. При создании операции указываем ссылку на объект у которого будет вызываться метод, сам метод, который будет выполняться в выделенном потоке и передаем объект ссылки. В конце добавляем только что созданную операцию в очередь.
Пришло время написать сам метод загрузки данных:
<code data-result="[object Object]">-(void)loadImageInBackground:(NSURL*)url {
    NSData *imageData = [NSData dataWithContentsOfURL:url];
    UIImage *downloadedImage = [UIImage imageWithData:imageData];

    [self.imageView performSelectorOnMainThread:@selector(setImage:)
                                     withObject:downloadedImage
                                  waitUntilDone:YES];
}</code>
Первые две строки нам знакомы с вышеупомянутого урока Закачка данных. Там это называлось «простой загрузкой». А вот третья строка требует разъяснения. Как я уже говорил, за обработку интерфейса в приложении отвечает основной поток. Поэтому все, что касается работы с UI-класами и их наследниками следует выполнять только в основном потоке. Для этого мы использовали метод performSelectorOnMainThread. Это не единственный момент, который может показаться сложным. Следует обратить внимания для какого объекта мы вызываем этот метод и имя какого селектора передаем. Давайте представим, что метод загрузки данных выполнялся в основном потоке. В таком случае он выглядел бы примерно так:
<code data-result="[object Object]">-(void)loadImage:(NSURL*)url {
    NSData *imageData = [NSData dataWithContentsOfURL:url];
    UIImage *downloadedImage = [UIImage imageWithData:imageData];

    self.imageView.image = downloadedImage;
}</code>
Не нужно его добавлять в проект, он нам послужит лишь в качестве примера. От загрузки в отедльном потоке методloadImage отличается именем и способом установки картинки. То есть, мы знаем, что у объектов класса UIImageView есть свойствоimage, которому можно передать картинку. И если вспомнить урок Свойства и методы доступа — становится ясно, что под свойствомimage скрывается метод -(void)setImage:(UIImage*)image. Именно этим методом мы и воспользовались в методе performSelectorOnMainThread. То есть, в качестве селектора передали метод сеттера, а в качестве объекта саму картинку.
Перед первым запуском не забудьте добавить объекты интерфейса в xib-файл и связать их с кодом.
Ради эксперимента можете код метода loadImage поместить в downloadImg. Но для чистоты опыта лучше понизить скорость Интернет-соединения и попытаться нажать на кнопку загрузки несколько раз. Она будет не активна, потому что загрузка будет выполнятся в основном потоке, блокируя обработку всех событий интерфейса.

Comments are closed.