NSUrlConnection synchronous request without following redirections - ios

Problem
I need to execute a synchronous HTTP request, without following redirects, preferably without using instance variables, since this is to be incorporated into the j2objc project.
What have I tried
I have tried using NSURLConnection sendSynchronousRequest, which unfortunately cannot easily be told not to follow redirects.
Background
Before telling me that I should not use synchronous requests, please bear in mind that this code is for emulating Java's HttpUrlConnection, which is inherently synchronous in behavior, for the j2objc project. The implementation of IosHttpUrlConnections' native makeSynchronousRequest currently always follows redirects. It should respect the HttpUrlConnection.instanceFollowRedirects field.
Further research conducted
When using NSUrlConnection in asynchronous mode, a delegate method is called, which allows for enabling/disabling redirects. However, I need synchronous operation.
This answer on NSUrlconnection: How to wait for completion shows how to implement sendSynchronousRequest using an async request. However, I haven't been able to modify it to use a delegate, and thus haven't been able to not follow redirects.
I hope you can help me

You can use a NSURLSession with a semaphore, create like this:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil];
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);
And you have to implement the following method of NSURLSessionTaskDelegate, and call the completionHandler block passing null to stop the redirect.
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *))completionHandler

I guess I'll pick up where they left off, but in Swift since it's so many years later.
class ViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let semaphore = DispatchSemaphore(value: 0)
let configuration = URLSessionConfiguration.ephemeral
configuration.timeoutIntervalForRequest = 10
let session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
// Redirects to google.com
guard let url = URL(string: "https://bit(dot)ly/19BiSHW") else {
return
}
var data: Data?
var response: URLResponse?
var error: Error?
let task = session.dataTask(with: url) { (innerData, innerResponse, innerError) in
// For clarity, we'll call this the data task's completion closure
// Pass the data back to the calling scope
data = innerData
response = innerResponse
error = innerError
semaphore.signal()
}
task.resume()
if semaphore.wait(timeout: .now() + .seconds(15)) == .timedOut {
// The task's completion closure wasn't called within the time out, handle appropriately
} else {
if let e = error as NSError? {
if e.domain == NSURLErrorDomain && e.code == NSURLErrorTimedOut {
print("The request timed out")
}
return
}
if let d = data {
// do whatever you want with the data here, such as print it
// (the data is the HTTP response body)
print(String.init(data: d, encoding: .utf8) ?? "Response data could not be turned into a string")
return
}
if let r = response {
print("No data and no error, but we received a response, we'll inspect the headers")
if let httpResponse = r as? HTTPURLResponse {
print(httpResponse.allHeaderFields)
}
}
}
}
}
extension ViewController: URLSessionTaskDelegate {
func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: #escaping (URLRequest?) -> Swift.Void) {
// Inside the delegate method, we will call the delegate's completion handler
// completionHandler: A block that your handler should call with
// either the value of the request parameter, a modified URL
// request object, or NULL to refuse the redirect and return
// the body of the redirect response.
// I found that calling the block with nil only triggers the
// return of the body of the redirect response if the session is ephemeral
// Calling this will trigger the data task's completion closure
// which signals the semaphore and allows execution to continue
completionHandler(nil)
}
}
What the code is doing:
It is creating an inherently asynchronous task (URLSessionTask), telling it to being execution by calling resume(), then halting the current execution context by waiting on a DispatchSemaphore. This is trick I've seen used, and personally used on many occasions to make something asynchronous behave in a synchronous fashion.
The key point to make is that the code stops execution in the current context. In this example, that context is the main thread (since it is in a UIViewController method), which is generally bad practice. So, if your synchronous code never continues executing (because the semaphore is never signaled) then you UI thread will be stopped forever causing the UI to be frozen.
The final piece is the implementation of the delegate method. The comments suggest that calling completionHandler(nil) should suffice and the documentation supports that. I found that this is only sufficient if you have an ephemeral URLSessionConfiguration. If you have the default configuration, the data task's completion closure doesn't get invoked, so the semaphore never gets signaled, therefore the code to never moves forward. This is what was causing the commenter's/asker's problems of a frozen UI.

