I want to write func for HTTP Request to my server and get some data, when i print it (print(responseString)) it looks good, but when i try to return data, its always empty
public func HTTPRequest(dir: String, param: [String:String]?) -> String{
var urlString = HOST + dir + "?"
var responseString = ""
if param != nil{
for currentParam in param!{
urlString += currentParam.key + "=" + currentParam.value + "&"
}
}
let url = URL(string: urlString)
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
guard error == nil else {
print("ERROR: HTTP REQUEST ERROR!")
return
}
guard let data = data else {
print("ERROR: Empty data!")
return
}
responseString = NSString(data: data,encoding: String.Encoding.utf8.rawValue) as! String
print(responseString)
}
task.resume()
return responseString
}
As mentioned in Rob's comments, the dataTask closure is run asynchronously. Instead of returning the value immediately, you would want to provide a completion closure and then call it when dataTask completes.
Here is an example (for testing, can be pasted to Xcode Playground as-is):
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let HOST = "http://example.org"
public func HTTPRequest(dir: String, param: [String: String]?, completion: @escaping (String) -> Void) {
var urlString = HOST + dir + "?"
if param != nil{
for currentParam in param! {
urlString += currentParam.key + "=" + currentParam.value + "&"
}
}
let url = URL(string: urlString)
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
guard error == nil else {
print("ERROR: HTTP REQUEST ERROR!")
return
}
guard let data = data else {
print("ERROR: Empty data!")
return
}
let responseString = NSString(data: data,encoding: String.Encoding.utf8.rawValue) as! String
completion(responseString)
}
task.resume()
}
let completion: (String) -> Void = { responseString in
print(responseString)
}
HTTPRequest(dir: "", param: nil, completion: completion)
You need to use completion block instead of returning value because the dataTask
closure is run asynchronously, i.e. later, well after you return from your method. You don't want to try to return the value immediately (because you won't have it yet). You want to (a) change this function to not return anything, but (b) supply a completion handler closure, which you will call inside the dataTask closure, where you build responseString.
For example, you might define it like so:
public func HTTPRequest(dir: String, param: [String:String]? = nil, completionHandler: @escaping (String?, Error?) -> Void) {
var urlString = HOST + dir
if let param = param {
let parameters = param.map { return $0.key.percentEscaped() + "=" + $0.value.percentEscaped() }
urlString += "?" + parameters.joined(separator: "&")
}
let url = URL(string: urlString)
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
guard let data = data, error == nil else {
completionHandler(nil, error)
return
}
let responseString = String(data: data, encoding: .utf8)
completionHandler(responseString, nil)
}
task.resume()
}
Note, I'm percent escaping the values in the parameters dictionary using something like:
extension String {
/// Percent escapes values to be added to a URL query as specified in RFC 3986
///
/// This percent-escapes all characters besides the alphanumeric character set and "-", ".", "_", and "~".
///
/// http://www.ietf.org/rfc/rfc3986.txt
///
/// - Returns: Returns percent-escaped string.
func percentEscaped() -> String {
let allowedCharacters = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~")
return self.addingPercentEncoding(withAllowedCharacters: allowedCharacters)!
}
}
And then you'd call it like so:
HTTPRequest(dir: directory, param: parameterDictionary) { responseString, error in
guard let responseString = responseString else {
// handle the error here
print("error: \(error)")
return
}
// use `responseString` here
DispatchQueue.main.async {
// because this is called on background thread, if updating
// UI, make sure to dispatch that back to the main queue.
}
}
// but don't try to use `responseString` here