Используя NSURLSession

NSURLSession класс и связанные классы обеспечивают API для загрузки содержания через HTTP. Этот API обеспечивает богатый набор методов делегата для поддержки аутентификации и предоставляет Вашему приложению возможность выполнить фоновые загрузки, когда Ваше приложение не работает или в iOS, в то время как приостановлено Ваше приложение.

Использовать NSURLSession API, Ваше приложение создает серию сеансов, каждый из которых координирует группу связанных задач передачи данных. Например, если Вы пишете веб-браузер, Ваше приложение могло бы создать один сеанс на вкладку или окно. В каждом сеансе Ваше приложение добавляет серию задач, каждая из которых представляет запрос на определенный URL (и на любой последующий URLs, если исходный URL возвратил перенаправление HTTP).

Как самый сетевой APIs, NSURLSession API является очень асинхронным. При использовании значения по умолчанию, предоставленного системой делегата, необходимо обеспечить блок обработчика завершения, возвращающий данные приложению, когда передача заканчивается успешно или с ошибкой. Также при обеспечении собственных объектов делегата объекты задачи вызывают методы тех делегатов с данными, поскольку они получены от сервера (или, для загрузок файла, когда передача завершена).

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

Понимание понятий сеанса URL

Поведение задач в сеансе зависит от трех вещей: тип сеанса (определенный типом объекта конфигурации раньше создавал его), тип задачи, и было ли приложение на переднем плане, когда создавалась задача.

Типы сеансов

NSURLSession API поддерживает три типа сеансов, как определено типом объекта конфигурации, используемого для создания сеанса:

  • Сеансы по умолчанию ведут себя так же к другим методам Основы для загрузки URLs. Они используют персистентный находящийся на диске кэш и хранят учетные данные в цепочке для ключей пользователя.

  • Эфемерные сеансы не хранят данных к диску; все кэши, учетные хранилища, и т.д. сохранены в RAM и связаны к сеансу. Таким образом, когда Ваше приложение лишает законной силы сеанс, они очищены автоматически.

  • Фоновые сеансы подобны сеансам по умолчанию, за исключением того, что отдельный процесс обрабатывает всю передачу данных. Фоновые сеансы имеют некоторые дополнительные ограничения, описанные в Фоновых Соображениях Передачи.

Типы задач

В сеансе, NSURLSession класс поддерживает три типа задач: задачи данных, задачи загрузки и задачи загрузки.

  • Задачи данных отправляют и получают использование данных NSData объекты. Задачи данных предназначаются для краткости часто интерактивные запросы с Вашего приложения на сервер. Задачи данных могут возвратить данные Вашему приложению одна часть за один раз после того, как каждая часть данных будет получена, или одновременно через обработчик завершения. Поскольку задачи данных не хранят данные к файлу, они не поддерживаются в фоновых сеансах.

  • В то время как приложение не работает, задачи загрузки получают данные в форме файла и поддерживают фоновые загрузки.

  • В то время как приложение не работает, задачи загрузки отправляют данные (обычно в форме файла) и поддерживают фоновые загрузки.

Фоновые соображения передачи

NSURLSession в то время как Ваше приложение приостановлено, класс поддерживает фоновые передачи. Фоновые передачи предоставлены только сеансами, создаваемыми с помощью фонового объекта конфигурации сеанса (как возвращено вызовом к backgroundSessionConfiguration:).

С фоновыми сеансами, потому что фактическая передача выполняется отдельным процессом и потому что перезапуск процесса Вашего приложения является относительно дорогим, несколько функций недоступны, приводя к следующим ограничениям:

  • Сеанс должен предоставить делегату к поставке события. (Для загрузок и загрузок, делегаты ведут себя то же что касается незавершенных передач.)

  • Только HTTP и протоколы HTTPS поддерживаются (никакие пользовательские протоколы).

  • Только загрузка и задачи загрузки поддерживаются (никакие задачи данных).

  • Перенаправления всегда сопровождаются.

  • В то время как приложение в фоновом режиме, объект конфигурации, если инициируется фоновая передача discretionary свойство обрабатывается как являющийся true.

Путем Ваше приложение ведет себя, когда оно повторно запускается, отличается немного между iOS и OS X.

