В предыдущем уроке я познакомил вас с NSURLSession
. Я говорил о его преимуществах по сравнению с NSURLConnection
и о том, как использовать NSURLSession
для простых задач, таких как получение данных из веб-службы и загрузка изображений из Интернета. В этом руководстве мы более подробно рассмотрим параметры конфигурации NSURLSession
и способы отмены и возобновления задачи загрузки. У нас есть много возможностей, так что давайте начнем.
Конфигурация сеанса
Как мы видели в предыдущем руководстве , сеанс, экземпляр NSURLSession
, является настраиваемым контейнером для размещения сетевых запросов. Конфигурация сеанса обрабатывается экземпляром NSURLSessionConfiguration
.
Объект конфигурации сеанса — это не что иное, как словарь свойств, который определяет, как ведет себя сеанс. Сеанс имеет один объект конфигурации сеанса, который задает политики cookie, безопасности и кэширования, максимальное количество подключений к хосту, время NSURLConnection
ресурса и сети и т. Д. Это значительное улучшение по сравнению с NSURLConnection
, который основывался на глобальном объекте конфигурации с большим меньше гибкости
переменчивость
Как только сеанс создан и настроен экземпляром NSURLSessionConfiguration
, конфигурация сеанса не может быть изменена. Если вам нужно изменить конфигурацию сеанса, вы должны создать новый сеанс. Помните, что можно скопировать конфигурацию сеанса и изменить ее, но эти изменения не влияют на сеанс, из которого была скопирована конфигурация.
Конфигурация по умолчанию
Класс NSURLSessionConfiguration
предоставляет три конструктора фабрики для создания экземпляров стандартных конфигураций, defaultSessionConfiguration
, ephemeralSessionConfiguration
и backgroundSessionConfiguration
. Первый метод возвращает копию объекта конфигурации сеанса по умолчанию , в результате чего сеанс ведет себя подобно объекту NSURLConnection
в его стандартной конфигурации. Изменение конфигурации сеанса, полученной с помощью defaultSessionConfiguration
фабрики defaultSessionConfiguration
, не меняет конфигурацию сеанса по умолчанию, копию которой она представляет.
Эфемерная конфигурация
Объект конфигурации сеанса, созданный путем вызова метода фабрики ephemeralSessionConfiguration
гарантирует, что в полученном сеансе не будет постоянного хранилища для файлов cookie, кэшей или учетных данных. Другими словами, файлы cookie, кэши и учетные данные хранятся в памяти. Поэтому эфемерные сеансы идеальны, если вам нужно реализовать приватный просмотр, что было просто невозможно до появления NSURLSession
.
Фоновая конфигурация
backgroundSessionConfiguration:
метод backgroundSessionConfiguration:
создает объект конфигурации сеанса, который позволяет загружать и выгружать вне процесса. Задачи загрузки и выгрузки управляются фоновым демоном и продолжают выполняться, даже если приложение приостановлено или происходит сбой. Мы поговорим больше о фоновых сессиях позже в этой серии.
Конфигурация сеанса
Как мы видели в предыдущем уроке, создать объект конфигурации сеанса очень просто. В приведенном ниже примере я использовал фабричный метод defaultSessionConfiguration
для создания экземпляра NSURLSessionConfiguration
. Настроить объект конфигурации сеанса так же просто, как изменить его свойства, как показано в примере. Затем мы можем использовать объект конфигурации сеанса для создания экземпляра объекта сеанса. Объект сеанса служит фабрикой для данных, задач загрузки и выгрузки, причем каждая задача соответствует одному запросу. В приведенном ниже примере мы запрашиваем API поиска iTunes, как мы делали в предыдущем руководстве.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
// Create Session Configuration
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
// Configure Session Configuration
[sessionConfiguration setAllowsCellularAccess:YES];
[sessionConfiguration setHTTPAdditionalHeaders:@{ @»Accept» : @»application/json» }];
// Create Session
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
// Send Request
NSURL *url = [NSURL URLWithString:@»https://itunes.apple.com/search?term=apple&media=software»];
[[session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@»%@», [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]);
}] resume];
|
В примере также показано, как легко добавить пользовательские заголовки, установив свойство HTTPAdditionalHeaders
объекта конфигурации сеанса. Прелесть NSURLSession
API в том, что каждый запрос, проходящий через сеанс, настраивается объектом конфигурации сеанса. Например, добавление заголовков аутентификации в набор запросов становится простым делом.
Отмена и возобновление загрузок
В предыдущем уроке я показал вам, как загрузить изображение с помощью NSURLSession
API. Однако сетевые подключения ненадежны, и слишком часто случается, что загрузка завершается неудачно из-за нестабильного сетевого подключения. К счастью, возобновить загрузку не сложно с помощью NSURLSession
API. В следующем примере я покажу вам, как отменить и возобновить загрузку изображения.
Прежде чем мы подробнее рассмотрим возобновление задачи загрузки, важно понять разницу между отменой и приостановкой задачи загрузки. Можно приостановить загрузку и возобновить ее позднее. Однако отмена задачи загрузки останавливает задачу, и возобновить ее позже невозможно. Однако есть одна альтернатива. Можно отменить задачу загрузки, вызвав на нем cancelByProducingResumeData:
. Он принимает обработчик завершения, который принимает один параметр — объект NSData
который используется для возобновления загрузки позднее, вызывая downloadTaskWithResumeData:
или downloadTaskWithResumeData:completionHandler:
в объекте сеанса. Объект NSData
содержит необходимую информацию для возобновления задачи загрузки с того места, где оно было прервано.
Шаг 1: Аутлеты и Действия
Откройте проект, который мы создали в предыдущем уроке, или загрузите его здесь . Мы начнем с добавления в пользовательский интерфейс двух кнопок: одна для отмены загрузки и одна для возобновления загрузки. В заголовочном файле контроллера представления создайте розетку и действие для каждой кнопки, как показано ниже.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
#import <UIKit/UIKit.h>
@interface MTViewController : UIViewController
@property (weak, nonatomic) IBOutlet UIButton *cancelButton;
@property (weak, nonatomic) IBOutlet UIButton *resumeButton;
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;
— (IBAction)cancel:(id)sender;
— (IBAction)resume:(id)sender;
@end
|
Шаг 2: Пользовательский интерфейс
Откройте основную раскадровку проекта и добавьте две кнопки в представление контроллера представления. Расположите кнопки, как показано на скриншоте ниже, и соедините каждую кнопку с соответствующей розеткой и действием.
Шаг 3: Рефакторинг
Нам нужно сделать рефакторинг, чтобы все работало правильно. Откройте MTViewController.m
и объявите одну переменную экземпляра и два свойства. Переменная экземпляра session
будет содержать ссылку на сеанс, который мы будем использовать для загрузки изображения.
01
02
03
04
05
06
07
08
09
10
|
#import «MTViewController.h»
@interface MTViewController () <NSURLSessionDelegate, NSURLSessionDownloadDelegate> {
NSURLSession *_session;
}
@property (strong, nonatomic) NSURLSessionDownloadTask *downloadTask;
@property (strong, nonatomic) NSData *resumeData;
@end
|
Нам также нужно реорганизовать метод viewDidLoad
, но сначала я бы хотел реализовать метод getter для сеанса. Его реализация довольно проста, как вы можете видеть ниже. Мы создаем объект конфигурации сеанса с помощью defaultSessionConfiguration
фабрики defaultSessionConfiguration
и defaultSessionConfiguration
с его помощью объект сеанса. Контроллер представления служит делегатом сеанса.
01
02
03
04
05
06
07
08
09
10
11
|
— (NSURLSession *)session {
if (!_session) {
// Create Session Configuration
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
// Create Session
_session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil];
}
return _session;
}
|
После реализации средства viewDidLoad
к viewDidLoad
метод viewDidLoad
становится намного проще. Мы создаем задачу загрузки, как мы это делали в предыдущем руководстве, и сохраняем ссылку на задачу в downloadTask
. Затем мы просим задачу загрузки resume
.
1
2
3
4
5
6
7
8
9
|
— (void)viewDidLoad {
[super viewDidLoad];
// Create Download Task
self.downloadTask = [self.session downloadTaskWithURL:[NSURL URLWithString:@»http://cdn.tutsplus.com/mobile/uploads/2014/01/5a3f1-sample.jpg»]];
// Resume Download Task
[self.downloadTask resume];
}
|
Шаг 4: Отмена загрузки
Действие cancel:
содержит логику отмены задачи загрузки, которую мы только что создали. Если downloadTask
не равно nil
, мы вызываем cancelByProducingResumeData:
для задачи. Этот метод принимает один параметр — блок завершения. Блок завершения также принимает один параметр, экземпляр NSData
. Если resumeData
не nil
, мы сохраняем ссылку на объект данных в свойстве resumeData
контроллера представления.
Если загрузка не возобновляется, параметр resumeData
блока завершения имеет resumeData
nil
. Не каждая загрузка возобновляема, поэтому важно проверить, является ли resumeData
допустимым объектом NSData
.
01
02
03
04
05
06
07
08
09
10
11
12
|
— (IBAction)cancel:(id)sender {
if (!self.downloadTask) return;
// Hide Cancel Button
[self.cancelButton setHidden:YES];
[self.downloadTask cancelByProducingResumeData:^(NSData *resumeData) {
if (!resumeData) return;
[self setResumeData:resumeData];
[self setDownloadTask:nil];
}];
}
|
Шаг 5: Возобновление загрузки
Возобновить загрузку после отмены очень просто. В действии resume:
мы проверяем, установлено ли свойство resumeData
контроллера представления. Если resumeData
является допустимым объектом NSData
, мы сообщаем объекту session
создать новую задачу загрузки и передаем ей объект NSData
. Это все, что нужно объекту session
для воссоздания задачи загрузки, которую мы отменили в действии cancel:
Затем мы resumeData
задаче загрузки resume
и устанавливаем значение resumeData
nil
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
— (IBAction)resume:(id)sender {
if (!self.resumeData) return;
// Hide Resume Button
[self.resumeButton setHidden:YES];
// Create Download Task
self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData];
// Resume Download Task
[self.downloadTask resume];
// Cleanup
[self setResumeData:nil];
}
|
Создайте проект и запустите приложение в iOS Simulator или на физическом устройстве. Загрузка должна начаться автоматически. Нажмите кнопку отмены, чтобы отменить загрузку, и нажмите кнопку возобновления, чтобы возобновить загрузку.
Шаг 6: Последние штрихи
Есть ряд деталей, о которых нам нужно позаботиться. Прежде всего, кнопки не всегда должны быть видны. Мы будем использовать наблюдение значения ключа, чтобы при необходимости показывать и скрывать кнопки. В viewDidLoad
кнопки и добавьте контроллер представления в качестве наблюдателя для resumeData
ключа resumeData
и downloadTask
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
— (void)viewDidLoad {
[super viewDidLoad];
// Add Observer
[self addObserver:self forKeyPath:@»resumeData» options:NSKeyValueObservingOptionNew context:NULL];
[self addObserver:self forKeyPath:@»downloadTask» options:NSKeyValueObservingOptionNew context:NULL];
// Setup User Interface
[self.cancelButton setHidden:YES];
[self.resumeButton setHidden:YES];
// Create Download Task
self.downloadTask = [self.session downloadTaskWithURL:[NSURL URLWithString:@»http://cdn.tutsplus.com/mobile/uploads/2014/01/5a3f1-sample.jpg»]];
// Resume Download Task
[self.downloadTask resume];
}
|
В observeValueForKeyPath:ofObject:change:context:
мы скрываем кнопку отмены, если resumeData
равен nil
и скрываем кнопку возобновления, если downloadTask
равен nil
. Создайте проект и запустите приложение еще раз, чтобы увидеть результат. Это лучше. Правильно?
01
02
03
04
05
06
07
08
09
10
11
12
|
— (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:@»resumeData»]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.resumeButton setHidden:(self.resumeData == nil)];
});
} else if ([keyPath isEqualToString:@»downloadTask»]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.cancelButton setHidden:(self.downloadTask == nil)];
});
}
}
|
observeValueForKeyPath:ofObject:change:context:
ли в observeValueForKeyPath:ofObject:change:context:
Поэтому важно обновить пользовательский интерфейс в блоке GCD (Grand Central Dispatch), который вызывается в главной очереди. Шаг 7: аннулирование сессии
Есть один ключевой аспект NSURLSession
о котором я еще не говорил, недействительность сессии. Сеанс сохраняет строгую ссылку на своего делегата, что означает, что делегат не освобождается, пока сеанс активен. Чтобы разорвать этот ссылочный цикл, сеанс должен быть признан недействительным. Когда сеанс признан недействительным, активные задачи отменяются или завершаются, и делегату отправляется сообщение URLSession:didBecomeInvalidWithError:
и сеанс освобождает своего делегата.
Есть несколько мест, которые мы можем сделать недействительными. Поскольку контроллер представления загружает только одно изображение, сеанс может быть признан недействительным по окончании загрузки. Взгляните на обновленную реализацию URLSession:downloadTask:didFinishDownloadingToURL:
Кнопка отмены также скрыта после завершения загрузки.
01
02
03
04
05
06
07
08
09
10
11
12
|
— (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
NSData *data = [NSData dataWithContentsOfURL:location];
dispatch_async(dispatch_get_main_queue(), ^{
[self.cancelButton setHidden:YES];
[self.progressView setHidden:YES];
[self.imageView setImage:[UIImage imageWithData:data]];
});
// Invalidate Session
[session finishTasksAndInvalidate];
}
|
Вывод
Пример проекта, который мы создали в этом руководстве, представляет собой упрощенную реализацию отмены и возобновления загрузки. В ваших приложениях может потребоваться записать объект resumeData
на диск для последующего использования, и может быть возможно, что несколько задач загрузки выполняются одновременно. Хотя это добавляет сложности, основные принципы остаются прежними. Обязательно предотвращайте утечки памяти, всегда отменяя сеанс, который вам больше не нужен.