Related

iOS: Force to cancel Operation in NSOperationQueue

I need to cancel all the operations in the NSOperationQueue immediately.
Consider my scenario,
I have to hit my server continuously ie I will be calling my server whenever user types in the textbox. After user completes his input I have to hit final api call. So I create one NSOperation for single API hit. While user types in the textbox. I create NSOperation object and add that in NSOperationQueue. After detecting that user completely his input, I cancelled all the operation in the queue and hit my final api. The Problem is some operations are not cancelled immediately. So my final api hit is not called immediately. It is waiting for some time (all operation have to finish) and then it called.
FYI,
myOperationQueue.cancelAllOperations()
In Operation start method I have this code
let session = URLSession.shared
let task = session.dataTask(with: urlRequest, completionHandler: { (data, urlResponse, error) -> Void in
})
task.resume()
Please provide me the best way to call my final API immediately.
The apple documentation about the cancellAllOperations method, it clearly explains your situation.
Canceling the operations does not automatically remove them from the queue or stop those that are currently executing. For operations that are queued and waiting execution, the queue must still attempt to execute the operation before recognizing that it is canceled and moving it to the finished state. For operations that are already executing, the operation object itself must check for cancellation and stop what it is doing so that it can move to the finished state. In both cases, a finished (or canceled) operation is still given a chance to execute its completion block before it is removed from the queue.
You have to cancel your task as well.
Implement a cancel override in your Operation subclass. This override cancels the task as well.
var task: URLSessionTask?
func scheduleTask() {
let session = URLSession.shared
///Task is an instance variable
task = session.dataTask(with: urlRequest, completionHandler: { (data, urlResponse, error) -> Void in
})
task?.resume()
}
public override func cancel() {
task?.cancel()
super.cancel()
}

Wait for Firebase to load before returning from a function

I have a simple function loading data from Firebase.
func loadFromFireBase() -> Array<Song>? {
var songArray:Array<Song> = []
ref.observe(.value, with: { snapshot in
//Load songArray
})
if songArray.isEmpty {
return nil
}
return songArray
}
Currently this function returns nil always, even though there is data to load. It does this because it doesn't ever get to the perform the completion block where it loads the array before the function returns. I'm looking for a way to make the function only return once the completion block has been called but I can't put return in the completion block.
(Variations on this question come up constantly on SO. I can never find a good, comprehensive answer, so below is an attempt to provide such an answer)
You can't do that. Firebase is asynchronous. Its functions take a completion handler and return immediately. You need to rewrite your loadFromFirebase function to take a completion handler.
I have a sample project on Github called Async_demo (link) that is a working (Swift 3) app illustrating this technique.
The key part of that is the function downloadFileAtURL, which takes a completion handler and does an async download:
typealias DataClosure = (Data?, Error?) -> Void
/**
This class is a trivial example of a class that handles async processing. It offers a single function, `downloadFileAtURL()`
*/
class DownloadManager: NSObject {
static var downloadManager = DownloadManager()
private lazy var session: URLSession = {
return URLSession.shared
}()
/**
This function demonstrates handling an async task.
- Parameter url The url to download
- Parameter completion: A completion handler to execute once the download is finished
*/
func downloadFileAtURL(_ url: URL, completion: #escaping DataClosure) {
//We create a URLRequest that does not allow caching so you can see the download take place
let request = URLRequest(url: url,
cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 30.0)
let dataTask = URLSession.shared.dataTask(with: request) {
//------------------------------------------
//This is the completion handler, which runs LATER,
//after downloadFileAtURL has returned.
data, response, error in
//Perform the completion handler on the main thread
DispatchQueue.main.async() {
//Call the copmletion handler that was passed to us
completion(data, error)
}
//------------------------------------------
}
dataTask.resume()
//When we get here the data task will NOT have completed yet!
}
}
The code above uses Apple's URLSession class to download data from a remote server asynchronously. When you create a dataTask, you pass in a completion handler that gets invoked when the data task has completed (or failed.) Beware, though: Your completion handler gets invoked on a background thread.
That's good, because if you need to do time-consuming processing like parsing large JSON or XML structures, you can do it in the completion handler without causing your app's UI to freeze. However, as a result you can't do UI calls in the data task completion handler without sending those UI calls to the main thread. The code above invokes the entire completion handler on the main thread, using a call to DispatchQueue.main.async() {}.
Back to the OP's code:
I find that a function with a closure as a parameter is hard to read, so I usually define the closure as a typealias.
Reworking the code from #Raghav7890's answer to use a typealias:
typealias SongArrayClosure = (Array<Song>?) -> Void
func loadFromFireBase(completionHandler: #escaping SongArrayClosure) {
ref.observe(.value, with: { snapshot in
var songArray:Array<Song> = []
//Put code here to load songArray from the FireBase returned data
if songArray.isEmpty {
completionHandler(nil)
}else {
completionHandler(songArray)
}
})
}
I haven't used Firebase in a long time (and then only modified somebody else's Firebase project), so I don't remember if it invokes it's completion handlers on the main thread or on a background thread. If it invokes completion handlers on a background thread then you may want to wrap the call to your completion handler in a GCD call to the main thread.
Edit:
Based on the answers to this SO question, it sounds like Firebase does it's networking calls on a background thread but invokes it's listeners on the main thread.
In that case you can ignore the code below for Firebase, but for those reading this thread for help with other sorts of async code, here's how you would rewrite the code to invoke the completion handler on the main thread:
typealias SongArrayClosure = (Array<Song>?) -> Void
func loadFromFireBase(completionHandler:#escaping SongArrayClosure) {
ref.observe(.value, with: { snapshot in
var songArray:Array<Song> = []
//Put code here to load songArray from the FireBase returned data
//Pass songArray to the completion handler on the main thread.
DispatchQueue.main.async() {
if songArray.isEmpty {
completionHandler(nil)
}else {
completionHandler(songArray)
}
}
})
}
Making Duncan answer more precise. You can make the function like this
func loadFromFireBase(completionHandler:#escaping (_ songArray: [Song]?)->()) {
ref.observe(.value) { snapshot in
var songArray: [Song] = []
//Load songArray
if songArray.isEmpty {
completionHandler(nil)
}else {
completionHandler(songArray)
}
}
}
You can return the songArray in a completion handler block.

