Wait until the method completes [duplicate] - swift

This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 3 years ago.
I want the getPhotos class method to be executed to the end, and only then the code after. Otherwise, the photos are not displayed in the collection. How can this be achieved?
// MARK: - Text Field Delegate
extension CollectionViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
collectionManager.getPhotos(searchTerm: textField.text!)
print("finish")
self.collectionView?.reloadData()
return true
}
}
Implementation of the getPhotos method:
func getPhotos(searchTerm: String) -> [FlickrPhoto] {
loader.searchFlickr(for: searchTerm) { searchResults in
switch searchResults {
case .error(let error):
print("Error Searching: \(error)")
case .results(let results):
self.searchPhotos = results.searchResults
}
}
return searchPhotos
}

you can use this closure:
func getPhotos(searchTerm: String, onCompletion: #escaping ([FlickrPhoto]) -> Void) {
loader.searchFlickr(for: searchTerm) { searchResults in
switch searchResults {
case .error(let error):
print("Error Searching: \(error)")
onCompletion([FlickrPhoto]())
case .results(let results):
self.searchPhotos = results.searchResults
onCompletion(results.searchResults)
}
}
return searchPhotos
}
and on completion call:
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
collectionManager.getPhotos(searchTerm: textField.text!) { results
[weak self] in
print("finish")
self.searchPhotos = results
self?.collectionView?.reloadData()
}
return true
}

You need to add a completion handler to your getPhotos method instead of the return. This is because getPhotos is asynchronous, so the completion handler is kind of what needs to be done after it finishes.
func getPhotos(searchTerm: String, completion: #escaping [FlickrPhoto]? -> ()) {
loader.searchFlickr(for: searchTerm) { searchResults in
switch searchResults {
case .error(let error):
print("Error Searching: \(error)")
case .results(let results):
self.searchPhotos = results.searchResults
}
}
return searchPhotos
}
and then
getPhotos(searchTerm: String, completion: { _ in
// The code inside this block will be executed when getPhotos finishes
})
However, you may not get the effect that you expect, as the return in textFieldShouldReturn will probably be executed before the completion.
Can I ask what are you trying to do with this?
EDITED:
If what you want is to be sure pics have been loaded before dismissing the textfield, you can add a flag/bool in your viewController
var didLoadPics = false
and then when you add this line to your method
self.searchPhotos = results.searchResults
didLoadPics = true
so your textFieldShouldReturn would only need
return didLoadPics

Related

SWIFT TASK CONTINUATION MISUSE: leaked its continuation - for delegate?

