NSLock.lock() executed while lock already held? - swift

I'm reviewing some Alamofire sample Retrier code:
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: #escaping RequestRetryCompletion) {
lock.lock() ; defer { lock.unlock() }
if let response = request.task.response as? HTTPURLResponse, response.statusCode == 401 {
requestsToRetry.append(completion)
if !isRefreshing {
refreshTokens { [weak self] succeeded, accessToken, refreshToken in
guard let strongSelf = self else { return }
strongSelf.lock.lock() ; defer { strongSelf.lock.unlock() }
...
}
}
} else {
completion(false, 0.0)
}
}
I don't follow how you can have lock.lock() on the first line of the function and then also have that same line strongSelf.lock.lock() within the closure passed to refreshTokens.
If the first lock is not released until the end of the should method when the defer unlock is executed then how does the the second strongSelf.lock.lock() successfully execute while the first lock is held?

The trailing closure of refreshTokens, where this second call to lock()/unlock() is called, runs asynchronously. This is because the closure is #escaping and is called from within a responseJSON inside the refreshTokens routine. So the should method will have performed its deferred unlock by the time the closure of refreshTokens is actually called.
Having said that, this isn't the most elegant code that I've seen, where the utility of the lock is unclear and the risk of deadlocking is so dependent upon the implementation details of other routines. It looks like it's OK here, but I don't blame you for raising an eyebrow at it.

Related

Sync closure needs to wait for async code

I am developing an app that uses MPRemoteCommandCenter to control audio reproduction from the lock screen.
The code is inside a #MainActor view-model, part of a SwiftUI project.
The initial code that I need to incorporate is:
commandCenter.playCommand.isEnabled = true
commandCenter.playCommand.addTarget { [unowned self] event in
if !self.isPlaying {
self.playOrPause()
return .success
}
return .commandFailed
}
I have tried this:
commandCenter.playCommand.isEnabled = true
commandCenter.playCommand.addTarget { [unowned self] event in
return await MainActor.run {
if !self.isPlaying {
self.playOrPause()
return .success
}
return .commandFailed
}
}
But still it doesn't compile.
I understand why: the callback method is not marked to be async. Still it expects a value, that I can return only from async code, because I need to wait to re-enter into the main thread.
Can anyone please help me?
You cannot ask for a sync function to wait for an async. You can have however an async function receiving the returned values from other async closures using withCheckedContinuation().
In your case, this is how it might work:
let isSuccessfullyCompleted: Bool = await withCheckedContinuation { continuation in
commandCenter.playCommand.isEnabled = true
commandCenter.playCommand.addTarget { [unowned self] event in
if !self.isPlaying {
self.playOrPause()
continuation.resume(returning: true)
} else {
continuation.resume(returning: false)
}
}
}
With the code above, isSuccessfullyCompleted will wait for the closure to complete and return a value, before resuming the code. The variable doesn't need to be a Bool, but the code above needs to run in an async function.

executeJavascript does not call completionHandler when inside a DispatchQueue

I've written a function that's supposed to return the HTML string that makes up a WKWebview. However, the completion handler is never called, and the project freezes indefinitely. I've also already adopted the WKScriptMessageHandler protocol so that's not the problem.
public func getHTML() -> String {
var result = ""
let group = DispatchGroup()
group.enter()
DispatchQueue.main.async {
self.webView.evaluateJavaScript("document.documentElement.outerHTML.toString()", completionHandler: {(html: Any?, error: Error?) in
if (error != nil) {
print(error!)
}
result = html as! String
group.leave()
})
}
group.wait()
print("done waiting")
return result
}
I've found several examples on how to get the html, like here, but I don't want to merely print, I want to be able to return its value. I'm not experienced with DispatchQueues, but I do know for that WKWebView's evaluateJavaScript completion handler always runs on the main thread

Swift: Recursive async func with completion handler that does not get called