Every 11th execution of NSURLSessionTask takes much longer than others

I'm having a strange behavior in my Swift app, that I currently don't understand.
I have subclassed NSOperation to create different operations that can call Rest-WebServices via NSURLSession / NSURLSessionTask. This works fine in general.
In my case I have to execute many of these operations successively. Let's say I create a "chain" of 30 NSOperations with setting dependencies to execute them one by one.
Now I could reproduce the behavior, that every 11th (?!) execution of such an operation, takes much longer than the others. It seems as if the execution "sleeps" for nearly 10 seconds before it goes on. I can rule out, that the concrete web service call is the issue. Because if I change the order of execution, it is still the 11th operation that "hangs".
Currently I am creating a new instance of NSURLSession (defaultConfiguration) during the execution of every operation. Yesterday I tried to create a static instance of NSURLSession and create the instances of NSURLSessionTask during execution only. And now the "hanger" is gone! Unfortunately I could not do it this way, because the NSURLSessionDelegate has to be different for some operations, but this delegate must be passed during initialization.
Did anyone experience a similar behavior?
First I thought my code is too complex to post. But after Ketans comment, I will give it a try. I have trimmed it down to the most important parts. I hope this helps to show my problem. If you need more detail, please let me know.
class AbstractWebServiceOperation: NSOperation {
// VARIANT 2: Create a static NSURLSession only once --> The "sleep" DOES NOT occur!
static let SESSION = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
init(servicePath:String, httpMethod:String) {
// This is an 'abstract' class, that will be subclassed for concrete webService calls that differ in servicePath for URL, HTTP Method and parameters
}
// Override start() function of NSOperation to do webService call. NSOperations vars (ready, executing, finished) are overridden too, to get NSOperation "waiting" for the webService result. But I don't think it is relevant for the issue. So I did leave it out.
override func start() {
super.start()
// [...]
if let request = createRequest()
{
let task = createTask(request)
task.resume()
}
// [...]
}
// Creates the concrete NSURLRequest by using the service path and HTTP method defined by the concrete subclass.
private func createRequest()-> NSMutableURLRequest? {
// [...]
let webServiceURL = "https://\(self.servicePath)"
let url = NSURL(string: webServiceURL)
let request = NSMutableURLRequest(URL: url!)
request.timeoutInterval = 60
request.HTTPMethod = self.httpMethod
request.addValue("application/json;charset=UTF-8", forHTTPHeaderField: "Content-Type")
request.addValue("application/json;charset=UTF-8", forHTTPHeaderField: "Accept")
return request;
}
// Creates the concrete NSURLSessionTask for the given NSURLRequest (using a completionHandler defined by getCompletionHandler())
func createTask(request:NSURLRequest) -> NSURLSessionTask
{
// VARIANT 1: Create a new NSURLSession every time a AbstractWebServiceOperation is executed --> The "sleep" occurs!
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: nil, delegateQueue: nil)
return session.dataTaskWithRequest(request, completionHandler:getCompletionHandler())
// VARIANT 2: Create a static NSURLSession only once --> The "sleep" DOES NOT occur!
return AbstractWebServiceOperation.SESSION.dataTaskWithRequest(request, completionHandler:getCompletionHandler())
}
// Returns the completion handler for the NSURLSessionTask (may be overriden in subclass)
func getCompletionHandler() -> (NSData?, NSURLResponse?, NSError?) -> Void
{
return completionHandler
}
// Default completion handler
lazy var completionHandler:(NSData?, NSURLResponse?, NSError?) -> Void = {(data : NSData?, response : NSURLResponse?, error : NSError?) in
// default completion handling
}
}
Awww... I simply forgot to call session.finishTasksAndInvalidate() to invalidate the session after my webService call is done.
That solves my problem!