I'm trying to extend my class with async/await capabilities, but at run-time there is an error in the console:
SWIFT TASK CONTINUATION MISUSE: query(_:) leaked its continuation!
Below is the class I'm trying to add the continuation to which uses a delegate:
class LocalSearch: NSObject, MKLocalSearchCompleterDelegate {
private let completer: MKLocalSearchCompleter
private var completionContinuation: CheckedContinuation<[MKLocalSearchCompletion], Error>?
init() {
completer = MKLocalSearchCompleter()
super.init()
completer.delegate = self
}
func query(_ value: String) async throws -> [MKLocalSearchCompletion] {
try await withCheckedThrowingContinuation { continuation in
completionContinuation = continuation
guard !value.isEmpty else {
completionContinuation?.resume(returning: [])
completionContinuation = nil
return
}
completer.queryFragment = value
}
}
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
completionContinuation?.resume(returning: completer.results)
completionContinuation = nil
}
func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
completionContinuation?.resume(throwing: error)
completionContinuation = nil
}
}
This is how I use it:
let localSearch = LocalSearch()
do {
let results = try await localSearch.query("toront")
print(results)
} catch {
print(error)
}
What am I doing wrong or is there a better way to achieve this?
This message appears if a continuation you created via withCheckedContinuation, or withCheckedThrowingContinuation doesn't report success or failure before being discarded. This is will lead to resource leaking:
Resuming from a continuation more than once is undefined behavior. Never resuming leaves the task in a suspended state indefinitely, and leaks any associated resources. CheckedContinuation logs a message if either of these invariants is violated.
Excerpt taken from the documentation for CheckedContinuation (emphasis mine).
Here are possible causes for this to happen:
not all code paths resume the continuation, e.g. there is an if/guard/case that exits the scope without instructing the continuation to report success/failure
class Searcher {
func search(for query: String) async throws -> [String] {
await withCheckedContinuation { continuation in
someFunctionCall(withCompletion: { [weak self] in
guard let `self` = self else {
// if `result` doesn't have the expected value, the continuation
// will never report completion
return
}
continuation.resume(returning: something)
})
}
}
}
an "old"-style async function doesn't call the completion closure on all paths; this is a less obvious reason, and sometimes a harder to debug one:
class Searcher {
private let internalSearcher = InternalSearcher()
func search(for query: String) async throws -> [String] {
await withCheckedContinuation { continuation in
internalSearcher.search(query: query) { result in
// everything fine here
continuation.resume(returning: result)
}
}
}
}
class InternalSearcher {
func search(query: String, completion: #escaping ([String]) -> Void {
guard !query.isEmpty else {
return
// legit precondition check, however in this case,
// the completion is not called, meaning that the
// upstream function call will imediately discard
// the continuation, without instructing it to report completion
}
// perform the actual search, report the results
}
}
the continuation is stored as a property when a function is called; this means that if a second function call happens while the first one is in progress, then the first completion will be overwritten, meaning it will never report completion:
class Searcher {
var continuation: CheckedContinuation<[String], Error>?
func search(for query: String) async throws -> [String] {
try await withCheckedTrowingContinuation { continuation in
// note how a second call to `search` will overwrite the
// previous continuation, in case the delegate method was
// not yet called
self.continuation = continuation
// trigger the searching mechanism
}
}
func delegateMethod(results: [String]) {
self.continuation.resume(returning: results)
self.continuation = nil
}
}
#1 and #2 usually happen when dealing with functions that accept completion callbacks, while #3 usually happens when dealing with delegate methods, since in that case, we need to store the continuation somewhere outside the async function scope, in order to access it from the delegate methods.
Bottom line - try to make sure that a continuation reports completion on all possible code paths, otherwise, the continuation will indefinitely block the async call, leading to the task associated with that async call leaking its associated resources.
In your case, what likely happened is that a second query() call occurred before the first call had a chance to finish.
And in that case, the first continuation got discarded without reporting completion, meaning the first caller never continued the execution after the try await query() call, and this is not ok at all.
The following piece of code needs to be fixed, in order not to overwrite a pending continuation:
func query(_ value: String) async throws -> [MKLocalSearchCompletion] {
try await withCheckedThrowingContinuation { continuation in
completionContinuation = continuation
One quick solution would be to store an array of continuations, resume all continuations in the delegate methods, and clear the array afterward. Also, in your specific case, you could simply extract the validation out of the continuation code, as you are allowed to synchronously return/throw, even when in an async function:
func query(_ value: String) async throws -> [MKLocalSearchCompletion] {
guard !value.isEmpty else {
return []
}
return try await withCheckedThrowingContinuation { continuation in
continuations.append(continuation)
completer.queryFragment = value
}
}
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
continuations.forEach { $0.resume(returning: completer.results) }
continuations.removeAll()
}
func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
continuations.forEach { $0.resume(throwing: error) }
continuations.removeAll()
}
I'd also strongly recommend converting your class to an actor, in order to avoid data races, regardless if you store one continuation, like now, or you use an array. The reason is that the continuation property is consumed from multiple threads and at some point you might end up with two threads concurrently accessing/writing the property.
I think the problem is here -
func query(_ value: String) async throws -> [MKLocalSearchCompletion] {
try await withCheckedThrowingContinuation { continuation in
// storing into a variable makes this continuation instance outlive the scope of it
// In other words, it leaks OR escapes the scope
// This is same as why we need to add #escaping attribute for callback functions arguments
// those are either stored in variables like this
// or passed to other functions (escaping scope of current function)
completionContinuation = continuation
// Try commenting above line, the warning should go away
// And your code will stop working as well :)
// How to design this component is other question.
}
}
UPDATE
import MapKit
class LocalSearch: NSObject, MKLocalSearchCompleterDelegate {
typealias Completion = (_ results: [MKLocalSearchCompletion]?, _ error: Error?) -> Void
private let completer: MKLocalSearchCompleter
private var completion: Completion?
override init() {
completer = MKLocalSearchCompleter()
super.init()
completer.delegate = self
}
func query(_ value: String, completion: #escaping Completion) {
self.completion = completion
completer.queryFragment = value
}
func query(_ value: String) async throws -> [MKLocalSearchCompletion] {
try await withCheckedThrowingContinuation { continuation in
guard !value.isEmpty else {
continuation.resume(returning: [])
return
}
self.query(value, completion: { (results, error) in
if let error = error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: results ?? [])
}
})
}
}
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
completion?(completer.results, nil)
}
func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
completion?(nil, error)
}
}

