Completion handler swift 3 return a variable from function

20,165

Solution 1

Two issues:

  • The completion handler passes a HistoryKey instance and has no return value so the signature must be the other way round.
  • The call of the completion handler must be inside the completion block of the data task.

To be able to parse the received data outside the completion block return the data on success

enum ConnectionResult {
   case success(Data)
   case failure(Error)
}

private func getHistoryKeys(searchterm: String, completion: @escaping (ConnectionResult) -> ()) {
   let url = PubmedAPI.createEsearchURL(searchString: searchterm)
   let task = session.dataTask(with: url) { (data, response, error) in
       if let error = error {
          completion(.failure(error))
       } else {
          completion(.success(data!))
       }
  }
  task.resume()
}

and call it

getHistoryKeys(searchterm: String) { connectionResult in 
    switch connectionResult {
       case .success(let data): 
           let myParser = XMLParser(data: data)
           myParser.delegate = self
           myParser.parse()
           // get the parsed data from the delegate methods

       case .failure(let error): print(error)
    }
}

Solution 2

You are not using completion block.
Use it like:

private func getHistoryKeys(searchterm: String, completion: @escaping (_ keys: Array) -> Void) {
    //do the magic
    completion(keys)
}

Then you can call this function as:

getHistoryKeys(searchterm: "str") { (keys) in 
    print("\(keys)")
}

Solution 3

Swift 4.2

enum HistoryKey {
    case success([String:String])
    case failure(String)
}

func myFunction(str: String, completionHandler: @escaping (HistoryKey) -> ()){
     completion(.success([String:String]))
     //OR
     completion(.failure(""))
}

myFunction(str: String) { result in 
    switch result {
       case .success(let data): break;
       case .failure(let error): break;
    }
}

OR

func myFunction(str: String, completionHandler: @escaping (String) -> ()){
     completionHandler("")
} 

myFunction(str: "someThing", completionHandler: {(str) in

})

Solution 4

Return the result as an argument in the completion handler:

private func getHistoryKeys(searchterm: String, completion: @escaping (result: HistoryKey) -> Void) {
    let url = PubmedAPI.createEsearchURL(searchString: searchterm)
    let request = URLRequest.init(url: url as URL)
    let task = session.dataTask(with: request) { (data, response, error) in

        if let theData = data{
            let myParser = XMLParser.init(data: theData)
            myParser.delegate = self
            myParser.parse()
        }

        //DispatchQueue.main.async { // if you want to do UI stuff dispatch calls to completion() on the main queue
        if keys.isEmpty {
            completion(.failure("no historyKeyDictionary"))
        } else{
            completion(.success(keys))
        }
        //}
    }
    task.resume()   
}

And call it like this:

getHistoryKeys("searchMe") { (result: HistoryKey) in
    print(result)
}
Share:
20,165
tim
Author by

tim

Updated on November 22, 2020

Comments

  • tim
    tim over 3 years

    I am confused surrounding the syntax for a completion handler in swift 3.

    In the function below, after parsing an xml file from a web service call, it should return a variable (an array [String:String]).
    My attempt is below, but obviously it is incorrect.

      enum HistoryKey {
      case success([String:String])
      case failure(String)
     }
    
     private func getHistoryKeys(searchterm: String, completion: @escaping () -> HistoryKey) {
        let url = PubmedAPI.createEsearchURL(searchString: searchterm)
        let request = URLRequest.init(url: url as URL)
        let task = session.dataTask(with: request) { (data, response, error) in
    
            if let theData = data{
                let myParser = XMLParser.init(data: theData)
                myParser.delegate = self
                myParser.parse()
            }
        }
        task.resume()
    
        if keys.isEmpty {
            return .failure("no historyKeyDictionary")
        }else{
            return .success(keys)
        }
    
    }// End of func
    

    I want to use this function as follows

     let result = self.getHistoryKeys(searchTerm)
    
  • tim
    tim almost 7 years
    Sorry, i have just added an edit to give an idea on how i would like to use the function. Would your solution work for above? because your return value is void
  • tim
    tim almost 7 years
    Sorry, i have just added an edit to give an idea on how i would like to use the function. Would your solution work for above?
  • shallowThought
    shallowThought almost 7 years
    No. self.getHistoryKeys(searchTerm) returns immediately, but your download takes time. You have to wait for it.
  • vadian
    vadian almost 7 years
    No it does not. Either you have to return the received Data because the parser works asynchronously and parse the XML outside the function or you have to assign the completion handler to a variable to get a strong reference and call the completion handler from the XMLParser delegate method. Btw: your array in the enum case is a dictionary.
  • vadian
    vadian almost 7 years
    PS: I updated the answer to pass the received data in the completion handler. Put the code to handle the result of the XML parser into the parser delegate method.
  • tim
    tim almost 7 years
    I understand your solution - obtaining the variable from the delegate method e.g parserDidEndDocument. But, could you show me how to call the completion handler from parserDidEndDocument for instance?