Swift return data from URLSession - ios

I cannot return data from my HTTPrequest and I can't get completion handlers to work either. So please assist me in my quest to solve this issue:
public static func createRequest(qMes: message, location: String, method: String) -> String{
let requestURL = URL(string: location)
var request = URLRequest(url: requestURL!)
request.httpMethod = method
request.httpBody = qMes.toString().data(using: .utf8)
let requestTask = URLSession.shared.dataTask(with: request) {
(data: Data?, response: URLResponse?, error: Error?) in
if(error != nil) {
print("Error: \(error)")
}
return String(data: data!, encoding: String.Encoding.utf8) as String!
}
requestTask.resume()
}
It is excpecting non-void return statement in void function. At this point I'm clueless...

You can use this completion block method to send the final response:
For Instance:
I have returned String in completion block, after successful response without error just pass the result in block.
public func createRequest(qMes: String, location: String, method: String , completionBlock: #escaping (String) -> Void) -> Void
{
let requestURL = URL(string: location)
var request = URLRequest(url: requestURL!)
request.httpMethod = method
request.httpBody = qMes.data(using: .utf8)
let requestTask = URLSession.shared.dataTask(with: request) {
(data: Data?, response: URLResponse?, error: Error?) in
if(error != nil) {
print("Error: \(error)")
}else
{
let outputStr = String(data: data!, encoding: String.Encoding.utf8) as String!
//send this block to required place
completionBlock(outputStr!);
}
}
requestTask.resume()
}
You can use this below code to execute the above completion block function:
self.createRequest(qMes: "", location: "", method: "") { (output) in
}
This will solve your following requirement.

{
(data: Data?, response: URLResponse?, error: Error?) in
if(error != nil) {
print("Error: \(error)")
}
return String(data: data!, encoding: String.Encoding.utf8) as String!
}
This part of your code is the completion handler for the dataTask() method. It's a block of code that you pass into the dataTask() method to be executed later on (when the server sends back some data or there's an error). It's not executed straight away.
This means that when your createRequest() method above is executing, it passes straight over that code, then onto the requestTask.resume() line, and then the method ends. At that point, because your method is defined as returning a String, you need to return a String. Returning it from the completion handler is no good because that hasn't been executed yet, that is going to be executed later on.
There's lots of different ways to handle asynchronous programming, but one way of tackling this is to change your createRequest() method so that it isn't defined to return a String, create a method that takes a String as a parameter which does whatever you wanted to do with the return value, and then call that method from your completion handler.