How can I get a list of repositories in the view model before the table view is refreshed?

I'm trying to build an app that searches GitHub repositories. And I'm using MVVM and Moya. When I call a method to search I get table view reloaded before a server responds.
So the app flow should be:
A user types in a search query into the search bar. In searchBarSearchButtonClicked(_:) of SearchViewController called my method to search. The method is inside SearchViewModel. So view model's method triggers RepositoryService and then NetworkService.
I put some print statements to see an order of execution. And in the console, I got: 2 3 4 (Here the table view is refreshing) 1. I've tried to use GCD in different places, also I tried to use barriers. At the end of the day, the table view is still refreshing before print(1) is called.
SearchViewController:
extension SearchViewController: UISearchBarDelegate {
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
guard let query = searchBar.text, query.count > 2 else { return }
viewModel.searchRepositories(withQuery: query) { [weak self] in
guard let self = self else { return }
print(4)
print("Reloading...")
self.tableView.reloadData()
}
}
}
SearchViewModel:
var repositories = [Repository]()
func searchRepositories(withQuery query: String, completion: #escaping () -> Void) {
repositories = repositoryService.searchRepositories(withQuery: query)
print(3)
completion()
}
RepositoryService:
private var repositories = [Repository]()
func searchRepositories(withQuery query: String) -> [Repository] {
networkService?.searchRepositories(withQuery: query) { [weak self] repositories in
guard let self = self, let repositories = repositories else { return }
self.repositories += repositories
}
print(2)
return self.repositories
}
NetworkService:
func searchRepositories(withQuery query: String,
completionHandler: #escaping (([Repository]?) -> Void)) {
provider?.request(.searchRepo(query: query)) { result in
switch result {
case .success(let response):
do {
let repositories = try response.map(SearchResults<Repository>.self)
print(1)
completionHandler(repositories.items)
} catch let error {
print(error.localizedDescription)
}
case .failure(let error):
print(error)
}
}
}
Your issue is basically that you are not handling async requests properly.
Consider the code:
var repositories = [Repository]()
func searchRepositories(withQuery query: String, completion: #escaping () -> Void) {
repositories = repositoryService.searchRepositories(withQuery: query) // here we are going to create a network request in the background, which takes time.
print(3)
completion() // when you call this, your network request is still trying, so when your tableView refreshes... the data hasn't returned yet.
}
As you can see in the comments, what will happen here is:
call to network
print(3)
completion -> reload Table View
network responses with data -> you do nothing.
What you should be doing...
func searchRepositories(withQuery query: String, completion: #escaping (SomeResponseType?, Error?) -> Void) {
repositories = repositoryService.searchRepositories(withQuery: query) { response in
completion(results, error)
}
}
You have followed this pattern elsewhere but you need to cover all async requests.
Same here:
func searchRepositories(withQuery query: String) -> [Repository] {
networkService?.searchRepositories(withQuery: query) { [weak self] repositories in
guard let self = self, let repositories = repositories else { return }
self.repositories += repositories
}
print(2)
return self.repositories
}
return self.repositories will be called instantly after the network call starts and the network request would not of had time to return the data.
Couple of useful resources on the topic:
Grand central dispatch in Swift 4 - RW
URLSession tutorial - RW

