() translation by (you can also view the original English article)
Сеть это сложно. Существуют различные движущиеся части, и для его работы необходимо учитывать множество факторов. К счастью, с течением времени появилось несколько библиотек с открытым исходным кодом, облегчающих работу в сети. AFNetworking, созданный и поддерживаемый людьми Gowalla, является одной из таких библиотек. Этот учебник познакомит вас с платформой AFNetworking, а также покажет, как запрашивать API iTunes Store!
В этом руководстве я познакомлю вас с AFNetworking и покажу, что может предложить эта библиотека. Потратив несколько минут с этой библиотекой, вы заметите, что она была разработана с учетом удобства использования. Это не только ускорит вашу разработку, но также позаботится о многих кропотливых сетевых задачах. Мы создадим простое приложение, которое запрашивает в iTunes Store фильмы, соответствующие поисковому запросу «Гарри». Результаты нашего запроса будут отображены в виде таблицы.
Краткое описание проекта
Приложение, которое мы собираемся создать, запрашивает API поиска iTunes Store. В частности, мы ищем в iTunes Store фильмы, соответствующие поисковому запросу «Гарри». Если наш запрос выполнен успешно, мы обрабатываем результаты и отображаем их в виде таблицы. Каждая строка представляет фильм с названием, режиссером и миниатюрой, показывающей обложку фильма. Готовы? Давайте начнем.
Настройка проекта
Прежде чем мы запачкаем руки с помощью AFNetworking, нам необходимо создать базовую основу. Это означает настройку нашего проекта, создание табличного представления и добавление представления индикатора активности. Мы покажем индикатор активности, когда наш запрос обрабатывается iTunes Store. Это даст пользователю тот ценный дополнительный бит обратной связи, который часто упускается из виду.
Создайте новый проект в XCode, выбирая шаблон Приложения единого представления из списка шаблонов. Назовите свое приложение NetworkingIsFun, введите идентификатор компании, установите «iPhone» для семейства устройств и снимите флажок «Использовать раскадровки». Вы можете оставить остальные нетронутыми, но убедитесь, что установлен флажок Использовать автоматический подсчет ссылок. Сообщите Xcode, где вы хотите сохранить свой проект, и нажмите «Сохранить».






