Асинхронная загрузка с NSURLSession не будет работать, но синхронная NSURLConnection работает
Редактировать: мне нужно загрузить файл асинхронно с iPhone в процесс на стороне сервера Python. Я хотел бы сделать запрос асинхронно, чтобы я мог отображать занятую анимацию, пока она работает.
Запрос должен включать имя пользователя, пароль и файл как "multipart/form-data".
Я могу заставить его работать синхронно, используя NSURLConnection с кодом, похожим на этот:
-(void) uploadDatabase{
Database *databasePath = [[Database alloc] init];
NSString *targetPath = [databasePath getPathToDatabaseInDirectory];
NSData *dbData = [NSData dataWithContentsOfFile:targetPath];
NSString *url = @"http://mydomain.com/api/upload/";
//NSString *username = [[NSUserDefaults standardUserDefaults] stringForKey:USERNAME];
NSString *username = @"user";
NSString *password = @"pass";
NSMutableURLRequest *request = [self createRequestForUrl:url withUsername:username andPassword:password andData:dbData];
NSURLResponse *response;
NSError *error;
NSData *result = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSString *stringResult = [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding];
NSLog(@"**server info %@", stringResult);}
// Запрос строительства
-(NSMutableURLRequest*) createRequestForUrl: (NSString*)urlString withUsername:(NSString*)username andPassword:(NSString*)password andData:(NSData*)dbData
{NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:60.0];
[request setHTTPMethod:@"POST"];
NSString *boundary = @"BOUNDARY_STRING";
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
[request addValue:contentType forHTTPHeaderField:@"Content-Type"];
NSMutableData *body = [NSMutableData data];
if(dbData != NULL)
{
//only send these methods when transferring data as well as username and password
[body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"file\"; filename=\"dbfile\"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[@"Content-Type: application/octet-stream\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[NSData dataWithData:dbData]];
}
[body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"username\"\r\n\r\n%@", username] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"password\"\r\n\r\n%@", password] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[request setHTTPBody:body];
return request;}
Тем не менее, когда я пытаюсь сделать это асинхронно с помощью NSURLSession, это не работает должным образом. Код с NSURLSession выглядит следующим образом:
-(void)uploadDatabase{
Database *databasePath = [[Database alloc] init];
NSString *targetPath = [databasePath getPathToDatabaseInDirectory];
NSURL *phonedbURL = [NSURL URLWithString:targetPath];
NSString *url = @"http://mydomain.com/api/upload/";
NSString *username = @"user";
NSString *password = @"pass";
NSMutableURLRequest *request = [self createRequestForUrl:url withUsername:username andPassword:password andData:NULL];
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
self.uploadSession = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate:self delegateQueue:Nil];
NSLog(@"the url = %@",url);
NSURLSessionUploadTask *uploadTask = [self.uploadSession uploadTaskWithRequest:request fromFile:phonedbURL];
[uploadTask resume];}
Я изо всех сил пытаюсь увидеть, что я делаю по-другому, хотя, как кажется, это должно работать.
Является ли использование NSURLSession правильным способом выполнения асинхронных запросов? и я новичок в NSURLSession, так что мне нужно изменить NSURLMutableRequest для запросов NSURLSession, а не NSURLConnection?
Заранее благодарю за любую помощь!
1 ответ
Вы правы, что если вы просто хотите сделать свой запрос асинхронным, вы должны удалиться sendSynchronousRequest
, Хотя мы бы однажды рекомендовали sendAsynchronousRequest
, эффективная iOS 9, NSURLConnection
формально не рекомендуется, и следует отдать предпочтение NSURLSession
,
Как только вы начнете использовать NSURLSession
Вы можете быть привлечены к этому. Например, можно использовать [NSURLSessionConfiguration backgroundSessionConfiguration:]
, а затем загружать прогресс даже после того, как приложение перешло в фоновый режим. (Вы должны написать несколько методов делегатов, поэтому для простоты я остановился на простой загрузке на переднем плане ниже.) Это всего лишь вопрос ваших бизнес-требований, компенсирующих новые NSURLSession
функции по сравнению с ограничением iOS 7+, которое оно влечет за собой
Кстати, любой разговор о сетевых запросах в iOS/MacOS, вероятно, является неполным без ссылки на AFNetworking. Это значительно упрощает создание этих многочастных запросов и, безусловно, заслуживает расследования. У них есть NSURLSession
поддержка тоже (но я не использовал их обертки сессий, поэтому не могу говорить с ним). Но AFNetworking, несомненно, заслуживает вашего внимания. Вы можете наслаждаться богатством API на основе делегатов (например, обновления прогресса, отменяемые запросы, зависимости между операциями и т. Д.), Предлагая гораздо больший контроль, чем при использовании удобных методов (таких как sendSynchronousRequest
), но не перетаскивая вас через сорняки самих методов делегата.
Независимо от того, если вы действительно заинтересованы в том, как делать загрузки с NSURLSession
, увидеть ниже.
Если вы хотите загрузить через NSURLSession
Это небольшой сдвиг в мышлении, а именно разделение конфигурации запроса (и его заголовков) на NSMutableURLRequest
от создания тела запроса (который вы сейчас указываете во время создания NSURLSessionUploadTask
). Тело запроса, который вы сейчас указываете как часть задачи загрузки, может быть NSData
, файл или поток (я использую NSData
ниже, потому что мы создаем многочастный запрос):
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
NSString *boundary = [self boundaryString];
[request addValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary] forHTTPHeaderField:@"Content-Type"];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
NSData *fileData = [NSData dataWithContentsOfFile:path];
NSData *data = [self createBodyWithBoundary:boundary username:@"rob" password:@"password" data:fileData filename:[path lastPathComponent]];
NSURLSessionUploadTask *task = [session uploadTaskWithRequest:request fromData:data completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSAssert(!error, @"%s: uploadTaskWithRequest error: %@", __FUNCTION__, error);
// parse and interpret the response `NSData` however is appropriate for your app
}];
[task resume];
И создание NSData
отправка очень похожа на ваш существующий код:
- (NSData *) createBodyWithBoundary:(NSString *)boundary username:(NSString*)username password:(NSString*)password data:(NSData*)data filename:(NSString *)filename
{
NSMutableData *body = [NSMutableData data];
if (data) {
//only send these methods when transferring data as well as username and password
[body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"file\"; filename=\"%@\"\r\n", filename] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", [self mimeTypeForPath:filename]] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:data];
[body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
}
[body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"username\"\r\n\r\n%@\r\n", username] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"password\"\r\n\r\n%@\r\n", password] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
return body;
}
Вы жестко запрограммировали границу и тип MIME, что нормально, но в приведенном выше примере используются следующие методы:
- (NSString *)boundaryString
{
NSString *uuidStr = [[NSUUID UUID] UUIDString];
// If you need to support iOS versions prior to 6, you can use
// Core Foundation UUID functions to generate boundary string
//
// adapted from http://developer.apple.com/library/ios/#samplecode/SimpleURLConnections
//
// NSString *uuidStr;
//
// CFUUIDRef uuid = CFUUIDCreate(NULL);
// assert(uuid != NULL);
//
// NSString *uuidStr = CFBridgingRelease(CFUUIDCreateString(NULL, uuid));
// assert(uuidStr != NULL);
//
// CFRelease(uuid);
return [NSString stringWithFormat:@"Boundary-%@", uuidStr];
}
- (NSString *)mimeTypeForPath:(NSString *)path
{
// get a mime type for an extension using MobileCoreServices.framework
CFStringRef extension = (__bridge CFStringRef)[path pathExtension];
CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, extension, NULL);
assert(UTI != NULL);
NSString *mimetype = CFBridgingRelease(UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType));
assert(mimetype != NULL);
CFRelease(UTI);
return mimetype;
}