I need to fetch large amounts of data from an endpoint in an async way. The API endpoint serves a predefined amount of data at a time. After the first request I must check to see if I get a "next" url from the response and visit that link in order to continue the download. This recursive behaviour continues until all available data has been served, in other words paging functionality (HAL links). At this point I have implemented a func that download recursively, however: problem is that the final completion handler does not seem to get called.
Demo code: The ThingsApi is a class that encapsulates the actual API call. The important thing is that this class has an initial url and during recursion will get specific url's to visit asynchronously. I call the downloadThings() func and need to get notified when it is finished. It works if I leave recursion out of the equation. But when recursion is in play then nothing!
I have created a simplified version of the code that illustrate the logic and can be pasted directly into the Playground. The currentPage and pages var's are just there to demo the flow. The last print() statement does not get called. Leave the currentPage += 1 to experience the problem and set currentPage += 6 to avoid recursion. Clearly I am missing out of some fundamental concept here. Anyone?
import UIKit
let pages = 5
var currentPage = 0
class ThingsApi {
var url: URL?
var next: URL?
init(from url: URL) {
self.url = url
}
init() {
self.url = URL(string: "https://whatever.org")
}
func get(completion: #escaping (Data?, HTTPURLResponse?, Error?) -> Void) {
// *** Greatly simplified
// Essentially: use URLSession.shared.dataTask and download data async.
// When done, call the completion handler.
// Simulate that the download will take 1 second.
sleep(1)
completion(nil, nil, nil)
}
}
func downloadThings(url: URL? = nil, completion: #escaping (Bool, Error?, String?) -> Void) {
var thingsApi: ThingsApi
if let url = url {
// The ThingsApi will use the next url (retrieved from previous call).
thingsApi = ThingsApi(from: url)
} else {
// The ThingsApi will use the default url.
thingsApi = ThingsApi()
}
thingsApi.get(completion: { (data, response, error) in
if let error = error {
completion(false, error, "We have nothing")
} else {
// *** Greatly simplified
// Parse the data and save to db.
// Simulate that the thingsApi.next will have a value 5 times.
currentPage += 1
if currentPage <= pages {
thingsApi.next = URL(string: "https://whatever.org?page=\(currentPage)")
}
if let next = thingsApi.next {
// Continue downloading things recursivly.
downloadThings(url: next) { (success, error, feedback) in
guard success else {
completion(false, error, "failed")
return
}
}
} else {
print("We are done")
completion(true, nil, "done")
print("I am sure of it")
}
}
})
}
downloadThings { (success, error, feedback) in
guard success else {
print("downloadThings() failed")
return
}
// THIS DOES NOT GET EXECUTED!
print("All your things have been downloaded")
}
It seems like this is simply a case of "you forgot to call it yourself" :)
In this if statement right here:
if let next = thingsApi.next {
// Continue downloading things recursivly.
downloadThings(url: next) { (success, error, feedback) in
guard success else {
completion(false, error, "failed")
return
}
}
} else {
print("We are done")
completion(true, nil, "done")
print("I am sure of it")
}
Think about what happens on the outermost call to downloadThings, and execution goes into the if branch, and the download is successful. completion is never called!
You should call completion after the guard statement!

Completion Handler Causes Deinit To Not Be Called

I have a network task that contains a completion handler, allowing me to determine if and when the task completed successfully;
func performUpload(url: URL, completionHandler: #escaping (_ response: Bool) -> ()) {
photoSave.savePhotoRemote(assetURL: url) { response in
completionHandler(response)
}
}
I call this function from another UIView, by using the following;
UploadHelper.shared.performUpload(url: fileAssetPath) { response in
if response {
// do stuff
}
}
What I am noticing is that when I capture the response and do stuff, the class that called this function will never deinit. However, if I change my function to the following;
UploadHelper.shared.performUpload(url: fileAssetPath) { response in }
and don't do anything with the response, the class will deinit.
What am I doing wrong with my function? I would like to capture the response accordingly, but not at the expense of my class not being released from memory.
You've got a retain cycle. Break it. Change
UploadHelper.shared.performUpload(url: fileAssetPath) { response in
if response {
// do stuff
}
}
to
UploadHelper.shared.performUpload(url: fileAssetPath) { [unowned self] response in
if response {
// do stuff
}
}

Should I call completion handler when self is deallocated

We all probably have used the pattern below. This may not matter much, I am just curious should I still call completion handler when self no longer exists?
var uid: String
func asyncTask(completion: #escaping(Result)->()) {
anotherAsyncTask() { [weak self] (result) in
guard let uid = self?.uid else {
completion(.error) // Should I call this???
return
}
// consume result
}
}
Since self is de-initated then it makes no sense to call
completion(.error) // Should I call this???
as the result is already on the fly here return suffices