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.
Related
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!
I am writing the iOS application using swift 4.2. I am making a service call to logout user.
I need to know where to use main thread (DispatchQueue.main.async).
Here is my code:
private func handleLogoutCellTap() {
logoutUseCase?.logout() { [weak self] (result) in
guard let self = self else { return }
switch result {
case let (.success(didLogout)):
didLogout ? self.handleSuccessfullLogout() : self.handleLogoutError(with: nil)
case let (.failure(error)):
self.handleLogoutError(with: error)
}
}
}
logoutUseCase?.logout() makes a service call and returns #escaping completion. Should I use DispatchQueue.main.async on this whole handleLogoutCellTap() function or just in a handling segment?
Move the control to main thread wherever you're updating the UI after receiving the response of logout.
If handleSuccessfullLogout() and handleLogoutError(with:) methods perform any UI operation, you can embed the whole switch statement in DispatchQueue.main.async, i,e.
private func handleLogoutCellTap() {
logoutUseCase?.logout() { [weak self] (result) in
guard let self = self else { return }
DispatchQueue.main.async { //here.....
switch result {
//rest of the code....
}
}
}
}
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
I want to use synchronized thread using Alamofire.
I am using like this code for synchronization.
let queue1 = DispatchQueue(label: "queue1")
let queue2 = DispatchQueue(label: "queue2")
queue1.async {
self.dataFromServer.getData()
}
queue2.async {
//check if success to get the data from server
while(!self.dataFromServer.resultData){}
DispatchQueue.main.async {
// do something on screen
}
}
...
func getData() {
Alamofire.request(url).responseJSON { response in
if response.error == nil {
// get data
self.resultData = true
}else{
// error
}
}
}
I want to do something after get data from server using Alamofire.
Is this correct?
If this is not good, please tell me about the synchronization please.
Kind regards.
There is another and probably the good way of doing it by using closures.
// You don't need to create queue and it asynchronously, since Alamofire calls are async.
// You can remove queue statement
let queue1 = DispatchQueue(label: "queue1")
queue1.async {
self.dataFromServer.getData(completionClosure: { (resultData) in
if resultData {
}
else {
}
})
}
Then in your function have a parameter that takes a closure and call it with success or failure boolean.
func getData(completionClosure: (Bool) -> Void) {
Alamofire.request(url).responseJSON { response in
if response.error == nil {
// get data
completionClosure(true)
}
else{
completionClosure(false)
}
}
}
you can call
dispatch_sync
instead of async or you can call other API in the success block of first API
Modify the getData() function to return block so that you get callback on the sucess of the api call
func getData(sucess:((Void) -> Void)?) {
Alamofire.request(url).responseJSON { response in
if response.error == nil {
// get data
sucess!()
self.resultData = true
}else{
// error
}
}
}
Do whatever you want to do after the api response has received in the sucess callback of the getData() function
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.