4

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)
Nishant Bhindi
  • 2,242
  • 8
  • 21
tim
  • 163
  • 1
  • 2
  • 11

6 Answers6

14

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)
    }
}
vadian
  • 274,689
  • 30
  • 353
  • 361
  • 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? – tim May 23 '17 at 10:17
  • 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 May 23 '17 at 10:21
  • 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. – vadian May 23 '17 at 10:34
  • 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? – tim May 23 '17 at 21:57
3

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)")
}
D4ttatraya
  • 3,344
  • 1
  • 28
  • 50
2

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

})
ZAFAR007
  • 3,049
  • 1
  • 34
  • 45
1

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)
}
shallowThought
  • 19,212
  • 9
  • 65
  • 112
  • 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 May 23 '17 at 10:15
  • No. `self.getHistoryKeys(searchTerm)` returns immediately, but your download takes time. You have to wait for it. – shallowThought May 23 '17 at 10:21
0

From what I can see, it should be

private func getHistoryKeys(searchterm: String, completion: @escaping (HistoryKey) -> ())

Also completion(.failure("no historyKeyDictionary")) or completion(.success(keys)) should be used instead of return

Tristan Beaton
  • 1,742
  • 2
  • 14
  • 25
0
 enum HistoryKey {
    case success([String: String])
    case failure(String)
 }

 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()
        }

        if keys.isEmpty {
            completion(.failure("no historyKeyDictionary"))
        } else {
            completion(.success(keys))
        }
    }
    task.resume()    
} // End of func

Something like that. Change @escaping declaration and do a completion instead of return.

Hope it helps.

Vlad Pulichev
  • 3,162
  • 2
  • 20
  • 34