Добавление таблицы и активность индикатор просмотров
Несмотря на то, что Interface Builder великолепен, я часто создаю свои интерфейсы программно, и это то, что мы будем делать и в этом уроке. Это позволит нам просто сосредоточиться на коде, не отвлекаясь на Interface Builder. Откройте ViewController.h и создайте три переменных экземпляра (ivars), а также свойства для этих ivars. Поскольку мы собираемся работать с табличным представлением, не забудьте привести свой контроллер представления в соответствие с протоколами UITableViewDataSource и UITableViewDelegate. Заголовочный файл вашего контроллера представления должен выглядеть примерно так, как показано ниже:
1 |
|
2 |
#import <UIKit/UIKit.h>
|
3 |
|
4 |
@interface ViewController : UIViewController <UITableViewDataSource, UITableViewDelegate> { |
5 |
UITableView *_tableView; |
6 |
UIActivityIndicatorView *_activityIndicatorView; |
7 |
NSArray *_movies; |
8 |
}
|
9 |
|
10 |
@property (nonatomic, retain) UITableView *tableView; |
11 |
@property (nonatomic, retain) UIActivityIndicatorView *activityIndicatorView; |
12 |
@property (nonatomic, retain) NSArray *movies; |
13 |
|
14 |
@end
|
Если вас смущает использование подчеркивания, я рекомендую вам прочитать об этом здесь. Не стесняйтесь опускать подчеркивания, если вы думаете, что это выглядит некрасиво или заставляет вас чувствовать себя некомфортно. Помимо подчеркивания, не должно быть никаких сюрпризов. Мы объявляем наши UITableView и UIActivityIndicatorView, а также NSArray, который мы будем использовать для хранения результатов, которые мы получаем из нашего поискового запроса. Готовы? Давайте перейдем к файлу реализации нашего контроллера представления.
Поскольку мы объявили три свойства в нашем заголовочном файле, нам нужно синтезировать их методы доступа в ViewController.m. Опять же, если подчеркивание сбивает вас с толку, вы можете пропустить их.
1 |
|
2 |
@synthesize tableView = _tableView, activityIndicatorView = _activityIndicatorView, movies = _movies; |
В нашем методе viewDidLoad мы настраиваем представления таблицы и индикатора активности. Код ниже должен быть самоочевидным по большей части. Если вы никогда не настраивали табличное представление без использования Interface Builder, вы можете увидеть несколько незнакомых вам строк. Вместо того, чтобы связывать табличное представление в Интерфейсном Разработчике, мы заботимся об этом в методе viewDidLoad. После вызова метода viewDidLoad суперкласса мы инициализируем наше табличное представление с помощью рамки и стиля и устанавливаем наш контроллер представления в качестве источника данных и делегата нашего табличного представления. Когда наше приложение запускается, мы скрываем наше табличное представление, так как нам нечего показывать, если наш запрос не дал результатов. Перед добавлением табличного представления в качестве подпредставления к представлению нашего контроллера представления, мы устанавливаем его маску авторазмера. Маска авторазмера определяет, как следует изменять размер табличного представления, если родительское представление -представление контроллера представления, к которому мы добавляем представление таблицы - изменения в размере. Это происходит, когда устройство поворачивается, например. Смущенный? Не беспокойся об этом. Это не важно для этого приложения.
1 |
|
2 |
- (void)viewDidLoad { |
3 |
[super viewDidLoad]; |
4 |
|
5 |
// Setting Up Table View
|
6 |
self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0.0, 0.0, self.view.bounds.size.width, self.view.bounds.size.height) style:UITableViewStylePlain]; |
7 |
self.tableView.dataSource = self; |
8 |
self.tableView.delegate = self; |
9 |
self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; |
10 |
self.tableView.hidden = YES; |
11 |
[self.view addSubview:self.tableView]; |
12 |
|
13 |
// Setting Up Activity Indicator View
|
14 |
self.activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; |
15 |
self.activityIndicatorView.hidesWhenStopped = YES; |
16 |
self.activityIndicatorView.center = self.view.center; |
17 |
[self.view addSubview:self.activityIndicatorView]; |
18 |
[self.activityIndicatorView startAnimating]; |
19 |
|
20 |
// Initializing Data Source
|
21 |
self.movies = [[NSArray alloc] init]; |
22 |
}
|
Настроить вид индикатора активности так же просто. Мы инициализируем представление индикатора активности с предопределенным стилем, устанавливаем его свойство hidesWhenStopped в значение YES и размещаем его в центре родительского представления. После добавления его в представление контроллера вида мы начинаем анимировать индикатор активности. Индикатор активности будет отображаться автоматически, поскольку мы установили для его свойства hidesWhenStopped значение YES.
В конце нашего метода viewDidLoad мы инициализируем массив movies. Мы будем использовать его позже для хранения результатов нашего поискового запроса.
Протоколы табличного представления
В этом руководстве мы реализуем только два метода протокола источника данных табличного представления. Оба эти метода являются обязательными. Это минимальная реализация, необходимая для запуска и запуска нашего табличного представления. Несмотря на то, что мы установили наш контроллер представления в качестве делегата табличного представления, мы не будем использовать ни один из методов делегата в нашем приложении. Если вы уже использовали табличное представление, вы не найдете сюрпризов в реализациях методов, показанных ниже.
1 |
|
2 |
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { |
3 |
if (self.movies && self.movies.count) { |
4 |
return self.movies.count; |
5 |
} else { |
6 |
return 0; |
7 |
}
|
8 |
}
|
9 |
|
10 |
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { |
11 |
static NSString *cellID = @"Cell Identifier"; |
12 |
|
13 |
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID]; |
14 |
|
15 |
if (!cell) { |
16 |
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellID]; |
17 |
}
|
18 |
|
19 |
return cell; |
20 |
|
21 |
}
|
В tableView: numberOfRowsInSection: мы должны вернуть количество строк в каждом разделе табличного представления. В нашем примере табличное представление содержит только один раздел (по умолчанию), что делает все немного проще. Сначала мы проверяем, не является ли наша переменная movies значение nil, и проверяем, что она содержит элементы. Если оба эти требования выполнены, мы возвращаем количество элементов в массиве movies, если нет, мы возвращаем ноль.
Наш tableView: cellForRowAtIndexPath: метод также является базовым. Мы начинаем с того, что спрашиваем наше табличное представление, есть ли ячейка, которую мы можем использовать повторно. Если это не так, мы создаем новую ячейку со стилем и повторно используем идентификатор. Мы заканчиваем нашу реализацию, возвращая нашу ячейку. Это будет делать сейчас. Теперь вы можете создавать и запускать ваше приложение. Если вы правильно выполнили шаги, вы должны увидеть, что индикатор активности вращается как сумасшедший, а представление таблицы должно быть скрыто.
Добавление AFNetworking в Mix
Добавить AFNetworking в ваш проект очень просто. Начните с загрузки библиотеки с GitHub и распакуйте архив. Архив содержит папку с именем AFNetworking, в которой содержатся исходные файлы, которые мы должны включить в наш проект. Перетащите всю эту папку в свой проект XCode и убедитесь, что вы отметили пункт Копировать элементы в папку целевой группы (если необходимо) и также добавили исходные файлы в свою цель.