В iOS, когда фоновая передача завершает или требует учетных данных, если Ваше приложение больше не работает, iOS автоматически повторно запускает Ваше приложение в фоновом режиме и вызывает application:handleEventsForBackgroundURLSession:completionHandler: метод на Вашем приложении UIApplicationDelegate объект. Этот вызов обеспечивает идентификатор сеанса, заставившего Ваше приложение быть запущенным. Ваше приложение должно сохранить тот обработчик завершения, создать фоновый объект конфигурации с тем же идентификатором и создать сеанс с тем объектом конфигурации. Новый сеанс автоматически повторно связан с продолжающимся фоновым действием. Позже, когда сеанс заканчивает последнюю фоновую задачу загрузки, он отправляет делегата сеанса a URLSessionDidFinishEventsForBackgroundURLSession: сообщение. Ваш делегат сеанса должен тогда вызвать сохраненный обработчик завершения.

И в iOS и в OS X, когда пользователь повторно запускает Ваше приложение, Ваше приложение должно сразу создать фоновые объекты конфигурации с теми же идентификаторами как любые сеансы, имевшие выдающиеся задачи, когда Ваше приложение в последний раз работало, затем создайте сеанс для каждого из тех объектов конфигурации. Эти новые сеансы так же автоматически повторно связаны с продолжающимся фоновым действием.

В то время как Ваше приложение было приостановлено, делегат, если завершилась какая-либо задача URLSession:downloadTask:didFinishDownloadingToURL: метод тогда вызывают с задачей и URL для недавно загруженного файла, связанного с ним.

Точно так же, если какая-либо задача требует учетных данных, NSURLSession вызовы объектов делегат URLSession:task:didReceiveChallenge:completionHandler: метод или URLSession:didReceiveChallenge:completionHandler: метод как надлежащий.

Для примера того, как использовать NSURLSession для фоновых передач посмотрите Простую Фоновую Передачу.

Жизненный цикл и взаимодействие делегата

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

Для полного описания жизненного цикла сеанса URL считайте Жизненный цикл Сеанса URL.

Поведение NSCopying

Сеанс и объекты задачи соответствуют NSCopying протокол следующим образом:

  • Когда Ваши копии приложений сеанс или объект задачи, Вы возвращаете тот же объект.

  • Когда Ваши копии приложений объект конфигурации, Вы получаете новую копию, которую можно независимо изменить.

Демонстрационный интерфейс класса делегата

Фрагменты кода в следующих разделах задачи основываются на интерфейсе класса, показанном в Перечислении 1-1.

  Выборка перечисления 1-1 делегирует интерфейс класса

#import <Foundation/Foundation.h>
 
 
typedef void (^CompletionHandlerType)();
 
@interface MySessionDelegate : NSObject <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>
 
@property NSURLSession *backgroundSession;
@property NSURLSession *defaultSession;
@property NSURLSession *ephemeralSession;
 
#if TARGET_OS_IPHONE
@property NSMutableDictionary *completionHandlerDictionary;
#endif
 
- (void) addCompletionHandler: (CompletionHandlerType) handler forSession: (NSString *)identifier;
- (void) callCompletionHandlerForSession: (NSString *)identifier;
 
 
@end

Создание и конфигурирование сеанса

NSURLSession API обеспечивает широкий диапазон параметров конфигурации:

Поскольку большинство настроек содержится в отдельном объекте конфигурации, можно снова использовать обычно используемые настройки. При инстанцировании объекта сеанса Вы указываете следующее:

После инстанцирования объекта сеанса Вы не можете изменить конфигурацию или делегата, не создавая новый сеанс.

Перечисление 1-2 показывает примеры того, как создать нормальные, эфемерные, и фоновые сеансы.

Перечисление 1-2  Создающие и конфигурирующие сеансы

 
#if TARGET_OS_IPHONE
    self.completionHandlerDictionary = [NSMutableDictionary dictionaryWithCapacity:0];
#endif
 
    /* Create some configuration objects. */
 
    NSURLSessionConfiguration *backgroundConfigObject = [NSURLSessionConfiguration backgroundSessionConfiguration: @"myBackgroundSessionIdentifier"];
    NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSessionConfiguration *ephemeralConfigObject = [NSURLSessionConfiguration ephemeralSessionConfiguration];
 
 
    /* Configure caching behavior for the default session.
       Note that iOS requires the cache path to be a path relative
       to the ~/Library/Caches directory, but OS X expects an
       absolute path.
     */
#if TARGET_OS_IPHONE
    NSString *cachePath = @"/MyCacheDirectory";
 
    NSArray *myPathList = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString *myPath    = [myPathList  objectAtIndex:0];
 
    NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
 
    NSString *fullCachePath = [[myPath stringByAppendingPathComponent:bundleIdentifier] stringByAppendingPathComponent:cachePath];
    NSLog(@"Cache path: %@\n", fullCachePath);
