how to pass variable value to outside of URLSession async - swift 3 - swift

I have this code :
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil {
print(error!)
return
}
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
let getDetail = parseJSON["detail"] as? String
returnDetail = getDetail!.base64Decoded()
} // parse json end
} // do end
catch {
print(error)
}
} // let task end
returnDetail has been defined previously. I did anything to set returnDetail value to getDetail!.base64Decoded() but it only works inside let task = ...
How can I pass it to the outer scope?

You have several methods to tackle the issue of returning a value from inside an asynchronous function. One of them is to wrap the asynchronous network call inside a function and make it return a completionHandler.
Some general advice: don't use force unwrapping unless you are 100% sure that your optional value won't be nil. With network requests, the data can be nil even if there's no error, so never force unwrap data, use safe unwrapping with if let or guard let. Don't use .mutableContainers in Swift when parsing a JSON value, since it has no effect. The mutability of the parsed JSON object is decided by using the let or var keyword to declare the variable holding it. Also don't use NSDictionary, use its native Swift counterpart, Dictionary ([String:Any] is a shorthand for the type Dictionary<String,Any>).
func getDetail(withRequest request: URLRequest, withCompletion completion: #escaping (String?, Error?) -> Void) {
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil {
completion(nil, error)
return
}
else if let data = data {
do {
guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String:Any] else {completion(nil, nil);return}
guard let details = json["detail"] as? String else {completion(nil, nil);return}
completion(details, nil)
}
catch {
completion(nil, error)
}
}
}
task.resume()
}
Then you can call this function by
getDetail(withRequest: request, withCompletion: { detail, error in
if error != nil {
//handle error
} else if detail = detail {
//You can use detail here
}
})

I would suggest to use a completion handler.
func foo(withCompletion completion: (String?, Error?) -> Void) {
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil {
completion(nil, error)
return
}
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
let details = parseJSON["detail"] as? String
completion(details, nil)
} // parse json end
} // do end
catch {
completion(nil, error)
}
} // let task end
}

I think, you use CallBack(Clourse) of Swift to return data when getDetail have data.

Related

How to write a completion handler in a separated block of code in Swift with parameters out of scope

I was trying to make my code clean and decouple the code below, I want to remove the trailing completion handler from it and write the completion handler in another blck of code.
func uploadMarcasMetodoNovo(_ id_resenha: Int) {
let resenhaDados:ResDadoModel = db.readDadosResenhaById(id_resenha)
let resenhaMarcas:[ResMarcasModel] = db.readResMarca(id_resenha)
// this for loop runs about for 7 times
for marca in resenhaMarcas {
contadorUploadMarcas = contadorUploadMarcas + 1
myUploadGroupMarcas.enter()
jsonRequestUploadImagemGrafica = ResMarcasModel.createJsonMarcaResenha(marca, resenhaDados.IdGedave )
let json: [String: Any] = jsonRequestUploadImagemGrafica
guard let jsonData = try? JSONSerialization.data(withJSONObject: json) else {
print("guard jsonData error")
return
}
let requestImagemGrafica = requestUploadFotos(jsonData)
let task = URLSession.shared.dataTask(with: requestImagemGrafica) { data, response, error in
if let error = error {
print("error: \(String(describing: error))")
return
}
print("data")
guard let returnData = String(data: data!, encoding: .utf8) else {
print("returnData guard fail")
return
}
print("returnData")
print(returnData)
self.confirmStatusEnviada(marca)
self.myUploadGroupMarcas.leave()
print("end TASK")
}
task.resume()
}
myUploadGroupMarcas.notify(queue: DispatchQueue.main) {
print("myUploadGroupMarcas notify")
// more code ...
}
}
This is the part that I write creating a separated completion handler
let myCompletionHandler: (Data?, URLResponse?, Error?) -> Void = {
(data, response, error) in
if let error = error {
print("error: \(String(describing: error))")
return
}
print("data")
guard let returnData = String(data: data!, encoding: .utf8) else {
print("returnData guard fail")
return
}
self.confirmStatusEnviada(marca)
self.myUploadGroupMarcas.leave()
}
but it won't work because in the last two lines of code are used paramters that are out of scope. The parameter "marca" and the parameter "myUploadGroupMarcas" are out of scope. Is there a way to use these parameters inside the separated completion handler function?
Ok based on our comments above, this is the route I would try: Write a short completion handler that calls your longer completion handler, passing the variables that are out of scope.
let task = URLSession.shared.dataTask(with: requestImagemGrafica) { data, response, error in
myCompletionHandler(data, response, error, marca, myUploadGroupMarcas)
}
Then you add two parameters to your completion handler in the function definition:
let myCompletionHandler: (Data?, URLResponse?, Error?, MarcaClass, myUploadGroupMarcas) -> Void
Obviously you need to replace MarcaClass with the actual class type that is marca and myUploadGroupMarcas seems to be a function so you'd need to write an appropriate parameter type for that.

