Asynchronous Upload with Nsurlsession Will Not Work But Synchronous Nsurlconnection Does

asynchronous upload with NSURLSession will not work but synchronous NSURLConnection does

You are correct, that if you just want to make your request asynchronous, you should retire sendSynchronousRequest. While we once would have recommended sendAsynchronousRequest, effective iOS 9, NSURLConnection is formally deprecated and one should favor NSURLSession.

Once you start using NSURLSession, you might find yourself drawn to it. For example, one can use a [NSURLSessionConfiguration backgroundSessionConfiguration:], then have uploads progress even after the app has gone into background. (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.

By the way, any conversation about network requests in iOS/MacOS is probably incomplete without a reference to AFNetworking. 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). But AFNetworking is undoubtedly worthy of your consideration. You can enjoy some of the richness of the delegate-base API (e.g. 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.

Regardless, if you're really interested in how to do uploads with NSURLSession, see below.


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). 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):

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 *) 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:

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

Best practice to replace a synchronous NSURLConnection with NSURLSession

This answer is not supposed to be best practice. It was just practical for me.

Faced with the situation when a bunch of synchronous requests is executed in background and the order of execution matters I've ended up using the following:

SyncRequestSender.h

#import <Foundation/Foundation.h>

@interface SyncRequestSender : NSObject

+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request
returningResponse:(NSURLResponse **)response
error:(NSError **)error;

@end

SyncRequestSender.m

#import "SyncRequestSender.h"

@implementation SyncRequestSender

+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request
returningResponse:(NSURLResponse **)response
error:(NSError **)error
{
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);

NSError __block *err = NULL;
NSData __block *data;
NSURLResponse __block *resp;

[[[NSURLSession sharedSession] dataTaskWithRequest:request
completionHandler:^(NSData* _data, NSURLResponse* _response, NSError* _error) {
resp = _response;
err = _error;
data = _data;
dispatch_group_leave(group);

}] resume];

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

if (response)
{
*response = resp;
}
if (error)
{
*error = err;
}

return data;
}

@end

Why does NSURLSession uploadTaskWithRequest:fromData: fail to upload to php server?

I had an issue with a semi-colon here->

"name=\"userfile\" ; filename=\"%@\"\r\n"" 

So it was a malformed body basically.

NSURLSession with NSBlockOperation and queues

The harshest criticisms of synchronous network requests are reserved for those who do it from the main queue (as we know that one should never block the main queue). But you're doing it on your own background queue, which addresses the most egregious problem with synchronous requests. But you're losing some wonderful features that asynchronous techniques provide (e.g. cancelation of requests, if needed).

I'll answer your question (how to make NSURLSessionDataTask behave synchronously) below, but I'd really encourage you to embrace the asynchronous patterns rather than fighting them. I'd suggest refactoring your code to use asynchronous patterns. Specifically, if one task is dependent upon another, simply put the initiation of the dependent task in the completion handler of the prior task.

If you have problems in that conversion, then post another Stack Overflow question, showing us what you tried, and we can try to help you out.


If you want to make an asynchronous operation synchronous, a common pattern is to use a dispatch semaphore so your thread that initiated the asynchronous process can wait for a signal from the completion block of the asynchronous operation before continuing. Never do this from the main queue, but if you're doing this from some background queue, it can be a useful pattern.

You can create a semaphore with:

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

You can then have the completion block of the asynchronous process signal the semaphore with:

dispatch_semaphore_signal(semaphore);

And you can then have the code outside of the completion block (but still on the background queue, not the main queue) wait for that signal:

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

So, with NSURLSessionDataTask, putting that all together, that might look like:

[queue addOperationWithBlock:^{

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

NSURLSession *session = [NSURLSession sharedSession]; // or create your own session with your own NSURLSessionConfiguration
NSURLSessionTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (data) {
// do whatever you want with the data here
} else {
NSLog(@"error = %@", error);
}

dispatch_semaphore_signal(semaphore);
}];
[task resume];

// but have the thread wait until the task is done

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

// now carry on with other stuff contingent upon what you did above
]);

With NSURLConnection (now deprecated), you have to jump through some hoops to initiate requests from a background queue, but NSURLSession handles it gracefully.


Having said that, using block operations like this means that the operations won't respond to cancellation events (while they're running, at least). So I generally eschew this semaphore technique with block operations and just wrap the data tasks in asynchronous NSOperation subclass. Then you enjoy the benefits of operations, but you can make them cancelable, too. It's more work, but a much better pattern.

For example:

//
// DataTaskOperation.h
//
// Created by Robert Ryan on 12/12/15.
// Copyright © 2015 Robert Ryan. All rights reserved.
//

@import Foundation;
#import "AsynchronousOperation.h"

NS_ASSUME_NONNULL_BEGIN

@interface DataTaskOperation : AsynchronousOperation

/// Creates a operation that retrieves the contents of a URL based on the specified URL request object, and calls a handler upon completion.
///
/// @param request A NSURLRequest object that provides the URL, cache policy, request type, body data or body stream, and so on.
/// @param dataTaskCompletionHandler The completion handler to call when the load request is complete. This handler is executed on the delegate queue. This completion handler takes the following parameters:
///
/// @returns The new session data operation.

- (instancetype)initWithRequest:(NSURLRequest *)request dataTaskCompletionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))dataTaskCompletionHandler;

/// Creates a operation that retrieves the contents of a URL based on the specified URL request object, and calls a handler upon completion.
///
/// @param url A NSURL object that provides the URL, cache policy, request type, body data or body stream, and so on.
/// @param dataTaskCompletionHandler The completion handler to call when the load request is complete. This handler is executed on the delegate queue. This completion handler takes the following parameters:
///
/// @returns The new session data operation.

