How do I set a the serverResponse var from within the if let data string statement - swift

func getResponse(serverName: String) -> String {
var serverResponse: String = "No Response"
let serverURL = "http://" + serverName + ":3000"
if let url = URL(string: serverURL) {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
if let jsonString = String(data: data, encoding: .utf8) {
serverResponse = jsonString
print(jsonString)
}
}
}.resume()
}
return serverResponse
}
I'm trying to set the serverResponse variable from within the if let jsonString but it always returns "No response"(the vars default) and the print function from within the if let jsonString will print out the server response.

DataTask is asynchronous. your function is returning the value before the server request has been completed. You should use a completion handler here.
func getResponse(serverName: String , completion : #escaping (Bool,String?) -> ()) {
let serverURL = "http://" + serverName + ":3000"
if let url = URL(string: serverURL) {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
if let jsonString = String(data: data, encoding: .utf8) {
print(jsonString)
completion(true,jsonString)
}
} else {
completion(false,nil)
}
}.resume()
}
}
Then you can call the above function like this:
getResponse(serverName: "yourServerName") { (isSuccess, response) in
if isSuccess {
self.serverResponse = response ?? ""
} else {
// your api request failed. show alert or whatever you want to inform the user.
}
}

You need to add a completionHandler.
func getResponse(serverName: String, onCompletion: #escaping (String?) -> Void) {
var serverResponse: String = "No Response"
let serverURL = "http://" + serverName + ":3000"
if let url = URL(string: serverURL) {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
if let jsonString = String(data: data, encoding: .utf8) {
serverResponse = jsonString
print(jsonString)
onCompletion(serverResponse)
}
}
}.resume()
}
}

Create serverResponse outside the getResponse(serverName:) method and use property observer didSet to observe the changes in serverResponse, i.e.
var serverResponse: String = "No Response" {
didSet {
print("newValue: ", serverResponse)
//add the code here..
}
}
didSet will be called every time there is any change in serverResponse. So, any code that you want to run after getting the serverResponse from API, write here.
Also, no need to return anything from getResponse(serverName:) method. So, the method will now look like,
func getResponse(serverName: String) {
let serverURL = "http://" + serverName + ":3000"
if let url = URL(string: serverURL) {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
if let jsonString = String(data: data, encoding: .utf8) {
serverResponse = jsonString
print(jsonString)
}
}
}.resume()
}
}

Related

How can I extract jsonString from this method that depends on a task?

var temp = ""
let appid = "**************"
struct WeatherData {
// Object with latitude and longitude to process requests
// from OpenWeatherMap.
var lat, lon: Float
init(latitude: Float, longitude: Float) {
lat = latitude
lon = longitude
}
func retrieve() {
var jsonString = ""
// Send request to OpenWeatherMap.
let requestAddress = "https://api.openweathermap.org/data/2.5/weather?lat=\(lat)&lon=\(lon)&appid=\(appid)"
// Assign the URL to retrieve JSON, with ! dangerous
// operation.
let url = URL(string: requestAddress)!
let urlSession = URLSession(configuration: .ephemeral)
let task = urlSession.dataTask(with: url) {(data, response, error) in
let data = data
jsonString = String(data: data!, encoding: .utf8)!
print(jsonString)
}
task.resume()
}
}
var bangkok = WeatherData(latitude: 13.736717, longitude: 100.523186)
print(bangkok.retrieve())
print("Program running...")
print(temp)
RunLoop.main.run()
The issue I'm having is only print() works but not a return statement or an assignment to a global variable which is what I need. I ultimately want to parse this jsonString into a working dictionary except that I can't get it out of the function at the moment.
I'm depending on a webpage that has only text as body content.
It common to return the result via a completion block:
enum AppError : String, Error
{
case unknownFailure
case requestFailed
...
}
func retrieve(completion: #escaping (Result<Data, AppError>) -> Void)
{
...
let task = urlSession.dataTask(with: url)
{ (data, response, error) in
DispatchQueue.main.async
{
if let statusCode = (response as? HTTPURLResponse)?.statusCode,
statusCode != 200
{
completion(.failure(.requestFailed))
}
else if let data = data
{
completion(.success(data))
}
else if let error = error
{
completion(.failure(.requestFailed))
}
else
{
completion(.failure(.unknownFailure))
}
}
}
task.resume()
}
You need to choose where to convert the received Data to JSON and ultimately to some Codable struct.
You must also decide on how to handle errors and what detail you want to pass to a caller. In the above example I hide the details of dataTask() errors and put them all under app-custom .requestFailed.

Swift http request use urlSession

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

function with dataTask returning a value

I wan't to check if my url statusCode equals to 200, I created a function returning a Boolean if the statusCode equals to 200, I'm using a dataTask, but I don't know how to return a value:
class func checkUrl(urlString: String) -> Bool{
let urlPath: String = urlString
var url: NSURL = NSURL(string: urlPath)!
var request: NSURLRequest = NSURLRequest(url: url as URL)
var response: URLResponse?
let session = Foundation.URLSession.shared
var task = session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
if let error = error {
print(error)
}
if let data = data{
print("data =\(data)")
}
if let response = response {
print("url = \(response.url!)")
print("response = \(response)")
let httpResponse = response as! HTTPURLResponse
print("response code = \(httpResponse.statusCode)")
if httpResponse.statusCode == 200{
return true
} else {
return false
}
}
})
task.resume()
}
The returns in if else are returning an error:
Unexpected non-void return value in void function
in order to return value you should use blocks. Try declaring your function like this:
class func checkUrl(urlString: String, finished: ((isSuccess: Bool)->Void) {
let urlPath: String = urlString
var url: NSURL = NSURL(string: urlPath)!
var request: NSURLRequest = NSURLRequest(url: url as URL)
var response: URLResponse?
let session = Foundation.URLSession.shared
var task = session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
if let error = error {
print(error)
}
if let data = data{
print("data =\(data)")
}
if let response = response {
print("url = \(response.url!)")
print("response = \(response)")
let httpResponse = response as! HTTPURLResponse
print("response code = \(httpResponse.statusCode)")
if httpResponse.statusCode == 200{
finished(isSuccess: true)
} else {
finished(isSuccess: false)
}
}
})
task.resume()
}
And then call it like this:
checkUrl("http://myBestURL.com", finished { isSuccess in
// Handle logic after return here
})
Hope that this will help.
Consider semaphore if you want to keep your original return pattern.
func checkUrl(urlString: String) -> Bool {
if let url = URL(string: fileUrl) {
var result: Bool!
let semaphore = DispatchSemaphore(value: 0) //1. create a counting semaphore
let session = URLSession.shared
session.dataTask(with: url, completionHandler: { (data, response, error) in
result = true //or false in case
semaphore.signal() //3. count it up
}).resume()
semaphore.wait() //2. wait for finished counting
return result
}
return false
}
Swift4, work in my case
Try to add guard let data = data else { return } in dataTask like:
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else { return }
print("get some data")
}.resume()
You're returning a value from a Void function that is the completionHandler closure of dataTask(_:, _:)
Regarding your code, there is something wrong: you can't return that value because it's executed on a different thread, it's an asynchronous operation. Please take a look at this thread: Returning data from async call in Swift function