My double-closure function is not working

I have two async function which send requests to my server.
DispatchQueue.global(qos: .userInitiated).async {
weak var weakself = self
self.unregisterPushServer(token: token!) { [weak self] success in
print("0")
if success {
print("1")
weakself?.unregisterPushInfoKeychain(token: token!)
print("2")
if let this = self {
print("PLEASE")
weakself?.registerPushServer(token: token!) { [weak this] success in
print("3")
if success {
print("4")
this?.registerPushInfoKeychain()
print("5")
}
}
}
print("success")
}
}
}
And the functions are
private func registerPushServer(token: String, completion: #escaping (Bool) -> ()) {
request() { (data, error) in
if data != nil {
completion(true)
} else {
completion(false)
}
}
private func unregisterPushServer(token: String, completion: #escaping (Bool) -> ()) {
request2() { (data, error) in
if data != nil {
completion(true)
} else {
completion(false)
}
}
But in console,
0
1
2
success
not seemed to executes codes after my PLEASE sign.
Why is my code is not working?
I first thought that the problem was about the queue, but it was not.
You don't need this line:
weak var weakself = self
By including [weak self] in the closure's capture list, self automatically becomes weak.
Try and replace the instances of weakself with just self.
I'm also thinking you may not even need the if let this = self condition.
I hope this helps.
OK, the problem was not in this code.
When I call this function, I did it like this.
func messaging(_ messaging: Messaging, didRefreshRegistrationToken fcmToken: String) {
print("Firebase registration token: \(fcmToken)")
let pushService = PushService()
pushService.updateRegistrationStatus(token: fcmToken)
}
the
pushService.updateRegistrationStatus(token: fcmToken)
was the function which contains the code I asked above.
In this situation, the function updateRegistrationStatus is not guaranteed because pushService itself is released by ARC when messaging(...) function block is end.
class AppDelegate: UIResponder, UIApplicationDelegate {
let pushService = PushService()
...
func messaging(_ messaging: Messaging, didRefreshRegistrationToken fcmToken: String) {
print("Firebase registration token: \(fcmToken)")
self.pushService.updateRegistrationStatus(token: fcmToken)
}
}
Now the pushService object is not released because it is declared as a global variable.

How to load UI with escaping closures and async calls?

I've written a function called 'configureLabels()' that is supposed to make a 'GET' request and retrieve a value which is then supposed to be set as the text for a label. The request is async so I thought I would be able to use an escaping closure to update the UI when the request is finished being made. I'm relatively new to coding, so I am not sure what I've done wrong. I'd really appreciate anyone's help in figuring this out.
This is the code containing the 'configureLabels()' method:
import UIKit
import SwiftyJSON
class ItemDetailViewController: UIViewController {
#IBOutlet weak var numberOfFiberGrams: UILabel!
var ndbnoResults = [JSON]()
var ndbno = ""
let requestManager = RequestManager()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
configureLabels()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func configureLabels() {
requestManager.ndbnoRequest(ndbno: ndbno) { (results) in
let json = JSON(results)
let fiber = json["food"]["nutrients"][7].dictionaryValue
for (key, value) in fiber {
if key == "value" {
self.numberOfFiberGrams.text = "\(value.stringValue)"
} else {
self.numberOfFiberGrams.text = "Fail"
}
}
}
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
And here is the code containing the function that 'configureLabels()' calls:
func ndbnoRequest(ndbno: String, apiKey: String, completionHandler: #escaping (_ results: JSON?) -> Void) {
Alamofire.request("https://api.nal.usda.gov/ndb/V2/reports?ndbno=\(ndbno)&type=f&format=json&api_key=\(apiKey)", method: .get).validate().responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
completionHandler(json)
print("successful ndbno request")
case .failure(let error):
completionHandler(nil)
print(error)
}
}
}
Your code looks ok only issue I have find with your code is you are not calling the completionHandler in failure part, You need to always call completion block so it will gave you idea have you got response or not as of your completionHandler argument is type of [JSON] as of your not having response in failure part you are not calling completionHandler in it. What you can do is make it optional and call completionHandler with nil argument in case of failure.
func ndbnoRequest(ndbno: String, completionHandler: #escaping (_ results: [JSON]?) -> Void) {
let parameters = ["api_key": "tIgopGnvNSP7YJOQ17lGVwazeYI1TVhXNBA2Et9W", "format": "json", "ndbno": "\(ndbno)"]
Alamofire.request("https://api.nal.usda.gov/ndb/reports/V2", method: .get, parameters: parameters).responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
let ndbnoResults = json["foods"].arrayValue
completionHandler(ndbnoResults)
print("successful ndbno request")
case .failure(let error):
completionHandler(nil)
print("error with ndbno request")
}
}
}
Now call it this way and wrapped the optional in completion block so you can confirm you get response.
requestManager.ndbnoRequest(ndbno: ndbno) { (results) in
if let result = results {
let json = JSON(result)
let fiber = json["food"]["nutrients"][7].dictionaryValue
for (key, value) in fiber {
if key == "value" {
self.numberOfFiberGrams.text = "\(value.stringValue)"
} else {
self.numberOfFiberGrams.text = "Fail"
}
}
}
else {
print("Problem to get response")
}
}
Everything related to UI must be ALWAYS done on the main thread.
So try this:
DispatchQueue.main.async {
let json = JSON(results)
let fiber = json["food"]["nutrients"][7].dictionaryValue
for (key, value) in fiber {
if key == "value" {
self.numberOfFiberGrams.text = "\(value.stringValue)"
} else {
self.numberOfFiberGrams.text = "Fail"
}
}
}
P.S. I agree with Nirav about failure callback - you should handle it too. And I strongly recommend you to give functions and vars more readable and meaningful names, not "ndbnoRequest" and "ndbno". You won't remember what does it mean in few weeks :)

