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 forNSURLConnection
.
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
How to Find Serial Number, Imei Number Using iOS Sdk
Uisearchbar's Cancel and Clear Buttons Not Working in iOS 7
Are Extensions Bad for Performance in Swift
How to Add Text, Shape and Signature in Photo Markup with Pencil Kit
Nsstrikethroughstyleattributename , How to Strike Out the String in iOS 10.3
Ios: Update Launch Screen Dynamically
Codesign Returned Errsecinternalcomponent in High Sierra
Swift - "Use of Unresolved Identifier"
Uiimagepickercontroller in Swift 3
Skspritenode Position Changes to 0,0 for No Reason
Swiftui: Animation Inside Navigationview
Swift - Get Date from Day of Year
Customizing Uilocalnotification's Alert
I Added a Localization in Xcode, Removed It and Now My Storyboard Is Gone
Uitableview Strange Layout Behavior Changes on Scroll
How to Show Different Alerts Based on a Condition After Clicking a Button in Swiftui