How to make sure that an Alamofire request runs? - swift

I copy-pasted the first example of the Alamofire readme (at fa3c6d0) into main.swift:
import Foundation
import Alamofire
Alamofire.request("https://httpbin.org/get").responseJSON { response in
print(response.request) // original URL request
print(response.response) // HTTP URL response
print(response.data) // server data
print(response.result) // result of response serialization
if let JSON = response.result.value {
print("JSON: \(JSON)")
}
}
print("Done")
When I run this, all I get is Done, then the application terminates.
While I see here that I can pick a dispatch queue, this answer seems to suggest that I shouldn't have to.
Anyway, having had a similar issue with "basic" requests I tried the same solution but to no avail: the application now blocks. So, apparently, Alamofire has a different default than URLSession and wants to use the main thread.
What is the best way to have a request executed (and waited for) in an application like this?

We need to do two things.
Execute the request in the background.
Block the main thread until the request is done, i.e. the completion handler ran.
My original code does neither.
The first item is achieved by using .response(queue: DispatchQueue(label: "some-name")) (or one of its variants).
Waiting can be done in several ways.
Using a flag and active waiting, as shown here (won't scale to more than one request).
Use active waiting with countdown latch as shown here (works for multiple requests).
Use DispatchSemaphore as seen e.g. here.
And probably many more.

Related

Multiple URLSession dataTask on asynchronous call with completion handler causes memory goes up

I'm developing an upload project in swift. I'm taking very large files (video, picture with size over 500 MB) with imagepickercontroller and dividing this file into chunks which has a size 1 MB. Then I send these chunks to remote server and make them defragment in server and I'm showing this file to user.
I have no problem if the file size is under 300 MB. But after this size, memory goes up too much and app is being crashed. Actually, in every case memory usage are raising but there is no crash.
When I watch progress on console, I see URLSession task begins. But, because of these tasks are waiting response from completion handler, the task queue is growing and memory usage goes up. Is there a way when a task begins, this task's completion handler begins too? I think if I can make task queue free concurrently, my problem solves. I'm waiting your helps.
let url:URL = URL(string: "\(addressPrefix)UploadFile")!
let session = URLSession.shared
let request = NSMutableURLRequest(url: url)
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
request.httpMethod = "POST"
let bodyData = "\(metaDataID)~\(chunkIndex)~\(chunkSize)~\(chunkHash)~\(wholeTicket)~\(fileDataString)"
request.httpBody = bodyData.data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue));
request.timeoutInterval = .infinity
let task = session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
guard let _:Data = data, let _:URLResponse = response, error == nil else {
var attemptCounter = 1
if attemptCounter <= 3 {
completion("\(attemptCounter).attempt",chunkSize, error)
attemptCounter += 1
}
return
}
let jsonStr = String(data: data!, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue))
completion(jsonStr, chunkSize, error)
SingletonConnectionManager.sharedConnectionDataManager.dataTasks["uploadFile"] = nil
})
SingletonConnectionManager.sharedConnectionDataManager.dataTasks["uploadFile"] = task
task.resume()
---I call this URLSession task from this function in a tableview controller
tmpConnection.uploadFile(chunk, metaDataID!, chunkIndex: chunkIndex, completion: {(result, chunkSize, error) in
// I want to enter immediately when 'uploadFile' get called })
The requests aren't really waiting until all of them have been sent. When things are working correctly, each callback happens when the associated request finishes, and it wouldn't make sense for that to happen sooner, because the callback provides the response from the server (which you can't possibly get back until after the request has been fully sent out).
The problem here is that you are completely clogging up the session by starting entirely too many tasks at the same time. There's a known bug in NSURLSession that causes it to start to fall apart when you create a large number of tasks in a single session all at once. When you get too many tasks in a session, IIRC, the session stops calling callbacks entirely, and basically the session becomes unusable. (There's another Stack Overflow question in which this was discussed a couple of years ago, though I can't seem to find it right now.)
And because the tasks never complete, your app ends up leaking all the memory that you're using for the body data, which means your app just allocates more and more memory until it gets evicted.
The only way to fix this problem is to stop adding all of the requests to the session all at once. Start a few tasks at first (for at most eight parts or so), and then wait to send the next part until one of the previous parts finishes or fails. This approach will not only prevent you from bricking the NSURLSession, but also will prevent you from allocating an insane amount of memory to hold all of the request body NSData objects, which are currently all sitting in RAM at once.
I suggest keeping an NSMutableArray of NSNumber object representing each unsent chunk. That way, you know what is still left to send, and you can just loop to 8 and pull off the first 8 numbers, and send the chunks with those numbers. When a request completes successfully, grab the next number out of the array and send the chunk with that number.
Also, you shouldn't stop after a particular number of retries. Instead, when a request fails, check the failure to decide whether to retry (network failure) or give up (server error). Then use reachability to wait until a good time to try again, and try again when it says that the destination host is reachable. Cancel the upload only if the user explicitly asks you to cancel the upload by hitting a cancel button or similar. If the user asks you to cancel the upload, tear down your data structure so you don't start any new requests, then invalidate the URL session.

Fiddler - add latency on single response without modifying response

In Fiddler how can I slow down the response of a specific request only, while passing through the response from the server?
I'm aware I can simulate a slow speed for all requests - that's not what I want.
Using the AutoResponder with a specific rule forces me to choose what to respond with.
How can I use the "Latency" feature without modifying the response? Is this possible in Fiddler?
I understood your question that you want to delay either request or response time for a specific request.
You could do it with the FiddlerScript module by updating the oSession object.
onBeforeRequest
// Delay sends by 300ms per KB uploaded.
oSession["request-trickle-delay"] = "300";
onBeforeResponse
// Delay receives by 150ms per KB downloaded.
oSession["response-trickle-delay"] = "150";
You would also need to filter the correct request in the selected method.
Filtering
// Sample Rule: Break requests for URLs containing "/path/"
if (oSession.uriContains("/path/")) {
}
if (oSession.hostname == "some.hostname") {
}
if (oSession.url == "some.url") {
}
Additional info can be found here
Hope it helps
Rather than using the latency feature, you can enter *delay:5000 as the then respond with... command, instead of a file path.
I noticed that the rules are ignored with a blank response, so you can use the latency with a command/path of *action, which isn't a real action, but causes the rule to execute and the latency to take effect, in case you really want to use the Latency column.

How to produce a response body with asynchronously created body chunks in Swift Vapor

I am looking into the Swift Vapor framework.
I am trying to create a controller class that maps data obtained on an SSL link to a third party system (an Asterisk PBX server..) into a response body that is sent over some time down to the client.
So I need to send received text lines (obtained separately on the SSL connection) as they get in, without waiting for a 'complete response' to be constructed.
Seeing this example:
return Response(status: .ok) { chunker in
for name in ["joe\n", "pam\n", "cheryl\n"] {
sleep(1)
try chunker.send(name)
}
try chunker.close()
}
I thought it might be the way to go.
But what I see connecting to the Vapor server is that the REST call waits for the loop to complete, before the three lines are received as result.
How can I obtain to have try chunker.send(name) send it's characters back the client without first waiting for the loop to complete?
In the real code the controller method can potentially keep an HTTP connection to the client open for a long time, sending Asterisk activity data to the client as soon as it is obtained. So each .send(name) should actually pass immediately data to the client, not waiting for the final .close() call.
Adding a try chunker.flush() did not produce any better result..
HTTP requests aren't really designed to work like that. Different browsers and clients will function differently depending on their implementations.
For instance, if you connect with telnet to the chunker example you pasted, you will see the data is sent every second. But Safari on the other hand will wait for the entire response before displaying.
If you want to send chunked data like this reliably, you should use a protocol like WebSockets that is designed for it.

Syncronous http request Swift

I'm using Alamofire to make a GET request. It's working just fine but it's request() method runs asynchronously, I cannot know which line is called first. I just want to know how can I make an exactly same GET request using synchronous manner.
Alamofire.request(.GET, "http://localhost:2403/postedjob", parameters: ["$limit": 2, "$sort": ["id":"+1"]])
.responseJSON { _, _, JSON, _ in
println(JSON)
}
This may not be the answer that you are expecting.. but I believe it to be the correct answer. Do not do http requests in a synchronous way. You should do this asynchronously and use protocols or completion blocks to execute code after the request has finished.

Using NSURLSession (in Swift) to get data

I've been searching around to learn how to get some data using NSURLSession in Swift.
There is a general pattern being repeated on the internet for making an http request using NSURLSession, and it looks a lot like this:
let url = NSURL(string: "myPretendURLthatIsNotThisURL.com")
let task = NSURLSession.sharedSession().dataTaskWithURL(url) {(data, response, error) in println(NSString(data: data, encoding: NSUTF8StringEncoding))}
task.resume()
If I place a real URL in, I can see a lot of JSON output printed, which is the expected response from the server. Nice : ) I can see the data from println(). However, I would rather display the returned data in a text view when I press a button. I do believe my solution relies on using a delegate (somehow).
Is it all right to request an example of how this is done, in the following context:
Code is in Swift
Press a button (IBAction, contains the code above to make the request)
Display the data returned (it's JSON, I'll worry how to parse it later on) in a text view.
Thanks!