Unexpected defer behaviours

I have a function that does processing asynchronously:
func something(completion: [Something] -> Void) {
dispatch_async(queue) {
...
dispatch_async(dispatch_get_main_queue()) {
completion(something)
}
}
}
I thought it would be wise to use defer to guarantee that completion gets called every time, so I tried this:
func something(completion: [Something] -> Void) {
dispatch_async(queue) {
...
defer {
dispatch_async(dispatch_get_main_queue()) {
completion(something)
}
}
}
}
Working well. Then I tried to use a guard statement within the asynchronous dispatch that always failed, to see if defer will activate. It didn't:
func something(completion: [Something] -> Void) {
dispatch_async(queue) {
...
guard let shouldFail = ... else { return }
defer {
dispatch_async(dispatch_get_main_queue()) {
completion(something)
}
}
}
}
defer would not be called. Why?
Because you are using defer after returning. The compiler doesn't know that you specified defer instructions (because it returned already and didn't see any defer instructions in that point, so the next lines are not fired up). If you'd move defer {} before the guard, then it will be called.
guard will return before even getting to the defer. Try doing it the other way around:
func something(completion: [Something] -> Void) {
dispatch_async(queue) {
...
defer {
dispatch_async(dispatch_get_main_queue()) {
completion(something)
}
}
guard let shouldFail = ... else { return }
}
}