iOS
NSURLSession
Поиск…
замечания
Класс NSURLSession и связанные классы предоставляют API для загрузки содержимого. Этот API предоставляет богатый набор методов делегирования для поддержки аутентификации и дает вашему приложению возможность выполнять фоновое скачивание, когда ваше приложение не работает, или в iOS, пока ваше приложение приостановлено.
На высоком уровне NSURLSession основан на концепции сеансов и задач. Задача представляет собой одиночный запрос для одного URL-адреса (или одной загрузки для одного URL-адреса). Сессия представляет собой группу связанных запросов.
Операционная система предоставляет один предшествующий сеанс - общий сеанс, который в основном работает как NSURLConnection. Кроме того, вы можете создавать свои собственные сеансы в своем приложении по мере необходимости.
Различные приложения используют сеансы по-разному. Многие приложения создают один сеанс при запуске и просто повторно используют его. Другие приложения получают возможность отменить группу связанных задач (например, веб-браузер, отменяющий все невыполненные запросы при закрытии вкладки) и, таким образом, создать один сеанс для хранения каждой группы связанных запросов.
Первым шагом при использовании NSURLSession является создание объекта конфигурации сеанса. Объект (обычно) многократного использования содержит различные параметры сеанса, которые вы можете настроить для своих конкретных потребностей, таких как максимальный параллелизм, дополнительные заголовки для отправки с каждым запросом, разрешать ли запросы отправлять по сотовому радио (только для iOS), тайм-ауты, хранилище учетных данных, минимальную версию TLS и даже настройки прокси.
Существует три типа конфигураций сеансов, в зависимости от того, как вы должны вести себя в результате:
- Конфигурации по умолчанию создают сеансы, которые очень похожи на NSURLConnection.
- Фоновые конфигурации создают сеансы, в которых запросы происходят вне процесса, что позволяет продолжить скачивание, даже если приложение больше не работает.
- Эфемерные конфигурации создают сеансы, которые не кэшируют что-либо на диск, не хранят файлы cookie на диске и т. Д. И, следовательно, подходят для поддержки таких вещей, как окна браузера инкогнито.
Когда вы создаете конфигурацию фона, вы должны предоставить идентификатор сеанса, который позволит вам повторно связать фоновый сеанс позже (если ваше приложение выйдет или приостановлено или завершено ОС). У вас не должно быть более одного экземпляра сеанса с тем же идентификатором, который активен в вашем приложении, поэтому, как правило, эти конфигурации не могут использоваться повторно. Все остальные конфигурации сеансов можно использовать повторно, чтобы создать столько сеансов, сколько хотите. Поэтому, если вам нужно создать несколько сеансов с аналогичными настройками, вы можете создать конфигурацию один раз и повторно использовать ее каждый раз, когда вы создаете новый сеанс.
После создания сеанса вы можете создавать задачи в этом сеансе. Существует три типа задач:
- Задачи данных возвращают данные как объект NSData . Они подходят для общего использования, но не поддерживаются в фоновых сеансах.
- Загрузка задач возвращает данные в виде файла на диске. Они подходят для больших запросов или для использования в фоновых сеансах.
- Загрузка задач загружает данные из объекта NSData или из файла на диске. Вы предоставляете объект данных или файл, который предоставляет тело POST. Данные / файл тела, которые вы предоставляете в задаче, переопределяют любые данные тела / файл, предоставленные в объекте NSURLRequest (если применимо).
Каждый из этих типов позволяет получить данные ответа несколькими различными способами - либо с помощью обратных вызовов на основе блоков, либо путем предоставления делегата на сеансе и реализации методов делегирования.
Кроме того, NSURLSession позволяет вам предоставлять методы делегирования для обработки аутентификации, выполнения пользовательской обработки сертификатов TLS (как для сертификатов клиентов, так и для проверки сервера), изменения поведения кэширования и т. Д.
Простой запрос GET
// define url
let url = NSURL(string: "https://urlToGet.com")
//create a task to get data from a url
let task = NSURLSession.sharedSession().dataTaskWithURL(url!)
{
/*inside this block, we have access to NSData *data, NSURLResponse *response, and NSError *error returned by the dataTaskWithURL() function*/
(data, response, error) in
if error == nil
{
// Data from the request can be manipulated here
}
else
{
// An error occurred
}
}
//make the request
task.resume()
Цель-C Создать задачу сеанса и данных
NSURL *url = [NSURL URLWithString:@"http://www.example.com/"];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// Configure the session here.
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
[[session dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
// The response object contains the metadata (HTTP headers, status code)
// The data object contains the response body
// The error object contains any client-side errors (e.g. connection
// failures) and, in some cases, may report server-side errors.
// In general, however, you should detect server-side errors by
// checking the HTTP status code in the response object.
}] resume];
Настройка конфигурации фона
Чтобы создать фоновый сеанс
// Swift:
let mySessionID = "com.example.bgSession"
let bgSessionConfig = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(mySessionID)
let session = NSURLSession(configuration: bgSessionConfig)
// add tasks here
// Objective-C:
NSString *mySessionID = @"com.example.bgSession";
NSURLSessionConfiguration *configuration =
[NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier: mySessionID];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration
delegate:self]
Кроме того, в iOS вы должны настроить поддержку для обработки возобновления приложения. При вызове приложения вашего приложения application:handleEventsForBackgroundURLSession:completionHandler:
метод (Objective-C) или application(_:handleEventsForBackgroundURLSession:completionHandler:)
(Swift) вызывается, это означает, что ваше приложение было перезаписано в фоновом режиме для обработки активности в сеансе.
В этом методе вы должны создать новый сеанс с предоставленным идентификатором и настроить его с помощью делегата для обработки событий так же, как обычно на переднем плане. Кроме того, вы должны сохранить предоставленный обработчик завершения в словаре, используя сеанс в качестве ключа.
Когда URLSessionDidFinishEventsForBackgroundURLSession:
делегата URLSessionDidFinishEventsForBackgroundURLSession:
(Obj-C) / URLSessionDidFinishEventsForBackgroundURLSession
(Swift) URLSessionDidFinishEventsForBackgroundURLSession
чтобы сообщить вам, что больше не нужно обрабатывать события, ваше приложение должно искать обработчик завершения для этого сеанса, удалить сеанс из словаря и вызовите обработчик завершения, тем самым сообщив операционной системе, что у вас больше нет какой-либо выдающейся обработки, связанной с сеансом. (Если вы по-прежнему делаете что-то по какой-то причине, когда получаете этот вызов делегата, подождите, пока не закончите.) Как только вы вызываете этот метод, фоновый сеанс немедленно становится недействительным.
Если ваше приложение затем получает application:application:didFinishLaunchingWithOptions:
call (вероятно, указывая на то, что пользователь, application:application:didFinishLaunchingWithOptions:
вашего приложения, когда вы были заняты обработкой фоновых событий), безопасно создать фоновый сеанс с тем же идентификатором, поскольку старый сеанс с этим идентификатор больше не существует.
Если вам интересно узнать подробности, на высоком уровне, когда вы создаете фоновый сеанс, вы делаете две вещи:
- Создание сеанса во внешнем демоне (nsurlsessiond) для обработки загрузок
- Создание сеанса в вашем приложении, который ведет переговоры с этим внешним демоном через NSXPC
Как правило, опасно создавать два сеанса с одним и тем же идентификатором сеанса в одном запуске приложения, потому что оба они пытаются поговорить с тем же сеансом в фоновом демоне. Вот почему официальная документация говорит, что никогда не создавать несколько сеансов с тем же идентификатором. Однако, если первый сеанс был временным сеансом, созданным как часть вызова handleEventsForBackgroundURLSession
, связь между недействительным сеансом in-app и сеансом в фоновом демонах больше не существует.
Отправка запроса POST с помощью аргументов с использованием NSURLSession в Objective-C
Существует два распространенных способа кодирования тела запроса POST: кодировка URL (application / x-www-form-urlencoded) и данные формы (multipart / form-data). Большая часть кода аналогична, но способ построения данных тела отличается.
Отправка запроса с использованием URL-кодирования
Будь то у вас есть сервер для вашего небольшого приложения или ваша работа в команде с полным инженером-коннектором, вы захотите поговорить с этим сервером в какой-то момент с вашим приложением iOS.
В следующем коде мы будем составлять строку аргументов, которые сценарий целевого сервера будет использовать, чтобы сделать что-то, что изменяется в зависимости от вашего случая. Например, мы можем отправить строку:
Имя = Брендон и пароль = ABCDE
На сервер, когда пользователь подписывается на ваше приложение, поэтому сервер может хранить эту информацию в базе данных.
Давайте начнем. Вы захотите создать запрос POST NSURLSession со следующим кодом.
// Create the configuration, which is necessary so we can cancel cacheing amongst other things.
NSURLSessionConfiguration * defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
// Disables cacheing
defaultConfigObject.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
NSURLSession * defaultSession = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSString * scriptURL = [NSString stringWithFormat:@"https://server.io/api/script.php"];
//Converts the URL string to a URL usable by NSURLSession
NSMutableURLRequest * urlRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:scriptURL]];
NSString * postDataString = [NSString stringWithFormat:@"name=%@&password=%@", [self nameString], [self URLEncode:passwordString]];
[urlRequest setHTTPMethod:@"POST"];
[urlRequest setHTTPBody:[postDataString dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask * dataTask = [defaultSession dataTaskWithRequest:urlRequest];
// Fire the data task.
[dataTask resume];
Вышеприведенный код только что создал и отправил запрос POST на сервер. Помните, что URL-адрес сценария и строка данных POST изменяются в зависимости от вашей ситуации. Если вы читаете это, вы будете знать, что заполнить эти переменные.
Вам также потребуется добавить небольшой метод, который кодирует URL:
- (NSString *)URLEncode:(NSString *)originalString encoding:(NSStringEncoding)encoding
{
return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(
kCFAllocatorDefault,
(__bridge CFStringRef)originalString,
NULL,
CFSTR(":/?#[]@!$&'()*+,;="),
CFStringConvertNSStringEncodingToEncoding(encoding));
}
Таким образом, когда сервер завершит обработку этих данных, он отправит возврат в ваше приложение iOS. Поэтому нам нужно обработать это возвращение, но как?
Мы используем программирование, основанное на событиях, и используем методы делегатов NSURLSession. Это означает, что когда сервер отправляет ответ, эти методы начнут запускать. Следующие 5 методов - это те, которые будут запускаться во всем запросе ENTIRE, каждый раз, когда он делается:
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler;
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data;
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error;
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler;
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler;
Ниже вы увидите вышеупомянутые методы, используемые в контексте. Каждая из их целей довольно понятна благодаря Apple, но я все равно прокомментировал их использование:
// Response handling delegates
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler{
// Handler allows us to receive and parse responses from the server
completionHandler(NSURLSessionResponseAllow);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
// Parse the JSON that came in into an NSDictionary
NSError * err = nil;
NSDictionary * jsonDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&err];
if (!err){ // if no error occurred, parse the array of objects as normal
// Parse the JSON dictionary 'jsonDict' here
}else{ // an error occurred so we need to let the user know
// Handle your error here
}
}
// Error handling delegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
if(error == nil){
// Download from API was successful
NSLog(@"Data Network Request Did Complete Successfully.");
}else{
// Describes and logs the error preventing us from receiving a response
NSLog(@"Error: %@", [error userInfo]);
// Handle network error, letting the user know what happened.
}
}
// When the session receives a challenge (because of iOS 9 App Transport Security blocking non-valid SSL certificates) we use the following methods to tell NSURLSession "Chill out, I can trust me".
// The following is not necessary unless your server is using HTTP, not HTTPS
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler{
if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){
if([challenge.protectionSpace.host isEqualToString:@"DomainNameOfServer.io"]){
NSURLCredential * credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler{
if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){
if([challenge.protectionSpace.host isEqualToString:@"DomainNameOfServer.io"]){
NSURLCredential * credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}
}
}
Итак, это все! Это код, который вам нужен для отправки, получения и анализа запроса API в iOS 9! Хорошо ... это было довольно много кода. Но если они реализованы правильно, как указано выше, это будет безотказно! Обязательно всегда обращайтесь с ошибками, которые были предложены выше.
Отправка запроса с использованием формы
Кодировка URL - это широко совместимый способ кодирования произвольных данных. Тем не менее, это относительно неэффективно для загрузки двоичных данных (например, фотографий), потому что каждый байт без ASCII превращается в трехсимвольный код. Он также не поддерживает вложения файлов, поэтому вам придется передавать имена файлов и данные файла в виде отдельных полей.
Предположим, мы хотим загрузить фотографию таким образом, чтобы она была эффективной и фактически выглядела как файл на стороне сервера. Один из способов сделать это - использовать кодировку формы. Для этого отредактируйте код, создающий NSURLSession, следующим образом:
UIImage * imgToSend;
// 2nd parameter of UIImageJPEGRepresentation represents compression quality. 0 being most compressed, 1 being the least
// Using 0.4 likely stops us hitting the servers upload limit and costs us less server space
NSData * imageData = UIImageJPEGRepresentation(imgToSend, 0.4f);
// Alternatively, if the photo is on disk, you can retrieve it with
// [NSData dataWithContentsOfURL:...]
// Set up the body of the POST request.
// This boundary serves as a separator between one form field and the next.
// It must not appear anywhere within the actual data that you intend to
// upload.
NSString * boundary = @"---------------------------14737809831466499882746641449";
// Body of the POST method
NSMutableData * body = [NSMutableData data];
// The body must start with the boundary preceded by two hyphens, followed
// by a carriage return and newline pair.
//
// Notice that we prepend two additional hyphens to the boundary when
// we actually use it as part of the body data.
//
[body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
// This is followed by a series of headers for the first field and then
// TWO CR-LF pairs.
[body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"tag_name\"\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
// Next is the actual data for that field (called "tag_name") followed by
// a CR-LF pair, a boundary, and another CR-LF pair.
[body appendData:[strippedCompanyName dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
// Encode the filename and image data as the "userfile" CGI parameter.
// This is similar to the previous field, except that it is being sent
// as an actual file attachment rather than a blob of data, which means
// it has both a filename and the actual file contents.
//
// IMPORTANT: The filename MUST be plain ASCII (and if encoded like this,
// must not include quotation marks in the filename).
//
NSString * picFileName = [NSString stringWithFormat:@"photoName"];
NSString * appendDataString = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"userfile\"; filename=\"%@.jpg\"\r\n", picFileName];
[body appendData:[appendDataString dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[@"Content-Type: application/octet-stream\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[NSData dataWithData:imageData]];
// Close the request body with one last boundary with two
// additional hyphens prepended **and** two additional hyphens appended.
[body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
// Create the session
// We can use the delegate to track upload progress and disable cacheing
NSURLSessionConfiguration * defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
defaultConfigObject.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
NSURLSession * defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];
// Data uploading task.
NSURL * url = [NSURL URLWithString:@"https://server.io/api/script.php"];
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url];
NSString * contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary];
[request addValue:contentType forHTTPHeaderField:@"Content-Type"];
request.HTTPMethod = @"POST";
request.HTTPBody = body;
NSURLSessionDataTask * uploadTask = [defaultSession dataTaskWithRequest:request];
[uploadTask resume];
Это создает и запускает запрос NSURLSession так же, как и раньше, и в результате методы делегата будут вести себя точно так же. Убедитесь, что сценарий, на который отправляется изображение (расположен на url в url
переменной), ожидает изображение и может его правильно проанализировать.