Instead of using return, try using completion handlers as you mentioned in your question.
func createRequest(qMes: message, location: String, method: String, completionHandler: #escaping (_ data:Data?, _ response: URLResponse?, _ error: NSError?) -> Void)
Then instead of return you should use something like completionHandler(data, response, error)
And this is how you make the request:
var request = URLRequest(url: Foundation.URL(string: URL)!)
request.httpMethod = method
//request.addValue(authString, forHTTPHeaderField: "Authorization") // if you need some
let task = URLSession.shared.dataTask(with: request, completionHandler: { data, response, error in
guard error == nil && data != nil else
{
print("error=\(error)")
completionHandler(data, response, error as NSError?)
return
}
completionHandler(data, response, error as NSError?)
})
task.resume()

Just in your function call
var webString = try String(contentsOf: URL(string: url)!)
And you have full response in string, that you can return

Related

Contextual closure type error when accessing the class file in swift

I am new to swift programming, i have Implemented Speech to text using Microsoft Azure,when i calling the class file i am getting the error like "Contextual closure type '(Data?, URLResponse?, Error?) -> Void' expects 3 arguments, but 1 was used in closure body " .can anyone help me to solve this error.
//This is the sample code where i am calling the function in class file
TTSHttpRequest.submit(withUrl: TTSSynthesizer.ttsServiceUri,
andHeaders: [
"Content-Type": "application/ssml+xml",
"X-Microsoft-OutputFormat": outputFormat.rawValue,
"Authorization": "Bearer " + accessToken,
"X-Search-AppId": appId,
"X-Search-ClientID": clientId,
"User-Agent": "TTSiOS",
"Accept": "*/*",
"content-length": "\(message.lengthOfBytes(using: encoding))"
],
andBody: message.data(using: encoding)) { (c: TTSHttpRequest.Callback) in
guard let data = c.data else { return }
callback(data)
}
//This is the class file where i am getting the error
class TTSHttpRequest {
typealias Callback = (data: Data?, response: URLResponse?, error: Error?)
static func submit(withUrl url: String, andHeaders headers: [String: String]? = nil, andBody body: Data? = nil, _ callback: #escaping (Callback) -> ()) {
guard let url = URL(string: url) else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
headers?.forEach({ (header: (key: String, value: String)) in
request.setValue(header.value, forHTTPHeaderField: header.key)
})
if let body = body {
request.httpBody = body
}
let task = URLSession.shared.dataTask(with: request) { (c:Callback) in //In this line i am getting above mentioned error.
callback(c)
}
task.resume()
}
}
As Leo Dabus commented, you cannot pass a single argument closure (your closure takes one argument c of type Callback) as a parameter expecting three-argument closure.
This is the effect of SE-0110 Distinguish between single-tuple and multiple-argument function types.
The status of the proposal currently shows as Deferred, but the most functionality of this proposal is already implemented and effective in Swift 4, and only a little part (including Addressing the SE-0110 usability regression in Swift 4) is rewinded and under re-designing.
One possible fix would be something like this:
class TTSHttpRequest {
typealias Callback = (data: Data?, response: URLResponse?, error: Error?)
static func submit(withUrl url: String, andHeaders headers: [String: String]? = nil, andBody body: Data? = nil, _ callback: #escaping (Callback) -> ()) {
guard let url = URL(string: url) else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
headers?.forEach({ (header: (key: String, value: String)) in
request.setValue(header.value, forHTTPHeaderField: header.key)
})
if let body = body {
request.httpBody = body
}
let task = URLSession.shared.dataTask(with: request) { data, response, error in //<- three arguments
callback((data, response, error)) //<- Call the callback with one tuple.
}
task.resume()
}
}

Swift Closure Value Capture scope

I have a question using Swift 3, I am trying to access the task variable from inside my completion closure but it is not available. If I try to access "task" inside the block I get "error: use of unresolved identifier 'task'" What am I doing wrong?
typealias completionHandler = (data: Data?, httpResponse: HTTPURLResponse?, validServer: Bool, serverRealm: String?, serverVersion: String?) -> Void
typealias failureHandler = (data: Data?, response: URLResponse?, error: NSError?) -> Void
func ping(address: String, completionBlock: completionHandler, failureBlock: failureHandler?) -> URLSessionDataTask? {
guard var addressComponents = URLComponents(string: address) else {
let error = NSError(domain: "PING", code: 99, userInfo: [NSLocalizedDescriptionKey : "Invalid URL: \(address)"])
failureBlock?(data: nil, response: nil, error: error)
return nil
}
addressComponents.path = Paths.ping.rawValue
let request = URLRequest(url: addressComponents.url!, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: gsTimeout)
//--------------------------------------------------------------------------------
// Ping the server and process the repsonse
//--------------------------------------------------------------------------------
var task: URLSessionDataTask!
task = session.dataTask(with: request) { (data, response, error) in
print("Task: \(task)")
if error == nil {
if let response = response as? HTTPURLResponse {
let results = self.parseHeader(response)
print("Results: \(results)")
if results.isServer == true {
completionBlock(data: data, httpResponse: response, validServer: results.isServer, serverRealm: results.realm, serverVersion: results.serverVersion)
}
}
} else {
failureBlock?(data: data, response: response, error: error)
}
}
task.taskDescription = Paths.ping.rawValue
task.resume()
return task
}
Updated with working code now. task is available if you use it inside the block. Thanks everyone
There is nothing wrong with your code. It compiles just fine.
You are unable to po the value of task using LLDB while paused in the debugger because you are paused inside the completion block. You never captured task in this code (you didn't refer to it), so it is not in scope here. Only outside scope actually referred to inside a closure is captured by the closure.

How to return JSON in swift from HTTPPost

I am new to iOS developing and need some help with JSON and what to be returning. I have the following function in my modal:
func loginRequest(username: String, password: String, completionHandler: ((NSURLResponse!, JSON, NSError?) -> Void)) {
var request : NSMutableURLRequest = NSMutableURLRequest()
request.URL = NSURL(string: ""correct post url"\(username)/\(password)")
request.HTTPMethod = "POST"
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue(), completionHandler:{ (response:NSURLResponse!, data: NSData!, error: NSError!) -> Void in
var error: AutoreleasingUnsafeMutablePointer<NSError?> = nil
let httpResponse = response as? NSHTTPURLResponse
var json = JSON(data: data!)
println(json)
})
}
This does successfully return the JSON if I print it inside this function. However, the following code in my view controller yields no errors but fails to return the JSON at all.
#IBAction func signIn(sender: UIButton) {
modal.loginRequest("Test", password: "Pass") { (response, json, error) -> Void in
println(json)
println("Hello")
if (json != nil) {
Do parsing stuff
}
}
In my ViewController, json does not return nil, it doesn't return at all. The code prints in from my modal but does not show in the VC. How am I calling the function wrong?
Your function doesn't call the completion handler closure which is passed as param. If you want access the data however, you have to call the completionHandler closure. This is how your code should be:
func loginRequest(username: String, password: String, completionHandler: ((NSURLResponse!, JSON, NSError?) -> Void)) {
var request : NSMutableURLRequest = NSMutableURLRequest()
request.URL = NSURL(string: ""correct post url"\(username)/\(password)")
request.HTTPMethod = "POST"
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue(), completionHandler:{ (response:NSURLResponse!, data: NSData!, error: NSError!) -> Void in
var error: AutoreleasingUnsafeMutablePointer<NSError?> = nil
let httpResponse = response as? NSHTTPURLResponse
var json = JSON(data: data!)
println(json)
// call the closure argument here, to pass the asynchrounsly retrieved vallues
// back to the caller of loginRequest
completionHandler(response, json, error)
})
}