- (instancetype)initWithURL:(NSURL *)url dataTaskCompletionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))dataTaskCompletionHandler;

@end

NS_ASSUME_NONNULL_END

and

//
// DataTaskOperation.m
//
// Created by Robert Ryan on 12/12/15.
// Copyright © 2015 Robert Ryan. All rights reserved.
//

#import "DataTaskOperation.h"

@interface DataTaskOperation ()

@property (nonatomic, strong) NSURLRequest *request;
@property (nonatomic, weak) NSURLSessionTask *task;
@property (nonatomic, copy) void (^dataTaskCompletionHandler)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error);

@end

@implementation DataTaskOperation

- (instancetype)initWithRequest:(NSURLRequest *)request dataTaskCompletionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))dataTaskCompletionHandler {
self = [super init];
if (self) {
self.request = request;
self.dataTaskCompletionHandler = dataTaskCompletionHandler;
}
return self;
}

- (instancetype)initWithURL:(NSURL *)url dataTaskCompletionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))dataTaskCompletionHandler {
NSURLRequest *request = [NSURLRequest requestWithURL:url];
return [self initWithRequest:request dataTaskCompletionHandler:dataTaskCompletionHandler];
}

- (void)main {
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:self.request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
self.dataTaskCompletionHandler(data, response, error);
[self completeOperation];
}];

[task resume];
self.task = task;
}

- (void)completeOperation {
self.dataTaskCompletionHandler = nil;
[super completeOperation];
}

- (void)cancel {
[self.task cancel];
[super cancel];
}

@end

Where:

//
// AsynchronousOperation.h
//

@import Foundation;

@interface AsynchronousOperation : NSOperation

/// Complete the asynchronous operation.
///
/// This also triggers the necessary KVO to support asynchronous operations.

- (void)completeOperation;

@end

And

//
// AsynchronousOperation.m
//

#import "AsynchronousOperation.h"

@interface AsynchronousOperation ()

@property (nonatomic, getter = isFinished, readwrite) BOOL finished;
@property (nonatomic, getter = isExecuting, readwrite) BOOL executing;

@end

@implementation AsynchronousOperation

@synthesize finished = _finished;
@synthesize executing = _executing;

- (instancetype)init {
self = [super init];
if (self) {
_finished = NO;
_executing = NO;
}
return self;
}

- (void)start {
if ([self isCancelled]) {
self.finished = YES;
return;
}

self.executing = YES;

[self main];
}

- (void)completeOperation {
self.executing = NO;
self.finished = YES;
}

#pragma mark - NSOperation methods

- (BOOL)isAsynchronous {
return YES;
}

- (BOOL)isExecuting {
@synchronized(self) {
return _executing;
}
}

- (BOOL)isFinished {
@synchronized(self) {
return _finished;
}
}

- (void)setExecuting:(BOOL)executing {
@synchronized(self) {
if (_executing != executing) {
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
}
}

- (void)setFinished:(BOOL)finished {
@synchronized(self) {
if (_finished != finished) {
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
}
}

@end

Exact Difference between NSURLConnection and NSUrlSession?

NSURLConnection is a much older API, introduced in OS X 10.2.

NSURLSession was introduced in OS X 10.9, and has a more modern (task- and block-based) API. It is the successor to NSURLConnection, and you should use it in all new projects.

This objc.io post has more information about the two.

Synchronous fetching data NSOperationQueue or NSURLSession

you can use NSURLSession tasks. first call first api method and you will get response in completion handler(block). now store it in any public property (as you want to use it again) within completion handler. From that completion handler call second api method and from the completion handler of that second method, call third api method by passing response and using public property which have first api method's object stored.

Completion handler or blocks getting called only when response is arrived so by this way you can manage your api calls.

Hope this will help :)

Replacing NSURLConnection with NSURLSession

Update:

NSURLConnection is deprecated effective Mac OS 10.11 and iOS 9. So, at this point NSURLSession should be used in lieu of NSURLConnection. As the NSURLConnection.h header says:

Deprecated: The NSURLConnection class should no longer be used. NSURLSession is the replacement for NSURLConnection.

My original answer is below.


The answer depends upon whether you need the richness of the various NSURLSession delegate methods or not. If you're ok using the completion block renditions (i.e. no progress callbacks, no streaming, etc.), the conversion from NSURLConnection to NSURLSession is pretty trivial. Just put your NSURLSession instance in your NetworkManager class, and then wrap the delegate based NSURLSessionTask instances in a concurrent NSOperation subclass and you're done. Just adopt the standard asynchronous/concurrent NSOperation subclass pattern.

If you're using the delegate-based renditions of NSURLSession, it's a whole different kettle of fish. The main hassle is that the various NSURLSessionTask delegate methods are called on the session delegate rather than a task delegate object. At first blush, this might sound like it's a trivial issue, but if your operation instances have unique completion/progress blocks, for example, you're stuck with the hassle of how to have the session object map these delegate method callbacks to the individual original request operation instances.

To tackle this, you have to maintain a mapping of task identifiers to your NSOperation subclass objects. You can then implement these NSURLSessionTask (including task, download task, and upload task) delegate methods at the respective NSOperation subclass. The NSURLSession network manager class can then, when it receives a NSURLSessionTask delegate call, use the task identifier to identify the appropriate NSOperation instance, and then call the appropriate delegate method there.

Finally, if you intend to handle background NSURLSession instances, life gets even more difficult (because background tasks will continue even after your app has terminated and all of its objects have been discarded). Background sessions simply don't lend themselves to an NSOperation-based approach.

Bottom line, this is trivial if you only need the completion-block NSURLSession methods, but it's a bit of a hassle if you need the delegate-based rendition.



Related Topics



Leave a reply



Submit