I’m trying to convert escaping closure
code to async-await
code in a certain file. More specifically, I’m trying to not use the completionHandler
way of using the URLSession.shared.dataTask
line of code in this file (which is specifically the line of code “URLSession.shared.dataTask(with: request) {
” in the file that I’m working in).
In this file I’m making the change in, I’m getting the error: “Cannot find 'data' in scope
” for line of code: “let json = try JSONSerialization.jsonObject(with: data!, options: [])
”.
I think I’m getting this error because I never defined “data
” further up in the file. I wasn’t getting this problem in the file that used an escaping closure
for the overall code in the file’s function, and used a completionHandler
for the “URLSession.shared.dataTask
” line of code (which is specifically “URLSession.shared.dataTask(with: request) { (data, response, error) in
”) in that file. Therefore, in the file that I’m working in, and that I have this error message in, I tried some solution attempts by creating the “data
” variable right after the “URLSession.shared.dataTask(with: request) {
” line of code, and tried two different variable assignments, however none of them worked. These solution attempts and the errors I got for them are in code snippets further below in this post. I made these solution attempts in the file that I’m working in, and that I have this error message in, which is File1-GetsDataNotUsingEscapingClosureAndNotUsingCompletionHandlerForURLSession.shared.dataTaskCode.swift
, and they are included in that file further below, and are commented out.
I was expecting that since the data
variable would have been created before the current line of code that has the error message (this line of code being “let json = try JSONSerialization.jsonObject(with: data!, options: [])
” in File1-GetsDataNotUsingEscapingClosureAndNotUsingCompletionHandlerForURLSession.shared.dataTaskCode.swift
) the code might be correct, and work, and the error message would go away.
How do I solve this error?
I’ve included the file that I’m currently working in that has the error mentioned above, and that doesn’t use an escaping closure
way of doing things, and doesn’t use a completionHandler
in the URLSession.shared.dataTask
line of code (which is specifically “URLSession.shared.dataTask(with: request) {
”), which is named File1-GetsDataNotUsingEscapingClosureAndNotUsingCompletionHandlerForURLSession.shared.dataTaskCode.swift
. I’ve also included the solution attempts which are labeled “Solution Attempt Version 1
” etc., and that include the errors that showed up after running the code with that solution attempt in this file.
I’ve also included the original file that uses the escaping closure
way of doing things, of which uses a completionHandler
in the URLSession.shared.dataTask
line of code (which is specifically “URLSession.shared.dataTask(with: request) { (data, response, error) in
”), which is named File2-GetsDataUsingEscapingClosureAndCompletionHandlerForURLSession.shared.dataTaskCode.swift
.
Code:
File1-GetsDataNotUsingEscapingClosureAndNotUsingCompletionHandlerForURLSession.shared.dataTaskCode.swift
:
import Foundation
import UIKit
import CoreLocation
extension UIViewController {
func retrieveSelectedRestaurantDetailViewInfo(
selected_restaurant_business_ID: String) async throws -> SelectedRestaurantDetailViewInfoNotUsingCodable? {
// MARK: Make API Call
let apiKey = "API key"
/// Create URL
let baseURL =
"https://api.yelp.com/v3/businesses/(selected_restaurant_business_ID)"
let url = URL(string: baseURL)
/// Creating Request
var request = URLRequest(url: url!)
request.setValue("Bearer (apiKey)", forHTTPHeaderField: "Authorization")
request.httpMethod = "GET"
///Initialize session and task
//Code for not using completionHandler:
URLSession.shared.dataTask(with: request) {
//Solution Attempt 1: Version of code for creating "data" variable, where the variable assignment is set to "Data".
//Got the error message for the above line of code "URLSession.shared.dataTask(with: request) {", saying "Contextual type for closure argument list expects 3 arguments, which cannot be implicitly ignored" with a "Fix" button below, and the text to the left of the "Fix" button being: "Insert '_,_,_ in '".
// var data = Data
//Solution Attempt 2: Version of code for creating "data" variable, where the variable assignment is set to "Data?".
//Got the same error message as Solution Attempt 1; Got the error message for the above line of code "URLSession.shared.dataTask(with: request) {", saying "Contextual type for closure argument list expects 3 arguments, which cannot be implicitly ignored" with a "Fix" button below, and the text to the left of the "Fix" button being: "Insert '_,_,_ in '".
// var data = Data?
do {
/// Read data as JSON
//The line of code below is where I'm getting the error message: "Cannot find 'data'" in.
let json = try JSONSerialization.jsonObject(with: data!, options: [])
/// Main dictionary
guard let responseDictionary = json as? NSDictionary else {return}
//Code for accessing restaraunt detail view info, and assigning it to selectedRestarauntDetailViewInfo view model thats a struct.
var selectedVenue = SelectedRestaurantDetailViewInfoNotUsingCodable()
//Rest of code for accessing restaraunt detail view info, and assigning it to seelctedRestarauntDetailViewInfo view model thats a struct.
selectedVenue.name = responseDictionary.value(forKey: "name") as? String
//*Rest of code for getting business info including address, hours, etc..*
return selectedVenue
} catch {
print("Caught error")
}
}.resume()
}
}
File2-GetsDataUsingEscapingClosureAndCompletionHandlerForURLSession.shared.dataTaskCode.swift
:
import Foundation
import UIKit
import CoreLocation
extension UIViewController {
func retrieveSelectedRestaurantDetailViewInfo(
selected_restaurant_business_ID: String,
completionHandler: @escaping (SelectedRestaurantDetailViewInfoNotUsingCodable?, Error?) -> Void) {
// MARK: Make API Call
let apiKey = "API key"
/// Create URL
let baseURL =
"https://api.yelp.com/v3/businesses/(selected_restaurant_business_ID)"
let url = URL(string: baseURL)
/// Creating Request
var request = URLRequest(url: url!)
request.setValue("Bearer (apiKey)", forHTTPHeaderField: "Authorization")
request.httpMethod = "GET"
///Initialize session and task
//Code for using completionHandler:
URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
completionHandler(nil, error)
}
do {
/// Read data as JSON
let json = try JSONSerialization.jsonObject(with: data!, options: [])
/// Main dictionary
guard let responseDictionary = json as? NSDictionary else {return}
//Code for accessing restaraunt detail view info, and assigning it to selectedRestarauntDetailViewInfo view model thats a struct.
var selectedVenue = SelectedRestaurantDetailViewInfoNotUsingCodable()
//Rest of code for accessing restaraunt detail view info, and assigning it to seelctedRestarauntDetailViewInfo view model thats a struct.
selectedVenue.name = responseDictionary.value(forKey: "name") as? String
//*Rest of code for getting business info including address, hours, etc..*
completionHandler(selectedVenue, nil)
} catch {
print("Caught error")
}
}.resume()
}
}
Thanks!
2
Answers
The conversion from the old style
dataTask
goes from:to:
There is no longer a task involved that needs to be resumed. The data and response are direct return values (as a tuple).
This works since you are calling this from inside a function declared as
async throws
so if the call todata
throws and error it will be propagated out to the caller.In the working version URLSession passes the data object into your closure when it calls it:
which is why you can access it. This is evident in the method signature
In the async/await world the data is returned directly from the method, but you aren’t referencing the returned data. You need to adapt your code to:
Then you will be able to use the provided
data
is the subsequent code as you would have done previously in your closure.