Swift scoping outside of a function

I have a singleton URLSession that is parsing the response data into a dictionary. I want to use a single value from that dictionary in a subsequent piece of code, but cannot figure out how to pass the value out from the scope it's currently in.
Here is the code as it stands now:
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard error == nil else {
debugPrint ("error: \(error!)")
return
}
guard let content = data else {
debugPrint("No data")
return
}
guard let json = (try? JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers)) as? [String: Any] else {
debugPrint("Not containing JSON")
return
}
if let idToken = json["id_token"] as? String {
let privateToken = idToken;
debugPrint("Gotten json response dictionary is \(idToken)")
}
}
task.resume()
return privateToken
Currently there is an IDE error on return privateToken saying that I am using an unresolved identifier: privateToken.
How can I take the string idToken and return it as a privateToken for use elsewhere?
Could you use a completion handler like:
func getPrivateToken(completion: #escaping(String) -> (), failure: #escaping (Error) -> ()) {
URLSession.shared.dataTask(with: request) { data, response, error in
guard error == nil else {
debugPrint ("error: \(error!)")
failure(error)
return
}
guard let content = data else {
debugPrint("No data")
failure(NSError(domain: "Your error message here.", code: 401, userInfo: nil))
return
}
guard let json = (try? JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers)) as? [String: Any] else {
debugPrint("Not containing JSON")
failure(NSError(domain: "Your error message here.", code: 401, userInfo: nil))
return
}
if let idToken = json["id_token"] as? String {
completion(idToken)
debugPrint("Gotten json response dictionary is \(idToken)")
}
}.resume()
}
And use it like so:
func exampleFunction() {
self.getPrivateToken(completion: { (token) in
// Do what ever you need with the token here.
print("ID token is: \(token)")
}) { (error) in
// Present error here
}
}

Swift: Setting the text of a label in a URLSessionTask

So I am downloading a JSON file using a URLRequest().
I parse through it in order to get a specific string and I want to set the text of a label I have in my ViewController to that specific string.
I use a CompletionHandler in order to retrieve the function that gets the JSON file from another Swift file.
Here is the code of calling the function and setting the label:
class SecondViewController: UIViewController {
tr = TransportServices()
tr.getLyftData(origin: originstring, destination: destinationstring){ json in
//Parsing JSON in order to get specific data
self.lyftlabel.text = stringexample
}
}
and here is the code of getting the JSON
func getLyftData(origin: String, destination: String, completionHandler: #escaping ([String: Any]) -> ()){
let urlrequest = URLRequest(url: URL(string: urlstring)!)
let config = URLSessionConfiguration.default
let sessions = URLSession(configuration: config)
let task = sessions.dataTask(with: urlrequest) {(data, response, error) in
guard error == nil else {
print(error!)
return
}
guard let responseData = data else {
print("error, did not receive data")
return
}
do {
if let json = try JSONSerialization.jsonObject(with: responseData, options: []) as? [String: Any]{
completionHandler(json)
}
}
catch {
print("Error with URL Request")
}
}
task.resume()
}
This does the job, but in a very slow manner. I know that there is a runtime issue because UILabel.text must be set from main thread only, but I don't know any other way to fix it. Please help.
If you want to set label text in main thread use this:
DispatchQueue.main.async {
self.lyftlabel.text = stringexample
}

JSON is not convertible to void (Openweather map API)