#else
    NSString *cachePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"/nsurlsessiondemo.cache"];
 
    NSLog(@"Cache path: %@\n", cachePath);
#endif
 
 
 
 
 
    NSURLCache *myCache = [[NSURLCache alloc] initWithMemoryCapacity: 16384 diskCapacity: 268435456 diskPath: cachePath];
    defaultConfigObject.URLCache = myCache;
    defaultConfigObject.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
 
    /* Create a session for each configurations. */
    self.defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];
    self.backgroundSession = [NSURLSession sessionWithConfiguration: backgroundConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];
    self.ephemeralSession = [NSURLSession sessionWithConfiguration: ephemeralConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];

За исключением фоновых конфигураций, можно снова использовать объекты конфигурации сеанса создать дополнительные сеансы. (Вы не можете снова использовать фоновые конфигурации сеанса, потому что поведение двух фоновых объектов сеанса, совместно использующих тот же идентификатор, не определено.)

В любое время можно также безопасно изменить объекты конфигурации. При создании сеанса сеанс выполняет глубокую копию на объекте конфигурации, таким образом, модификации влияют только на новые сеансы, не существующие сеансы. Например, Вы могли бы создать второй сеанс для содержания, которое должно быть получено, только если Вы находитесь на соединении Wi-Fi как показано в Перечислении 1-3.

Перечисление 1-3  , Создающее второй сеанс с тем же объектом конфигурации

    ephemeralConfigObject.allowsCellularAccess = YES;
 
    // ...
 
    NSURLSession *ephemeralSessionWiFiOnly = [NSURLSession sessionWithConfiguration: ephemeralConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];

Выбирающие ресурсы Используя предоставленных системой делегатов

Самый прямой способ использовать NSURLSession как понижение замены для sendAsynchronousRequest:queue:completionHandler: метод на NSURLSession. Используя этот подход, Ваша потребность обеспечить только две части кода в Вашем приложении:

Используя предоставленных системой делегатов, можно выбрать определенный URL только с одной строкой кода на запрос. Перечисление 1-4 показывает пример этой упрощенной формы.

Перечисление 1-4  , Запрашивающее ресурс с помощью предоставленный системой делегатов

    NSURLSession *delegateFreeSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: [NSOperationQueue mainQueue]];
 
    [[delegateFreeSession dataTaskWithURL: [NSURL URLWithString: @"http://www.example.com/"]
                       completionHandler:^(NSData *data, NSURLResponse *response,
                                           NSError *error) {
                           NSLog(@"Got response %@ with error %@.\n", response, error);
                           NSLog(@"DATA:\n%@\nEND DATA\n",
                                 [[NSString alloc] initWithData: data
                                         encoding: NSUTF8StringEncoding]);
                       }] resume];

Выборка данных Используя пользовательского делегата

Если Вы используете пользовательского делегата для получения данных, делегат должен реализовать, по крайней мере, следующие методы:

Если Ваше приложение должно использовать данные после URLSession:dataTask:didReceiveData: возвраты метода, Ваш код ответственен за то, что хранил данные в некотором роде.

Например, веб-браузер, возможно, должен был бы представить данные, когда это поступает вместе с любыми данными, которые это ранее получило. Чтобы сделать это, это могло бы использовать словарь, отображающий объект задачи на NSMutableData объект для хранения результатов, и затем использует appendData: метод на том объекте добавить недавно полученные данные.

Перечисление 1-5 показывает, как Вы создаете и запускаете задачу данных.

  Пример задачи Данных перечисления 1-5

    NSURL *url = [NSURL URLWithString: @"http://www.example.com/"];
 
    NSURLSessionDataTask *dataTask = [self.defaultSession dataTaskWithURL: url];
    [dataTask resume];

Загрузка файлов

На высоком уровне, загружая файл подобно получению данных. Ваше приложение должно реализовать следующие методы делегата:

Когда приложение не работает, при планировании загрузки в фоновом сеансе загрузка продолжается. Когда приложение повторно запускается, при планировании загрузки в стандартном или эфемерном сеансе загрузка должна начаться снова.

Во время передачи от сервера, если пользователь говорит Вашему приложению приостанавливать загрузку, Ваше приложение может отменить задачу путем вызова cancelByProducingResumeData: метод. Позже, Ваше приложение может передать возвращенные данные резюме любому downloadTaskWithResumeData: или downloadTaskWithResumeData:completionHandler: метод для создания новой задачи загрузки, продолжающей загрузку.