how to make a HTTP request in Swift

Code
let url = NSURL(string:"http://www.stackoverflow.com")
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) { (data, response, error ) in
if error == nil {
var urlContent = NSString(data: data, encoding: NSUTF8StringEncoding)
println(urlContent)
}
}
Try like this:
let url = NSURL(string:"http://www.stackoverflow.com")!
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) -> Void in
dispatch_async(dispatch_get_main_queue()) {
if let data = data {
if let urlContent = NSString(data: data, encoding: NSUTF8StringEncoding) {
println(urlContent)
}
} else if let error = error {
println(error.description)
}
}
//
}
task.resume()
See the Apple docs for dataTaskWithURL(_:completionHandler:).
After you create the task, you must start it by calling its resume method.

Why my return is nil but if i press the url in chrome/safari, i can get data?

#IBAction func mainButtonnBeTapped(sender: AnyObject) {
let session = NSURLSession.sharedSession()
let request = NSURLRequest(URL: NSURL(string: "http://hq.sinajs.cn/list=sz000609")!)
let task = session.dataTaskWithRequest(request, completionHandler: {
(data, response, error) -> Void in
let myString = NSString(data: data, encoding: NSUTF8StringEncoding)
println("this is my string: \(myString)")
})
task.resume()
}
I am using above url to try to get some data, but the return is nil, but i enter the url in chrome/safari, i can get some data.
I really don't why, can anyone help to explain?
This HTTP server sends a
Content-Type = application/x-javascript; charset=GBK
header field in the response, therefore you get the correct encoding from the textEncodingName property of the NSURLResponse. This can be
converted to a NSStringEncoding.
This is just a translation of the solution presented in https://stackoverflow.com/a/19885463/1187415 to Swift, plus some
simple error checking:
let session = NSURLSession.sharedSession()
let request = NSURLRequest(URL: NSURL(string: "http://hq.sinajs.cn/list=sz000609")!)
let task = session.dataTaskWithRequest(request, completionHandler: {
(data, response, error) -> Void in
var usedEncoding = NSUTF8StringEncoding // Some fallback value
if let encodingName = response.textEncodingName {
let encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding(encodingName))
if encoding != UInt(kCFStringEncodingInvalidId) {
usedEncoding = encoding
}
}
if let myString = NSString(data: data, encoding: usedEncoding) {
println("this is my string: \(myString)")
} else {
println("failed to decode data")
}
})
task.resume()
Output:
this is my string: var hq_str_sz000609="绵世股份, ....
Minor changes are necessary for Swift 2:
let session = NSURLSession.sharedSession()
let request = NSURLRequest(URL: NSURL(string: "http://hq.sinajs.cn/list=sz000609")!)
let task = session.dataTaskWithRequest(request, completionHandler: {
(data, response, error) -> Void in
var usedEncoding = NSUTF8StringEncoding // Some fallback value
if let encodingName = response?.textEncodingName {
let encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding(encodingName))
if encoding != UInt(kCFStringEncodingInvalidId) {
usedEncoding = encoding
}
}
if let myString = String(data: data!, encoding: usedEncoding) {
print("this is my string: \(myString)")
} else {
print("failed to decode data")
}
})
task.resume()
Update for Swift 3:
let session = URLSession.shared
let request = URLRequest(url: URL(string: "http://hq.sinajs.cn/list=sz000609")!)
let task = session.dataTask(with: request, completionHandler: {
(data, response, error) -> Void in
guard let data = data else { return }
var usedEncoding = String.Encoding.utf8 // Some fallback value
if let encodingName = response?.textEncodingName {
let encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding(encodingName as CFString))
if encoding != UInt(kCFStringEncodingInvalidId) {
usedEncoding = String.Encoding(rawValue: encoding)
}
}
if let myString = String(data: data, encoding: usedEncoding) {
print("this is my string: \(myString)")
} else {
print("failed to decode data")
}
})
task.resume()
The text you try to get is probably not UTF-8, try with another encoding, like this for example:
let myString = NSString(data: data, encoding: NSASCIIStringEncoding)
Update: read Martin R's answer for how to find the right encoding.