简体   繁体   English

使用NSURLSession的异步上载不起作用,但同步NSURLConnection会起作用

[英]asynchronous upload with NSURLSession will not work but synchronous NSURLConnection does

edit: I need to upload a file asynchronously from an iPhone to a Python server-side process. 编辑:我需要从iPhone异步上传文件到Python服务器端进程。 I'd like to do the request asynchronously so that I can display a busy animation while it's working. 我想异步执行请求,以便在工作时显示繁忙的动画。

The request needs to include the username, password and file as 'multipart/form-data'. 请求需要包含用户名,密码和文件作为“multipart / form-data”。

I can get it working synchronously using NSURLConnection with the code looking like this:: 我可以使用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);}

// Request construction //请求构建

    -(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;}

However, when I try to do this asynchronously using NSURLSession it doesn't seem to work properly. 但是,当我尝试使用NSURLSession异步执行此操作时,它似乎无法正常工作。 The code with NSURLSession looks like this: 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];}

I'm struggling to see what I'm doing differently though as it seems this should work. 我正在努力看到我正在以不同的方式做什么,尽管看起来这应该有效。

Is using NSURLSession the right way to do asynchronous requests? 是否正确使用NSURLSession进行异步请求? and I'm new to NSURLSession so do I have to change my NSURLMutableRequest for NSURLSession requests rather than NSURLConnection? 而且我是NSURLSession的新手,所以我必须为NSURLSession请求而不是NSURLConnection更改我的NSURLMutableRequest吗?

Thanks in advance for any help! 在此先感谢您的帮助!

You are correct, that if you just want to make your request asynchronous, you should retire sendSynchronousRequest . 你是对的,如果你只想让你的请求异步,你应该退出sendSynchronousRequest While we once would have recommended sendAsynchronousRequest , effective iOS 9, NSURLConnection is formally deprecated and one should favor NSURLSession . 虽然我们曾经推荐过sendAsynchronousRequest ,有效的iOS 9, NSURLConnection正式弃用,并且应该支持NSURLSession

Once you start using NSURLSession , you might find yourself drawn to it. 一旦开始使用NSURLSession ,您可能会发现自己已经被它吸引了。 For example, one can use a [NSURLSessionConfiguration backgroundSessionConfiguration:] , then have uploads progress even after the app has gone into background. 例如,可以使用[NSURLSessionConfiguration backgroundSessionConfiguration:] ,然后即使应用程序进入后台也可以上传进度。 (You have to write a few delegate methods, so for simplicity's sake, I've stayed with a simple foreground upload below.) It's just a question of your business requirements, offsetting the new NSURLSession features versus the iOS 7+ limitation it entails. (你必须编写一些委托方法,所以为了简单起见,我在下面继续使用简单的前台上传。)这只是你的业务需求的问题,抵消了新的NSURLSession功能与它所需的iOS 7+限制。

By the way, any conversation about network requests in iOS/MacOS is probably incomplete without a reference to AFNetworking . 顺便说一下,如果没有AFNetworking的引用,任何关于iOS / MacOS中网络请求的对话可能都是不完整的。 It greatly simplifies creation of these multipart requests and definitely merits investigation. 它极大地简化了这些多部分请求的创建,绝对值得调查。 They have NSURLSession support, too (but I haven't used their session wrappers, so can't speak to it). 他们也有NSURLSession支持(但我没有使用他们的会话包装器,所以不能说它)。 But AFNetworking is undoubtedly worthy of your consideration. 但AFNetworking毫无疑问值得您考虑。 You can enjoy some of the richness of the delegate-base API (eg progress updates, cancelable requests, dependencies between operations, etc.), offering far greater control that available with convenience methods (like sendSynchronousRequest ), but without dragging you through the weeds of the delegate methods themselves. 您可以享受基于委托的API的丰富功能(例如,进度更新,可取消请求,操作之间的依赖关系等),提供更好的控制,使用方便的方法(如sendSynchronousRequest ),但不会拖过你的杂草委托方法本身。

Regardless, if you're really interested in how to do uploads with NSURLSession , see below. 无论如何,如果您真的对如何使用NSURLSession进行上传NSURLSession ,请参阅下文。


If you want to upload via NSURLSession , it is a slight shift in thinking, namely, separating the configuration of the request (and its headers) in the NSMutableURLRequest from the creation of the the body of the request (which you now specify during the instantiation of the NSURLSessionUploadTask ). 如果你想通过NSURLSession上传,那么思考就会略有转变,即将NSMutableURLRequest中的请求(及其标题)的配置与请求主体的创建(您现在在实例化时指定)分开NSURLSessionUploadTask )。 The body of the request that you now specify as part of the upload task can be either a NSData , a file, or a stream (I use a NSData below, because we're building a multipart request): 您现在指定为上载任务的一部分的请求正文可以是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];

And the creation of the NSData being sent is much like your existing code: 并且发送的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;
}

You hardcoded the boundary and the mime type, which is fine, but the above happens to use the following methods: 你硬编码边界和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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM