Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to return value from a Closure in Swift?

So I am using fabric plugin/Twitter-kit to use twitter api in my application. I want to get the image URL of profile picture of a celebrity. here is my code below.

func getImageURL(celebrity :String) -> String{

    var imageURL = ""
    let client = TWTRAPIClient()
    let statusesShowEndpoint = userSearch
    let params = ["q": celebrity,
                  "page" : "1",
                  "count" : "1"
                  ]
    var clientError : NSError?

    let request = client.URLRequestWithMethod("GET", URL: statusesShowEndpoint, parameters: params, error: &clientError)

     client.sendTwitterRequest(request) { (response, data, connectionError) -> Void in
        if connectionError != nil {
            print("Error: \(connectionError)")
        }

        do {
            let json = try NSJSONSerialization.JSONObjectWithData(data!, options: [])
            print("json: \(json)")

            let profileImageURL: String? = json[0].valueForKey("profile_image_url_https") as? String

            imageURL =  self.cropTheUrlOfImage(profileImageURL!)
            print(imageURL)

            //how do i return the value back. confused ?????
            return imageURL

        } catch let jsonError as NSError {
            print("json error: \(jsonError.localizedDescription)")
        }
    }

    return imageURL

}

I don't know how to return value because the method finishes before the closure executes completely. I am new to swift, Any help is appreciated.

I want to use this method in cellForRowIndexPath in tableview to dowload image from imageURl.

like image 219
Ajay Avatar asked Jul 10 '16 22:07

Ajay


People also ask

Can closure return value Swift?

Swift's closures can return values as well as take parameters, and you can use those closures in functions. Even better, those functions can also return values, but it's easy for your brain to get a bit fuzzy here because there's a lot of syntax.

Can a closure return a value?

Closures can also return values, and they are written similarly to parameters: you write them inside your closure, directly before the in keyword.

How do you escape a closure in Swift?

In Swift, a closure is non-escaping by default. If a closure can escape the function, you'll need to annotate its function parameter with the @escaping keyword. This is what happens in the code at the top of this section. var onTaskFinished:(() -> Void)?

How do you call a closure in Swift?

Understanding closure syntax in Swift For closures, we must always write down the return type even when the closure doesn't return anything. Instead of -> Void or "returns Void ", this type specifies -> () or "returns empty tuple". In Swift, Void is a type alias for an empty tuple.


2 Answers

You need to return from an @escaping closure. Change the function

func getImageURL(celebrity: String) -> String {

}

to

func getImageURL(celebrity: String, completion: @escaping(String)->()) {

      // your code
      completion(imgURL)
}

You can use it as given below

getImageURL(celebrity: String) { (imgURL) in

      self.imgURL = imgURL // Using self as the closure is running in background
}

Here is an example how I write multiple methods with closures for completion.

class ServiceManager: NSObject {

//  Static Instance variable for Singleton
static var sharedSessionManager = ServiceManager()

//  Function to execute GET request and pass data from escaping closure
func executeGetRequest(with urlString: String, completion: @escaping (Data?) -> ()) {

    let url = URL.init(string: urlString)
    let urlRequest = URLRequest(url: url!)

    URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
        //  Log errors (if any)
        if error != nil {
            print(error.debugDescription)
        } else {
            //  Passing the data from closure to the calling method
            completion(data)
        }
    }.resume()  // Starting the dataTask
}

//  Function to perform a task - Calls executeGetRequest(with urlString:) and receives data from the closure.
func downloadMovies(from urlString: String, completion: @escaping ([Movie]) -> ()) {
    //  Calling executeGetRequest(with:)
    executeGetRequest(with: urlString) { (data) in  // Data received from closure
        do {
            //  JSON parsing
            let responseDict = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any]
            if let results = responseDict!["results"] as? [[String:Any]] {
                var movies = [Movie]()
                for obj in results {
                    let movie = Movie(movieDict: obj)
                    movies.append(movie)
                }
                //  Passing parsed JSON data from closure to the calling method.
                completion(movies)
            }
        } catch {
            print("ERROR: could not retrieve response")
        }
    }
  }
}

Below is the example how I use it to pass values.

ServiceManager.sharedSessionManager.downloadMovies(from: urlBase) { (movies : [Movie]) in   // Object received from closure
      self.movies = movies
      DispatchQueue.main.async {
            //  Updating UI on main queue
            self.movieCollectionView.reloadData()
      }
}

I hope this helps anybody looking for the same solution.

like image 82
caffieneToCode Avatar answered Oct 10 '22 08:10

caffieneToCode


You are correct, sendTwitterRequest will return after your function has already returned, so there is no way for the outer function to return an imageURL.

Instead, in the closure, take the return value that you want in the cell and store it somewhere (in a member variable) and have the tableView update it itself (e.g. with tableView.reloadData()).

This will cause it to get the cell again (cellForRow ...). Change the implementation to use the member variable where you stored the value from the call.

like image 39
Lou Franco Avatar answered Oct 10 '22 09:10

Lou Franco