Unexpected defer behaviours - swift

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

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

Swift Dispatch Main

I have code that I am repeating a bunch. I want to update things on the main thread, UI stuff.
public func closeGraph() {
DispatchQueue.main.sync{_closeGraph()}
}
That's easy enough, but what if user interaction triggers it and I am already on the main thread. ++ungood.
public func closeGraph() {
if Thread.isMainThread {
_closeGraph()
} else {
DispatchQueue.main.sync{_closeGraph()}
}
}
Oops openGraph() throws...
public func openGraph() throws {
do {
if Thread.isMainThread {
try _openGraph()
} else {
try DispatchQueue.main.sync{try _openGraph()}
}
} catch {
throw error
}
}
Is there a better way to this so i don't have to copy paste it everywhere?
You can extract your check to a generic function like this:
public func mainQueue(execute block: () throws -> Void) rethrows {
if Thread.isMainThread {
try block()
} else {
try DispatchQueue.main.sync{ try block() }
}
}
then if your _closeGraph() function doesn't throw any error, your code will be like this:
public func closeGraph() {
mainQueue(execute: _closeGraph)
}
and if your _closeGraph() function throws, your code will be like this:
public func closeGraph() {
do {
try mainQueue(execute: _closeGraph)
} catch {
print(error)
}
}
As you can see you will need to write the handling of the throwing function every time.
Update: If you have a generic error handling in mind, you can hide the do-catch statement in mainQueue(execute:) function like this:
public func mainQueue(execute block: () throws -> Void) {
do {
if Thread.isMainThread {
try block()
} else {
try DispatchQueue.main.sync{ try block() }
}
} catch {
print(error)
}
}
then calling this function will be the same whether the _closeGraph() function is throwing or not:
public func closeGraph() {
mainQueue(execute: _closeGraph)
}

Passing an element back with swift issue

I am having some issues and am just really lost so this is a last resort.
I have this function:
func fetchGoogleData(forLocation: CLLocation) {
//guard let location = currentLocation else { return }
googleClient.getGooglePlacesData(location: forLocation) { (response) in
self.listResults(places: response.results)
}
}
Which calls this functions:
func listResults(places: [Place]) {
dump(places)
}
However, dumping responses.result or places return a null value but looping through places returns all the correct values.
Is there a way to return response.results as the value without calling this function?
You can use completion block (Closures) to return your results. Let say your response.results is of type [Place]. Then your function should be like below
func fetchGoogleData(forLocation: CLLocation, completion: #escaping [Place]->Void) {
//guard let location = currentLocation else { return }
googleClient.getGooglePlacesData(location: forLocation) { (response) in
completion(response.results)
}
}
and call it as below:
self.fetchGoogleData(forLocation: currentPosition) { (places) in
//print(places)
}

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.

ReactiveCocoa ignore nil in Swift

I'm using ReactiveCocoa in many places around my app. I've build a check to skip nil values as followed:
func subscribeNextAs<T>(nextClosure:(T) -> ()) -> RACDisposable {
return self.subscribeNext {
(next: AnyObject!) -> () in
self.errorLogCastNext(next, withClosure: nextClosure)
}
}
private func errorLogCastNext<T>(next:AnyObject!, withClosure nextClosure:(T) -> ()){
if let nextAsT = next as? T {
nextClosure(nextAsT)
} else {
DDLogError("ERROR: Could not cast! \(next)", level: logLevel, asynchronous: false)
}
}
This helps to log failed castings, but will also fail for nil values.
In Objective-C you would simply call ignore as followed:
[[RACObserve(self, maybeNilProperty) ignore:nil] subscribeNext:^(id x) {
// x can't be nil
}];
But in Swift the ignore property can't be nil. Any idea to use ignore in Swift?
Finally, with help from powerj1984 I created this method for now:
extension RACSignal {
func ignoreNil() -> RACSignal {
return self.filter({ (innerValue) -> Bool in
return innerValue != nil
})
}
}