Статьи

Сеть с NSURLSession: часть 1

С точки зрения разработчика, одним из наиболее значительных изменений в iOS 7 и OS X Mavericks в этом отношении является введение NSURLSession . Несмотря на то, что NSURLSession может показаться пугающим на первый взгляд, важно, чтобы вы поняли, что это такое, как оно связано с NSURLConnection и каковы различия. В этой серии я NSURLSession вас с основами NSURLSession чтобы вы могли использовать эту новую технологию в своих собственных приложениях.


Первый вопрос, который вы можете себе задать, — это то, почему Apple посчитала необходимым ввести NSURLSession то время как мы полностью довольны NSURLConnection . Реальный вопрос в том , довольны ли вы NSURLConnection . Вы помните тот момент, когда вы NSURLConnection и бросали вещи в NSURLConnection ? Большинство разработчиков Cocoa были именно в этом месте, поэтому Apple решила вернуться к чертежной доске и создать более элегантное решение, более подходящее для современного Интернета.

Хотя NSURLSession и NSURLConnection имеют много общего в том, как они работают, на фундаментальном уровне они совершенно разные. Apple создала NSURLSession чтобы напоминать общие концепции NSURLConnection , но в ходе этой серии вы узнаете, что NSURLSession является современным, простым в использовании и созданным с учетом требований мобильных устройств.


Прежде чем обсуждать различия между NSURLSession и NSURLConnection , хорошей идеей будет сначала поближе познакомиться с NSURLSession . Несмотря на название, NSURLSession — это не просто еще один класс, который вы можете использовать в приложениях для iOS или OS X. NSURLSession — это, прежде всего, такая же технология, как NSURLConnection .

NSURLSession и NSURLConnection предоставляют API для взаимодействия с различными протоколами, такими как HTTP и HTTPS . Объект сеанса, экземпляр класса NSURLSession , управляет этим взаимодействием. Это легко конфигурируемый контейнер с элегантным API, который обеспечивает детальное управление. Он предлагает функции, которые отсутствуют в NSURLConnection . Более того, с NSURLSession вы можете выполнять задачи, которые просто невозможны с NSURLConnection , такие как реализация частного просмотра.

Основной единицей работы при работе с NSURLSession является задача, экземпляр NSURLSessionTask . Существует три типа задач: задачи с данными, задачи загрузки и задачи загрузки .

  • Чаще всего вы будете использовать задачи с данными, которые являются экземплярами NSURLSessionDataTask . Задачи данных используются для запроса данных с сервера, таких как данные JSON. Принципиальное отличие задач выгрузки и загрузки заключается в том, что они возвращают данные непосредственно в ваше приложение, а не проходят через файловую систему. Данные хранятся только в памяти.
  • Как следует из названия, задачи загрузки используются для загрузки данных в удаленный пункт назначения. NSURLSessionUploadTask является подклассом NSURLSessionDataTask и ведет себя аналогичным образом. Одно из ключевых отличий обычной задачи с данными заключается в том, что задачи загрузки можно использовать в сеансе, созданном в фоновой конфигурации сеанса.
  • Загрузите задачи, экземпляры NSURLSessionDownloadTask , наследуйте непосредственно от NSURLSessionTask . Наиболее существенное отличие от задач с данными заключается в том, что задача загрузки записывает свой ответ непосредственно во временный файл. Это сильно отличается от обычной задачи данных, которая хранит ответ в памяти. Можно отменить задачу загрузки и возобновить ее позднее.

Как вы можете себе представить, асинхронность является ключевым понятием в NSURLSession . API NSURLSession возвращает данные, вызывая обработчик завершения или через делегата сеанса. API NSURLSession был разработан с учетом гибкости, как вы заметите чуть позже в этом руководстве.

Как я упоминал ранее, NSURLSession — это и технология, и класс, с которым вы будете работать. NSURLSession API содержит несколько классов, но NSURLSession является ключевым компонентом, отправляющим запросы и получающим ответы. Однако конфигурация объекта сеанса обрабатывается экземпляром класса NSURLSessionConfiguration . Класс NSURLSessionTask и его три конкретных подкласса являются рабочими и всегда используются вместе с сеансом, поскольку именно сеанс создает объекты задачи.

И NSURLSession и NSURLConnection сильно зависят от шаблона делегирования. Протокол NSURLSessionDelegate объявляет несколько методов делегатов для обработки событий на уровне сеанса. Кроме того, NSURLSessionTask класс NSURLSessionTask и подклассы объявляют протокол делегата для обработки событий уровня задачи.

API NSURLSession на классах, с которыми вы уже знакомы, таких как NSURL , NSURLRequest и NSURLResponse .


Чем NSURLSession отличается от NSURLConnection ? Это важный вопрос, потому что NSURLConnection не осуждается Apple. Вы все еще можете использовать NSURLConnection в своих проектах. Почему вы должны использовать NSURLSession ?