I am calling Openweather map API using Swift and from the response I need to return a particular value as string.
However when I try to return the value error comes as JSON is not convertible to string.
func callWeatherServ(name:String, completion:(Dictionary<String,AnyObject>) -> Void)
{
var baseUrl: String = "http://api.openweathermap.org/data/2.5/weather"
var url: String = "\(baseUrl)?q=\(name)"
let finalUrl: NSURL = NSURL(string: url)!
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(finalUrl, completionHandler: {data, response, error -> Void in
if error != nil
{
// If there is an error in the web request, print it to the console
println(error.localizedDescription)
}
var err: NSError?
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as! NSDictionary
if err != nil
{
// If there is an error parsing JSON, print it to the console
println("JSON Error \(err!.localizedDescription)")
}
let json = JSON(jsonResult)
println("response is \(json) ")
var weathername = json["weather"][0]["main"]
if (weathername != nil)
{
return weathername
}
})
task.resume()
}
I get that since we have used closure whose return type void so we should use completion handler. But I am not aware how we can do that.
Also how we can call the function if we pass completion handler as parameter?
If you want to keep using SwiftyJSON as in your example, here's how to do it:
change the type of the completion handler from a dictionary to the JSON type used by SwiftyJSON.
then wrap the value you want to "return" in the handler.
then call your method as in my example, with a trailing closure
Swift 2
func callWeatherServ(name:String, completion:(object: JSON) -> Void) {
let baseUrl: String = "http://api.openweathermap.org/data/2.5/weather"
let url: String = "\(baseUrl)?q=\(name)"
if let finalUrl = NSURL(string: url) {
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(finalUrl, completionHandler: {data, response, error -> Void in
if let error = error {
print(error.localizedDescription)
} else {
if let data = data {
let json = JSON(data: data)
print("response is \(json) ")
completion(object: json["weather"][0]["main"])
} else {
print("No data")
}
}
})
task.resume()
}
}
Call the method:
callWeatherServ("paris") { (object) in
// here you get back your JSON object
print(object)
}
Note that you were parsing your data twice, with NSJSONSerialization and with SwiftyJSON, so I've removed the unnecessary NSJSONSerialization part.
Original Swift 1 version
func callWeatherServ(name:String, completion:(object: JSON) -> Void)
{
var baseUrl: String = "http://api.openweathermap.org/data/2.5/weather"
var url: String = "\(baseUrl)?q=\(name)"
let finalUrl: NSURL = NSURL(string: url)!
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(finalUrl, completionHandler: {data, response, error -> Void in
if error != nil
{
// If there is an error in the web request, print it to the console
println(error.localizedDescription)
}
var err: NSError?
let json = JSON(data: data, options: NSJSONReadingOptions.allZeros, error: &err)
println("response is \(json) ")
var weathername = json["weather"][0]["main"]
if (weathername != nil)
{
completion(object: weathername)
}
})
task.resume()
}
Call the method:
callWeatherServ("paris", completion: { (object) -> Void in
println(object) // "Clear"
})
Implement completion handler from where you are calling this method and use the string at that place only no need to return the string.
You can directly use it from the completion handle by implemet it in caller function

Invalid conversion from throwing function of type

I am trying to convert my code over to Swift 2 and I am having issues with this last function. I get the follow error on the declaration of task:
Invalid conversion from throwing function of type ... to non throwing type.
func performSearch(searchTerm: String){
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(urlForQuery(searchTerm)!, completionHandler: { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
do {
let result: NSDictionary = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers) as! NSDictionary
}
let results: AnyObject? = result.objectForKey("results")
if let testVar: NSMutableArray = results as? NSMutableArray {
// Uncomment this to print all feeds
//println(testVar)
self.searchedPodcasts.removeAllObjects()
self.searchedPodcasts.addObjectsFromArray(testVar as [AnyObject])
NSNotificationCenter.defaultCenter().postNotificationName("SearchPerformed", object: self)
}
})
task!.resume()
}
I have tried the following but it doesn't work either and it also means I can't call task.resume for some reason:
func performSearch(searchTerm: String){
let session = NSURLSession.sharedSession()
do {
let task = session.dataTaskWithURL(urlForQuery(searchTerm)!, completionHandler: { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
do {
let result: NSDictionary = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers) as! NSDictionary
}
let results: AnyObject? = result.objectForKey("results")
if let testVar: NSMutableArray = results as? NSMutableArray {
// Uncomment this to print all feeds
//println(testVar)
self.searchedPodcasts.removeAllObjects()
self.searchedPodcasts.addObjectsFromArray(testVar as [AnyObject])
NSNotificationCenter.defaultCenter().postNotificationName("SearchPerformed", object: self)
}
})
} catch {
print("error)
}
task!.resume()
}
What can I do to fix it?
Edit: Tried this but the same error is still returned.
func test(searchTerm: String) {
let session = NSURLSession.sharedSession()
do {
let task = try session.dataTaskWithURL(urlForQuery(searchTerm)!, completionHandler: { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
let result: NSDictionary = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers) as! NSDictionary
})
} catch {
print("hello")
}
}
Try adding a catch block to your do-try-catch sentence:
do {
let result: NSDictionary = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers) as! NSDictionary
}
catch {
print("error");
}
You'll also need to declare the result variable outside or move more code inside the do block.
For more info about exception handling in Swift 2 check out this guide.
Apple replaced NSError with ErrorType in Swift 2.
So replace your own explicit usage of NSError with ErrorType and you don't need the additional try catch block suggested in the other answer and you can express your error handling with much less code.