После добавления библиотеки AFNetworking в ваш проект создайте и запустите приложение и наблюдайте, как все разваливается. Что случилось с нашим проектом? Почему мы получаем все эти предупреждения и ошибки? Когда мы настроили наш проект XCode, мы включили автоматический подсчет ссылок (ARC). На момент написания, библиотека AFNetworking не использовала ARC. Не волнуйтесь, однако, мы все еще можем использовать эту аккуратную библиотеку без особых усилий. Все, что нам нужно сделать, это сообщить компилятору, что все исходные файлы библиотеки AFNetworking не используют ARC. Вот и все.
Как нам это сделать? Выберите ваш проект в Навигаторе проектов и выберите цель. Перейдите на вкладку «Фазы сборки» в верхней панели навигации и откройте панель «Компилировать источники». В этой таблице показаны все исходные файлы, которые компилятор будет компилировать во время компиляции. Левый столбец показывает имена файлов, а правый столбец показывает флаги, о которых должен знать компилятор.



Вы можете видеть эти флаги как инструкции или сообщения для компилятора. Все, что вам нужно сделать, это добавить флаг компилятора в каждый исходный файл библиотеки AFNetworking. Для этого выберите исходный файл из списка и дважды щелкните ячейку в правом столбце. Появится небольшое окно, в котором вы можете добавить один или несколько флагов компилятора. В нашем случае просто введите -fno-objc-arc и нажмите Готово. Этот флаг сообщает компилятору, что исходный файл не использует ARC. Убедитесь, что вы добавили этот флаг ко всем десяти исходным файлам библиотеки AFNetworking.