Если передача перестала работать, Ваш делегат URLSession:task:didCompleteWithError: метод вызывают с NSError объект. Если задача resumable, что объект userInfo словарь содержит значение для NSURLSessionDownloadTaskResumeData ключ. Ваше приложение должно использовать достижимость APIs для определения, когда повторить и должно тогда вызвать downloadTaskWithResumeData: или downloadTaskWithResumeData:completionHandler: создать новую задачу загрузки продолжать ту загрузку.

Перечисление 1-6 обеспечивает пример загрузки умеренно большого файла. Перечисление 1-7 обеспечивает пример методов делегата задачи загрузки.

  Пример задачи Загрузки перечисления 1-6

    NSURL *url = [NSURL URLWithString: @"/RU/iOS/documentation/Cocoa/Reference/"
                  "Foundation/ObjC_classic/FoundationObjC.pdf"];
 
    NSURLSessionDownloadTask *downloadTask = [self.backgroundSession downloadTaskWithURL: url];
    [downloadTask resume];

  Методы делегата перечисления 1-7 для задач загрузки

-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
    NSLog(@"Session %@ download task %@ finished downloading to URL %@\n",
        session, downloadTask, location);
 
#if 0
    /* Workaround */
    [self callCompletionHandlerForSession:session.configuration.identifier];
#endif
 
#define READ_THE_FILE 0
#if READ_THE_FILE
    /* Open the newly downloaded file for reading. */
    NSError *err = nil;
    NSFileHandle *fh = [NSFileHandle fileHandleForReadingFromURL:location
        error: &err];
 
    /* Store this file handle somewhere, and read data from it. */
    // ...
 
#else
    NSError *err = nil;
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *cacheDir = [[NSHomeDirectory()
        stringByAppendingPathComponent:@"Library"]
        stringByAppendingPathComponent:@"Caches"];
    NSURL *cacheDirURL = [NSURL fileURLWithPath:cacheDir];
    if ([fileManager moveItemAtURL:location
        toURL:cacheDirURL
        error: &err]) {
 
        /* Store some reference to the new URL */
    } else {
        /* Handle the error. */
    }
#endif
 
}
 
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    NSLog(@"Session %@ download task %@ wrote an additional %lld bytes (total %lld bytes) out of an expected %lld bytes.\n",
        session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
}
 
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
{
    NSLog(@"Session %@ download task %@ resumed at offset %lld bytes out of an expected %lld bytes.\n",
        session, downloadTask, fileOffset, expectedTotalBytes);
}
 

Загрузка содержания организации

Ваше приложение может обеспечить содержание организации запроса для HTTP запрос POST тремя способами: как NSData объект, как файл, или как поток. В целом Ваше приложение должно:

Независимо от которого стиля Вы выбираете, если Ваше приложение предоставляет пользовательскому делегату сеанса, тот делегат должен реализовать URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend: метод делегата получить загружает информацию прогресса.

Кроме того, если Ваше приложение предоставляет организации запроса с помощью потока, это должно предоставить пользовательскому делегату сеанса, реализующему URLSession:task:needNewBodyStream: метод, описанный более подробно в Загрузке Содержания Организации Используя Поток.

Загрузка содержания организации Используя объект NSData

Загружать содержание организации с NSData объект, Ваше приложение вызывает любого uploadTaskWithRequest:fromData: или uploadTaskWithRequest:fromData:completionHandler: метод для создания задачи загрузки, и предоставляет данные организации запроса через fromData параметр.

Объект сеанса вычисляет Content-Length заголовок на основе размера объекта данных.

Ваше приложение должно обеспечить любую дополнительную информацию заголовка, которой сервер мог бы потребовать — тип контента, например — как часть объекта URL-запроса.

Загрузка содержания организации Используя файл

Для загрузки содержания организации от файла приложение вызывает любого uploadTaskWithRequest:fromFile: или uploadTaskWithRequest:fromFile:completionHandler: метод для создания задачи загрузки, и обеспечивает файл URL, из которого задача читает содержание организации.

Объект сеанса вычисляет Content-Length заголовок на основе размера объекта данных. Если Ваше приложение не обеспечивает значение для Content-Type заголовок, сеанс также обеспечивает тот.

Ваше приложение может обеспечить любую дополнительную информацию заголовка, которой сервер мог бы потребовать как часть объекта URL-запроса.

Загрузка содержания организации Используя поток

