0

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!

cg1000
  • 7
  • 4

2 Answers2

0

The conversion from the old style dataTask goes from:

URLSession.shared.dataTask(with: request) { (data, response, error) in
    if let data {
        // do stuff with the non-optional data
    else if let error {
    }
}.resume()

to:

let (data, response) = try await URLSession.shared.data(for: request)
// do stuff with the non-optional data and/or response

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 to data throws and error it will be propagated out to the caller.

HangarRash
  • 7,314
  • 5
  • 5
  • 32
  • Thank you this worked, and it makes sense! I ended up changing the indentation of the `do` block, and had to change the "`with:`" to "`for:`" in "`(with: request)`" in the solution line of code, but this worked in regard to the error going away. The only thing is other errors in other lines of code below this changed line of code came up after making these changes, which I'll make a subsequent post about. Thanks for the help! – cg1000 Mar 03 '23 at 19:48
  • Fixed my typo with the `data(with:` to `data(for:`. You don't need a `do/catch` block unless you specifically want to handle certain errors within the function instead of letting them propagate to the caller. – HangarRash Mar 03 '23 at 19:53
  • Thank you! Ah I see, this makes sense, thank you for the information about the `do / catch` block! – cg1000 Mar 04 '23 at 04:38
0

In the working version URLSession passes the data object into your closure when it calls it:

URLSession.shared.dataTask(with: request) { (data, response, error) in

which is why you can access it. This is evident in the method signature

func dataTask(
    with url: URL,
    completionHandler: @escaping @Sendable (Data?, URLResponse?, Error?) -> Void
) -> URLSessionDataTask

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:

let (data, response) = try await URLSession.shared.data(with: request)

Then you will be able to use the provided data is the subsequent code as you would have done previously in your closure.

flanker
  • 3,840
  • 1
  • 12
  • 20
  • This worked, and it makes sense! Only thing is other errors came up after making this change that are below the changed line of code (as mentioned in my other reply comment). I'll make a subsequent post about this, but thanks for the help! – cg1000 Mar 03 '23 at 19:52