Cancel Alamofire file download if this file is already exists

How to cancel Alamofire request if the downloaded file is already exists in documents folder?
Here is the code for request:
Alamofire.download(.GET, fileUrls[button.tag], destination: { (temporaryURL, response) in
if let directoryURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0] as? NSURL {
let fileURL = directoryURL.URLByAppendingPathComponent(response.suggestedFilename!)
self.localFilePaths[button.tag] = fileURL
if NSFileManager.defaultManager().fileExistsAtPath(fileURL.path!) {
NSFileManager.defaultManager().removeItemAtPath(fileURL.path!, error: nil)
}
return fileURL
}
println("temporaryURL - \(temporaryURL)")
self.localFilePaths[button.tag] = temporaryURL
return temporaryURL
}).progress { _, totalBytesRead, totalBytesExpectedToRead in
println("\(totalBytesRead) - \(totalBytesExpectedToRead)")
dispatch_async(dispatch_get_main_queue()) {
self.progressBar.setProgress(Float(totalBytesRead) / Float(totalBytesExpectedToRead), animated: true)
if totalBytesRead == totalBytesExpectedToRead {
self.progressBar.hidden = true
self.progressBar.setProgress(0, animated: false)
}
}
}.response { (_, _, data, error) in
let previewQL = QLReaderViewController()
previewQL.dataSource = self
previewQL.currentPreviewItemIndex = button.tag
self.navigationController?.pushViewController(previewQL, animated: true)
}
I've also tried to create a request variable var request: Alamofire.Request? and then cancel request?.cancel() it if that file exists but it doesn't work.
Can someone help me to solve this issue?
Rather than cancelling the request, IMO you shouldn't make it in the first place. You should do the file check BEFORE you start the Alamofire request.
If you absolutely feel you need to start the request, you can always cancel immediately after starting the request.
var shouldCancel = false
let request = Alamofire.request(.GET, "some_url") { _, _ in
shouldCancel = true
}
.progress { _, _, _ in
// todo...
}
.response { _, _, _ in
// todo...
}
if shouldCancel {
request.cancel()
}
TL; DR: Canceling a request is a bit cumbersome in many cases. Even Alamofire, as far as I know, does not guarentee that request will be cancelled upon your request, immediately. However, you may use dispatch_suspend or NSOperation in order to overcome this.
Grand Central Dispatch (GCD)
This way utilizes functional programming.
Here we enlight our way with low-level programming. Apple introduced a good library, aka GCD, to do some thread-level programming.
You cannot cancel a block, unless... you suspend a queue (if it is not main or global queue).
There is a C function called dispatch_suspend, (from Apple's GCD Reference)
void dispatch_suspend(dispatch_object_t object);
Suspends the invocation of block objects on a dispatch object.
And you can also create queues (who are dispatch_object_ts) with dispatch_queue_create.
So you can do your task in user created queue, and you may suspend this queue in order to prevent CPU from doing something unnecessary.
NSOperation (also NSThread)
This way utilizes functional programming over object-oriented interface.
Apple also introduced NSOperation, where object-oriented programming may be object, whereas it is easier to cope with.
NSOperation is an abstract class, which associates code and data, according to the Apple's documentation.
In order to use this class, you should either use one of its defined subclasses, or create your own subclass: In your case particularly, I suppose NSBlockOperation is the one.
You may refer to this code block:
let block = NSBlockOperation { () -> Void in
// do something here...
}
// Cancel operation
block.cancel()
Nevertheless, it also does not guarantee stopping from whatever it is doing. Apple also states that:
This method does not force your operation code to stop. Instead, it updates the object’s internal flags to reflect the change in state. If the operation has already finished executing, this method has no effect. Canceling an operation that is currently in an operation queue, but not yet executing, makes it possible to remove the operation from the queue sooner than usual.
If you want to take advantage of flags, you should read more: Responding to the Cancel Command

How to find and cancel a task in NSURLSession?

I'm using an NSURLSession object to load images in my application. That could be loading several images simultaneously.
In some moments I need to cancel the loading of one specific image and continue loading others.
Could you suggest the correct way to do that?
To get tasks list you can use NSURLSession's method
- (void)getTasksWithCompletionHandler:(void (^)(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks))completionHandler;
Asynchronously calls a completion callback with all outstanding data,
upload, and download tasks in a session.
Then check task.originalRequest.URL for returned tasks to find the one you want to cancel.
Based on all the answers below, I'd go for something like this:
Swift 5
func cancelTaskWithUrl(_ url: URL) {
URLSession.shared.getAllTasks { tasks in
tasks
.filter { $0.state == .running }
.filter { $0.originalRequest?.url == url }.first?
.cancel()
}
}
You also probably want to account for your task completion handler, since canceling the task will result Error in that completion handler.
Hope below code help.
-(IBAction)cancelUpload:(id)sender {
if (_uploadTask.state == NSURLSessionTaskStateRunning) {
[_uploadTask cancel];
}
}
Swift 3.0 version of #Avt's answer to get the task list. Use getTasksWithCompletionHandler.
func getTasksWithCompletionHandler(_ completionHandler: #escaping ([URLSessionDataTask],
[URLSessionUploadTask],
[URLSessionDownloadTask]) -> Void) {
}
The returned arrays contain any tasks that you have created within the
session, not including any tasks that have been invalidated after
completing, failing, or being cancelled.
I suggest two methods:
Put the list of NSURLSessionTask in an array. In case you don't know exactly how many images you would get. Though you have to know the index of session in order to cancel it.
If you get a limited number of images. Just use a set of NSURLSessionTask as global variables so you can access to cancel it anywhere in your class.
I think you should do this...
First, keep track of your requests per xib
var download_requests = [NSURLSession]()
Then, whenever you make a request, append your request to your array like so,
let s = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
if let url = NSURL(string: "http://my.url.request.com")
{
download_requests.append(s)
s.dataTaskWithURL(url)
{ (data, resp, error) -> Void in
// ....
}
}
Then whenever you want to cancel any outstanding requests, (let's say on viewDidDisappear), do
override func viewDidDisappear(animated: Bool)
{
super.viewDidDisappear(animated)
//stop all download requests
for request in download_requests
{
request.invalidateAndCancel()
}
}

Resources