In Swift, when using dataTaskWithRequest the completionHandler returns a nil NSURLResponse when the request times out

When I post data to a service, the request is normally fine, but if it takes too long and times out, in the completion handler, I get NSURLResponse as nil even though it should never return nil.
I'm using Swift 1.1, here's an example of how I'm doing this:
func postX(actionKey:String, postData:AnyObject, callBack:((data:NSData?, resp: NSURLResponse) -> Void)?) -> Void
{
var mreq = createRequest(actionKey, method: "POST", https: true, json: true)
if (self.dataTask != nil)
{
self.dataTask?.cancel()
}
var err: NSError?
mreq.HTTPBody = NSJSONSerialization.dataWithJSONObject(postData, options: nil, error: &err)
self.dataTask = self.getSession().dataTaskWithRequest(mreq, completionHandler: { (data:NSData!, resp: NSURLResponse!, error: NSError!) -> Void in
Dlog.log("response: \(resp)") //Prints out: response: nil
if (error != nil)
{
//do something
}
else
{
//do something else
}
})
if (self.dataTask != nil)
{
self.dataTask!.resume()
}
}
The NSURLResponse reference in the completionHandler block of dataTaskWithRequest is an optional (meaning that it can be nil). If the request times out, you'd expect it to be nil (because you presumably have not yet received any response).
I would suggest changing the NSURLResponse parameter of your callback closure to be an optional as well, just like dataTaskWithRequest. (I might return the optional NSError, too, so you can check for particular errors.) And you can detect timeout errors by looking for NSURLErrorTimedOut.
For example:
func postX(actionKey: String, postData: AnyObject, callBack: (NSData?, NSURLResponse?, NSError?) -> ()) {
let request = createRequest(actionKey, method: "POST", https: true, json: true)
dataTask?.cancel() // note, we don't need `if` clause, as the `?` does everything for us
request.HTTPBody = try! NSJSONSerialization.dataWithJSONObject(postData, options: [])
let task = getSession().dataTaskWithRequest(request) { data, response, error in
callBack(data, response, error)
}
task.resume()
dataTask = task
}
Then you could use it like:
postX(actionKey, postData: postData) { data, response, error in
// handle response however you want
// did it time out?
if error?.domain == NSURLErrorDomain && error?.code == NSURLErrorTimedOut {
print("timed out") // note, `response` is likely `nil` if it timed out
}
}
For the sake of completeness, the Swift 3 implementation might look like:
func postX(actionKey: String, postData: AnyObject, callBack: (Data?, URLResponse?, NSError?) -> ()) {
var request = createRequest(actionKey: actionKey, method: "POST", https: true, json: true)
dataTask?.cancel() // note, we don't need `if` clause, as the `?` does everything for us
request.httpBody = try! JSONSerialization.data(withJSONObject: postData, options: [])
let task = getSession().dataTask(with: request) { data, response, error in
callBack(data, response, error)
}
task.resume()
dataTask = task
}
func createRequest(actionKey: String, method: String, https: Bool, json: Bool) -> URLRequest {
var request = URLRequest(url: ...)
...
return request
}
And it would be called like:
postX(actionKey: actionKey, postData: postData) { data, response, error in
// handle response however you want
// did it time out?
if error?.domain == NSURLErrorDomain && error?.code == NSURLErrorTimedOut {
print("timed out") // note, `response` is likely `nil` if it timed out
}
}