Для загрузки содержания организации использование потока приложение вызывает uploadTaskWithStreamedRequest: метод для создания задачи загрузки. Ваше приложение предоставляет объекту запроса связанный поток, из которого задача читает содержание организации.

Ваше приложение должно обеспечить любую дополнительную информацию заголовка, которой сервер мог бы потребовать — тип контента и длина, например — как часть объекта URL-запроса.

Кроме того, потому что сеанс не может обязательно перемотать предоставленный поток для перечитывания данных, приложение ответственно за обеспечение нового потока, если сеанс должен повторить запрос (например, если аутентификация перестала работать). Чтобы сделать это, Ваше приложение обеспечивает a URLSession:task:needNewBodyStream: метод. Когда тот метод вызывают, Ваше приложение должно выполнить любые действия, необходимы, чтобы получить или создать новый поток организации, и затем вызвать предоставленный блок обработчика завершения с новым потоком.

Загрузка файла Используя задачу загрузки

Для загрузки содержания организации для задачи загрузки приложение должно предоставить любому NSData возразите или поток организации как часть NSURLRequest объект обеспечил, когда он создает запрос загрузки.

При обеспечении данных с помощью потока приложение должно обеспечить a URLSession:task:needNewBodyStream: метод делегата обеспечить новый поток организации в случае ошибки аутентификации. Этот метод описан далее в Загрузке Содержания Организации Используя Поток.

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

Обработка аутентификации и пользовательской проверки цепочки TLS

Если удаленный сервер возвращает код состояния, указывающий, что аутентификация требуется и если та аутентификация требует проблемы уровня соединения (такой как клиентский сертификат SSL), NSURLSession вызывает метод делегата запроса аутентификации.

Когда аутентификация перестала работать для задачи, имеющей организацию загрузки на основе потоков, задача не может обязательно перемотаться и повторное использование тот поток безопасно. Вместо этого NSURLSession вызовы объектов делегат URLSession:task:needNewBodyStream: метод делегата получить новое NSInputStream объект, предоставляющий данные организации для нового запроса. (Объект сеанса не выполняет этот вызов, если организации загрузки задачи предоставлены от файла или NSData объект.)

Для получения дополнительной информации о записи метода делегата аутентификации для NSURLSession, считайте Запросы аутентификации и Проверку Цепочки TLS.

Обработка Фонового Действия iOS

Если Вы используете NSURLSession когда загрузка завершается, в iOS автоматически повторно запускается Ваше приложение. Ваше приложение application:handleEventsForBackgroundURLSession:completionHandler: когда сеанс вызывает Вашего делегата сеанса, метод делегата приложения ответственен за воссоздание надлежащего сеанса, хранение обработчика завершения, и вызов того обработчика URLSessionDidFinishEventsForBackgroundURLSession: метод.

Перечисление 1-8 и Перечисление 1-9 показывают примеры их сеанс и методы делегата приложения, соответственно.

  Методы делегата Сеанса перечисления 1-8 для фоновых загрузок iOS

#if TARGET_OS_IPHONE
-(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
{
    NSLog(@"Background URL session %@ finished events.\n", session);
 
    if (session.configuration.identifier)
        [self callCompletionHandlerForSession: session.configuration.identifier];
}
 
- (void) addCompletionHandler: (CompletionHandlerType) handler forSession: (NSString *)identifier
{
    if ([ self.completionHandlerDictionary objectForKey: identifier]) {
        NSLog(@"Error: Got multiple handlers for a single session identifier.  This should not happen.\n");
    }
 
    [ self.completionHandlerDictionary setObject:handler forKey: identifier];
}
 
- (void) callCompletionHandlerForSession: (NSString *)identifier
{
    CompletionHandlerType handler = [self.completionHandlerDictionary objectForKey: identifier];
 
    if (handler) {
        [self.completionHandlerDictionary removeObjectForKey: identifier];
        NSLog(@"Calling completion handler.\n");
 
        handler();
    }
}
#endif
 

  Методы делегата Приложения перечисления 1-9 для фоновых загрузок iOS

- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler
{
    NSURLSessionConfiguration *backgroundConfigObject = [NSURLSessionConfiguration backgroundSessionConfiguration: identifier];
 
    NSURLSession *backgroundSession = [NSURLSession sessionWithConfiguration: backgroundConfigObject delegate: self.mySessionDelegate delegateQueue: [NSOperationQueue mainQueue]];
 
    NSLog(@"Rejoining session %@\n", identifier);
 
    [ self.mySessionDelegate addCompletionHandler: completionHandler forSession: identifier];
}