Первое, что нужно понять, это то, что экземпляр NSURLSession является объектом, который управляет запросом и ответом. Это похоже на то, как работает NSURLConnection , но ключевое отличие состоит в том, что конфигурация запроса обрабатывается объектом сеанса, который является долгоживущим объектом. Это делается через класс NSURLSessionConfiguration . Он не только предоставляет NSURLSession API NSURLSession через класс NSURLSessionConfiguration , но и способствует отделению данных (тела запроса) от метаданных. NSURLSessionDownloadTask хорошо иллюстрирует это, напрямую записывая ответ в файловую систему.

Аутентификация проще и элегантнее обрабатывается NSURLSession . API NSURLSession обрабатывает аутентификацию на основе соединения, а не на основе запроса, как NSURLConnection . NSURLSession API также делает более удобным предоставление опций HTTP, и каждый сеанс может иметь отдельный контейнер хранения в зависимости от того, как вы настроили сеанс.

Во введении я говорил вам, что NSURLSession предоставляет современный интерфейс, который изящно интегрируется с iOS 7. Одним из примеров такой интеграции является NSURLSession вне процесса NSURLSession . NSURLSession оптимизирован для сохранения времени работы от батареи, поддерживает приостановку, отмену и возобновление задач, а также многозадачный API UIKit. Что не нравится в NSURLSession ?


Новый API лучше всего изучается на практике, поэтому пришло время запустить Xcode и намочить ноги. Запустите Xcode 5, создайте новый проект, выбрав New> Project … в меню File , и выберите шаблон приложения Single View Application из списка шаблонов приложений iOS.

Создайте новый проект в Xcode 5.

Дайте вашему проекту имя, сообщите Xcode, где вы хотите его сохранить, и нажмите « Создать» . Нет необходимости ставить проект под контроль версий.

Настройте проект.

При работе с NSURLSession важно понимать, что объект сеанса, экземпляр NSURLSession , является звездным игроком. Он обрабатывает запросы и ответы, настраивает запросы, управляет хранением и состоянием сеанса и т. Д. Создать сеанс можно несколькими способами. Самый быстрый способ начать работу — это использовать метод класса sharedSession как показано ниже.

1
2
3
4
5
— (void)viewDidLoad {
    [super viewDidLoad];
 
    NSURLSession *session = [NSURLSession sharedSession];
}

Создайте объект session в методе viewDidLoad контроллера представления, как показано выше. Созданный нами объект session подходит для нашего примера, но в большинстве случаев вам, вероятно, потребуется немного больше гибкости. Объект session мы только что создали, использует глобальные NSURLCache , NSHTTPCookieStorage и NSURLCredentialStorage . Это означает, что он работает очень похоже на стандартную реализацию NSURLConnection .

Чтобы использовать объект session , давайте запросим API поиска iTunes Store и поищем программное обеспечение, созданное Apple. API поиска iTunes Store прост в использовании и не требует аутентификации, что делает его идеальным для нашего примера.

Чтобы запросить API поиска, нам нужно отправить запрос на https://itunes.apple.com/search и передать несколько параметров. Как мы видели ранее, при использовании NSURLSession API запрос представляется задачей. Чтобы запросить API поиска, все, что нам нужно, это задача с данными, экземпляр класса NSURLSessionDataTask . Взгляните на обновленную реализацию viewDidLoad показанную ниже.

1
2
3
4
5
6
7
8
9
— (void)viewDidLoad {
    [super viewDidLoad];
 
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:@»https://itunes.apple.com/search?term=apple&media=software»] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        NSLog(@»%@», json);
    }];
}

Существует несколько методов, доступных для создания задачи, но ключевая концепция, которую необходимо понять, заключается в том, что объект session выполняет фактическое создание и настройку задачи. В этом примере мы вызываем dataTaskWithURL:completionHandler: и передаем ему экземпляр NSURL а также обработчик завершения. Обработчик завершения принимает три аргумента: необработанные данные ответа ( NSData ), объект ответа ( NSURLResponse ) и объект ошибки ( NSError ). Если запрос выполнен успешно, объект ошибки имеет значение nil . Поскольку мы знаем, что запрос возвращает ответ JSON, мы создаем объект Foundation из data объекта data и записываем результаты в консоль.

Важно понимать, что объект error переданный обработчику завершения, заполняется, а не nil , если запрос не выполнен или обнаружена ошибка. Другими словами, если запрос возвратил ответ 404 , запрос действительно был успешным в отношении сеансов. Объект error будет равен nil . Это важная концепция для понимания при работе с NSURLSession и NSURLConnection по этому вопросу.

Создайте проект и запустите приложение в iOS Simulator или на физическом устройстве и осмотрите консоль Xcode. Ничего не выводится на консоль. Что пошло не так? Как я упоминал ранее, NSURLSession API поддерживает приостановку, отмену и возобновление задач или запросов. Это поведение аналогично NSOperation и может напоминать вам библиотеку AFNetworking . Задача имеет свойство state которое указывает, выполняется ли задача ( NSURLSessionTaskStateRunning ), приостановлена ( NSURLSessionTaskStateSuspended ), NSURLSessionTaskStateCanceling ( NSURLSessionTaskStateCanceling ) или выполнена ( NSURLSessionTaskStateCompleted ). Когда объект сеанса создает задачу, задача начинает свою жизнь в приостановленном состоянии. Чтобы запустить задачу, нам нужно сказать, чтобы она resume , вызывая ее. Обновите метод viewDidLoad как показано ниже, еще раз запустите приложение и проверьте вывод в консоли. Миссия выполнена.