NSURLConnection sendAsynchronousRequest can't get variable out of closure

I'm trying to get a simple text response from a PHP page using POST. I have the following code:
func post(url: String, info: String) -> String {
var URL: NSURL = NSURL(string: url)!
var request:NSMutableURLRequest = NSMutableURLRequest(URL:URL)
var output = "Nothing Returned";
request.HTTPMethod = "POST";
var bodyData = info;
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()){
response, data, error in
output = (NSString(data: data, encoding: NSUTF8StringEncoding))!
}
return output
}
While this code does not throw any errors, when I make a call to it like this:
println(post(url, info: data))
It only prints: "Nothing Returned" even though if I were to change the line:
output = (NSString(data: data, encoding: NSUTF8StringEncoding))!
to this:
println((NSString(data: data, encoding: NSUTF8StringEncoding)))
it does print out the proper response. Am I doing something wrong with my variables here?
This is calling asynchronous function that is using a completion handler block/closure. So, you need to employ the completion handler pattern in your own code. This consists of changing the method return type to Void and adding a new completionHandler closure that will be called when the asynchronous call is done:
func post(url: String, info: String, completionHandler: (NSString?, NSError?) -> ()) {
let URL = NSURL(string: url)!
let request = NSMutableURLRequest(URL:URL)
request.HTTPMethod = "POST"
let bodyData = info
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) { response, data, error in
guard data != nil else {
completionHandler(nil, error)
return
}
completionHandler(NSString(data: data!, encoding: NSUTF8StringEncoding), nil)
}
}
Or, since NSURLConnection is now formally deprecated, it might be better to use NSURLSession:
func post(url: String, info: String, completionHandler: (NSString?, NSError?) -> ()) -> NSURLSessionTask {
let URL = NSURL(string: url)!
let request = NSMutableURLRequest(URL:URL)
request.HTTPMethod = "POST"
let bodyData = info
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
dispatch_async(dispatch_get_main_queue()) {
guard data != nil else {
completionHandler(nil, error)
return
}
completionHandler(NSString(data: data!, encoding: NSUTF8StringEncoding), nil)
}
}
task.resume()
return task
}
And you call it like so:
post(url, info: info) { responseString, error in
guard responseString != nil else {
print(error)
return
}
// use responseString here
}
// but don't try to use response string here ... the above closure will be called
// asynchronously (i.e. later)
Note, to keep this simple, I've employed the trailing closure syntax (see Trailing Closure section of The Swift Programming Language: Closures), but hopefully it illustrates the idea: You cannot immediately return the result of an asynchronous method, so provide a completion handler closure that will be called when the asynchronous method is done.

Resources