Запрос к API для поиска iTunes Store
AFNetworking - это библиотека, которая может многое для вас сделать, но сегодня мы собираемся использовать только две изящные функции. Прежде чем мы сможем начать использовать классы AFNetworking, нам нужно добавить следующий оператор импорта чуть ниже первого оператора импорта в файле реализации вашего контроллера представления.
1 |
|
2 |
#import "AFNetworking.h"
|
Это утверждение импорта даст нам доступ ко всем классам AFNetworking. Вернитесь к методу viewDidLoad нашего контроллера представления и добавьте следующий фрагмент сразу после инициализации массива movie.
1 |
|
2 |
NSURL *url = [[NSURL alloc] initWithString:@"http://itunes.apple.com/search?term=harry&country=us&entity=movie"]; |
3 |
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url]; |
4 |
|
5 |
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) { |
6 |
NSLog(@"%@", JSON); |
7 |
|
8 |
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) { |
9 |
NSLog(@"Request Failed with Error: %@, %@", error, error.userInfo); |
10 |
}];
|
11 |
|
12 |
[operation start]; |
Позвольте мне объяснить, что происходит. В первой строке мы создаем NSURL для нашего запроса. Строка, которую мы используем при инициализации, соответствует формату, который ожидает API поиска iTunes Store. Синтаксис API поиска довольно прост: термин, который мы ищем, это «Гарри», мы ограничиваем наш поиск в американском iTunes Store и ищем только фильмы. Легко, правда?
Затем мы инициализируем NSURLRequest и передаем NSURL, который мы только что создали. Затем начинается AFNetworking. AFNetworking содержит несколько очень специализированных классов, которые делают нашу работу очень легкой. Здесь мы используем AFJSONRequestOperation. Это класс, предназначенный для извлечения и анализа данных JSON в фоновом режиме. Как следует из названия, этот класс является подклассом NSOperation или, если быть более точным, один из суперклассов этого класса наследуется от NSOperation. Класс позволяет вам получить запрошенные данные, а также анализирует ответ JSON. Это означает, что нам не нужно иметь дело с необработанным JSON. Возвращаемые данные готовы к использованию в вашем приложении. AFNetworking использует встроенный анализатор JSON в iOS 5 и использует свой собственный анализатор JSON для более старых версий iOS.
Использовать AFJSONRequestOperation легко, поскольку он имеет только один метод класса. Этот метод класса принимает три аргумента: (1) NSURLRequest, (2) блок успеха, выполняемый при успешном выполнении запроса, и (3) блок отказа, исполняемый при сбое запроса. Если блоки являются новыми для вас, или вам неудобно их использовать, то я рекомендую прочитать учебник Коллина Руффенаха о блоках и перечислении на Mobiletuts +. Блок успеха принимает три аргумента: (1) наш NSURLRequest, (2) NSHTTPURLResponse нашего запроса и (3) проанализированный JSON-объект. Блок отказов практически идентичен. Единственное отличие состоит в том, что он принимает дополнительный аргумент, NSError, который содержит больше информации о том, что пошло не так в случае сбоя нашего запроса.
В целях тестирования мы регистрируем проанализированный JSON-объект, чтобы увидеть, что API-интерфейс поиска в iTunes Store возвращает нам. Кроме того, мы также регистрируем ошибку в блоке сбоя в случае сбоя нашего запроса. Перед тем как снова собрать и запустить наше приложение, нам нужно запустить операцию, вызвав start нашего объекта операции. Создайте и запустите ваше приложение и посмотрите на вывод в консоли.
Если все прошло хорошо, вы увидите результаты нашего запроса, записанные в консоли. Анализируемый объект JSON представляет собой словарь с двумя ключами: (1) resultCount, который содержит количество возвращаемых результатов и (2) фактические результаты в виде массива словарей. Мы не только зарегистрировали ответ в консоли, чтобы увидеть, был ли наш запрос успешным, мы теперь можем видеть ключи для каждого элемента в массиве результатов. Нам понадобятся эти ключи для отображения некоторой информации в нашем табличном представлении.
Заполнение табличного представления
Теперь мы готовы показать результаты в нашем табличном представлении. Замените оператор журнала в блоке успеха на фрагмент, показанный ниже. Мы начнем с присвоения массива результатов объекта ответа массиву movies. Осталось только скрыть индикатор активности, остановив его, отобразив табличное представление и перезагрузив табличное представление с новыми данными, сохраненными в массиве movies.
1 |
|
2 |
self.movies = [JSON objectForKey:@"results"]; |
3 |
[self.activityIndicatorView stopAnimating]; |
4 |
[self.tableView setHidden:NO]; |
5 |
[self.tableView reloadData]; |
Затем мы изменяем tableView: cellForRowAtIndexPath: метод. Настройте свою реализацию так, чтобы она соответствовала приведенной ниже После получения ссылки на ячейку, мы запрашиваем массив фильмов для правильного элемента и обновляем метки ячейки заголовком и режиссером фильма. Создайте и запустите ваше приложение.
1 |
|
2 |
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { |
3 |
static NSString *cellID = @"Cell Identifier"; |
4 |
|
5 |
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID]; |
6 |
|
7 |
if (!cell) { |
8 |
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellID]; |
9 |
}
|
10 |
|
11 |
NSDictionary *movie = [self.movies objectAtIndex:indexPath.row]; |
12 |
cell.textLabel.text = [movie objectForKey:@"trackName"]; |
13 |
cell.detailTextLabel.text = [movie objectForKey:@"artistName"]; |
14 |
|
15 |
return cell; |
16 |
}
|
Возможно, вы заметили, что нет миниатюр, которые можно увидеть. Давайте исправим это, добавив три дополнительные строки в наш tableView: cellForRowAtIndexPath: метод. Создайте и запустите ваше приложение.
1 |
|
2 |
NSURL *url = [[NSURL alloc] initWithString:[movie objectForKey:@"artworkUrl100"]]; |
3 |
NSData *data = [NSData dataWithContentsOfURL:url]; |
4 |
cell.imageView.image = [[UIImage alloc] initWithData:data]; |
Вы заметили, что наше табличное представление не прокручивается плавно. Это почему? Как я уже упоминал в предыдущем руководстве, вы всегда должны следить за тем, чтобы основной поток вашего приложения оставался отзывчивым. В текущей реализации нашего метода tableView: cellForRowAtIndexPath: мы загружаем миниатюры в основном потоке. Это означает, что пользовательский интерфейс не может быть обновлен, пока не будет завершен запрос эскиза. Наши миниатюры крошечные, поэтому запросы не занимают слишком много времени, но представьте, что вы используете тот же подход для больших активов. Пользовательский опыт был бы ужасен. Даже для нашего простого приложения пользовательский опыт неприемлем. Однако мы можем исправить это с помощью очень полезной функции библиотеки AFNetworking.
AF Networking на помощь
Создатели AFNetworking также увидели необходимость загрузки ресурсов в фоновом режиме. Поэтому они создали категорию для UIImageView. Эта категория позволяет загружать изображения в фоновом режиме только с двумя строками кода. Эта категория - настоящий спасатель жизни. Посмотрите на фрагмент ниже.
1 |
|
2 |
NSURL *url = [[NSURL alloc] initWithString:[movie objectForKey:@"artworkUrl100"]]; |
3 |
[cell.imageView setImageWithURL:url placeholderImage:[UIImage imageNamed:@"placeholder"]]; |
Первая строка кода остается прежней. Во второй строке мы сообщаем представлению изображения, где находится миниатюра, передавая NSURL, и передаем изображение-заполнитель, которое отображается, пока наш запрос не вернул ответ. Как это круто? Все, что нам нужно сделать, это добавить изображение-заполнитель в наш проект. Это может быть любое изображение, которое вы хотите, но вы можете найти изображение, которое я использовал в качестве заполнителя, в файле загрузки, прилагаемом к этому учебному пособию. После того, как вы добавили изображение-заполнитель, соберите и запустите ваше приложение и убедитесь, насколько плавно прокручивается табличное представление!
Заключение
Обратите внимание, что наше приложение очень простое в своем исполнении, поскольку оно не выполняет никакого кэширования, и мы можем искать в iTunes Store только термин «гарри» в разделе фильмов. Однако, приложив совсем немного усилий, вы можете сделать аккуратное приложение для более быстрого поиска в iTunes Store.
Я надеюсь, что этот урок убедил вас, что библиотека AFNetworking - отличный инструмент для вашего арсенала. Он может сделать намного больше, чем то, что я показал вам в этом посте, но главная цель этого урока - научить вас работать с AFNetworking и быть готовым использовать его в реальном сценарии.