01
02
03
04
05
06
07
08
09
10
11
— (void)viewDidLoad {
    [super viewDidLoad];
 
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:@»https://itunes.apple.com/search?term=apple&media=software»] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        NSLog(@»%@», json);
    }];
 
    [dataTask resume];
}

В предыдущем примере мы использовали обработчик завершения для обработки ответа, полученного нами от запроса. Также можно достичь того же результата, внедрив протокол (ы) делегата задачи. Давайте посмотрим, что нужно для загрузки изображения, используя NSURLSession и NSURLSessionDownloadTask .

Откройте MTViewController.h и создайте два выхода, как показано ниже. Мы будем использовать первый выход, экземпляр UIImageView , для отображения загруженного изображения пользователю. Второй выпуск, экземпляр UIProgressView , будет отображать ход выполнения задачи загрузки.

1
2
3
4
5
6
7
8
#import <UIKit/UIKit.h>
 
@interface MTViewController : UIViewController
 
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;
 
@end

Откройте основную раскадровку проекта ( Main.storyboard ), перетащите экземпляр UIImageView в представление контроллера представления и подключите выход контроллера представления, который мы только что создали, в заголовочном файле контроллера представления. Повторите этот процесс для просмотра прогресса.

Настройте пользовательский интерфейс приложения.

В этом примере мы не будем использовать sharedSession класса sharedSession , потому что нам нужно настроить объект session который мы будем использовать для выполнения запроса. Обновите реализацию viewDidLoad как показано ниже.

1
2
3
4
5
6
7
8
— (void)viewDidLoad {
    [super viewDidLoad];
 
    NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil];
    NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:[NSURL URLWithString:@»http://cdn.tutsplus.com/mobile/uploads/2013/12/sample.jpg»]];
    [downloadTask resume];
}

Чтобы предотвратить появление предупреждений компилятора, убедитесь, что класс NSURLSessionDelegate NSURLSessionDownloadDelegate протоколам NSURLSessionDelegate и NSURLSessionDownloadDelegate , как показано ниже.

1
2
3
4
5
#import «MTViewController.h»
 
@interface MTViewController () <NSURLSessionDelegate, NSURLSessionDownloadDelegate>
 
@end

В viewDidLoad мы создаем экземпляр класса NSURLSessionConfiguration , вызывая defaultSessionConfiguration класса defaultSessionConfiguration . Как указано в документации, при использовании конфигурации сеанса по умолчанию сеанс будет вести себя как экземпляр NSURLConnection в конфигурации по умолчанию, что хорошо для нашего примера.

В этом примере мы создаем экземпляр NSURLSession , вызывая метод sessionWithConfiguration:delegate:delegateQueue: метод класса, и передаем объект sessionWithConfiguration:delegate:delegateQueue: созданный нами недавно. Мы устанавливаем контроллер представления в качестве делегата сеанса и передаем nil в качестве третьего аргумента. Вы можете игнорировать третий аргумент на данный момент. Основное отличие от предыдущего примера состоит в том, что мы устанавливаем делегат session в контроллере представления.

Чтобы загрузить изображение, нам нужно создать задачу загрузки. Мы делаем это, вызывая downloadTaskWithURL: для объекта session , передавая экземпляр NSURL и вызывая resume в задаче загрузки. Мы могли бы использовать обработчик завершения, как мы делали ранее, но я хочу показать вам возможности использования делегата.

Чтобы все это работало, нам нужно реализовать три метода- NSURLSessionDownloadDelegate протокола NSURLSessionDownloadDelegate , URLSession:downloadTask:didFinishDownloadingToURL: URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes: и URLSession:downloadTask:downloadTask didWriteData:totalBytesWritten:totalBytesExpectedToWrite: Реализация каждого метода довольно проста. Важно отметить, что нам нужно обновить пользовательский интерфейс в главном потоке, используя GCD (Grand Central Dispatch). sessionWithConfiguration:delegate:delegateQueue: nil в качестве третьего аргумента sessionWithConfiguration:delegate:delegateQueue: операционная система создала для нас фоновую очередь. Это хорошо, но это также означает, что мы должны знать, что методы делегата вызываются в фоновом потоке вместо основного потока. Создайте проект и запустите приложение, чтобы увидеть результат нашей тяжелой работы.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
— (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
    NSData *data = [NSData dataWithContentsOfURL:location];
 
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.progressView setHidden:YES];
        [self.imageView setImage:[UIImage imageWithData:data]];
    });
}
 
— (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {
 
}
 
— (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
    float progress = (double)totalBytesWritten / (double)totalBytesExpectedToWrite;
 
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.progressView setProgress:progress];
    });
}

С этими двумя примерами вы должны иметь NSURLSession представление об NSURLSession API NSURLSession , о том, как он сравнивается с NSURLConnection , и каковы его преимущества. В следующей части этой серии мы рассмотрим более продвинутые функции NSURLSession .