Extra trailing closure passed in call - Task showing error - swift

I'm pretty new to Swift, and attempting to convert a program in c# to swift, and I'm receiving the following error whenever I try to use a Task.
I tried to make the button's action function async, and ran into more errors, and read that one should use a wrapper function. Thus the following code is my attempt..
#IBAction func noteBrowser_button(_ sender: Any){
Task{
await load_noteBrowserScreen()
}
}
func load_noteBrowserScreen()async throws {
do{
if ( try await Server.serverVersion() >= Server.retrieveCustomerLimit )
{
//screen to change to
let screen = storyboard!.instantiateViewController(identifier: "Note_Browser") as UIViewController?
//show screen
show(screen!, sender: self)
}
else
{
Popup.showMessage(parent: Popup.parent,
title: "Server Update Required",
msg: "Your server needs updated to enable this feature.")
}
}
catch {}
}

So it turns out the answer to the problem above was conflicting class names. There is the built-in Task with swift, and there was a Task.swift class, which was throwing a conflict, and by renaming that class removed the above errors.
Thanks everyone for your time offering suggestions.

Related

How to cancel an `async` function with cancellable type returned from `async` operation initiation

I need to support cancellation of a function that returns an object that can be cancelled after initiation. In my case, the requester class is in a 3rd party library that I can't modify.
actor MyActor {
...
func doSomething() async throws -> ResultData {
var requestHandle: Handle?
return try await withTaskCancellationHandler {
requestHandle?.cancel() // COMPILE ERROR: "Reference to captured var 'requestHandle' in concurrently-executing code"
} operation: {
return try await withCheckedThrowingContinuation{ continuation in
requestHandle = requester.start() { result, error in
if let error = error
continuation.resume(throwing: error)
} else {
let myResultData = ResultData(result)
continuation.resume(returning: myResultData)
}
}
}
}
}
...
}
I have reviewed other SO questions and this thread: https://forums.swift.org/t/how-to-use-withtaskcancellationhandler-properly/54341/4
There are cases that are very similar, but not quite the same. This code won't compile because of this error:
"Reference to captured var 'requestHandle' in concurrently-executing code"
I assume the compiler is trying to protect me from using the requestHandle before it's initialized. But I'm not sure how else to work around this problem. The other examples shown in the Swift Forum discussion thread all seem to have a pattern where the requester object can be initialized before calling its start function.
I also tried to save the requestHandle as a class variable, but I got a different compile error at the same location:
Actor-isolated property 'profileHandle' can not be referenced from a
Sendable closure
You said:
I assume the compiler is trying to protect me from using the requestHandle before it’s initialized.
Or, more accurately, it is simply protecting you against a race. You need to synchronize your interaction with your “requester” and that Handle.
But I’m not sure how else to work around this problem. The other examples shown in the Swift Forum discussion thread all seem to have a pattern where the requester object can be initialized before calling its start function.
Yes, that is precisely what you should do. Unfortunately, you haven’t shared where your requester is being initialized or how it was implemented, so it is hard for us to comment on your particular situation.
But the fundamental issue is that you need to synchronize your start and cancel. So if your requester doesn’t already do that, you should wrap it in an object that provides that thread-safe interaction. The standard way to do that in Swift concurrency is with an actor.
For example, let us imagine that you are wrapping a network request. To synchronize your access with this, you can create an actor:
actor ResponseDataRequest {
private var handle: Handle?
func start(completion: #Sendable #escaping (Data?, Error?) -> Void) {
// start it and save handle for cancelation, e.g.,
handle = requestor.start(...)
}
func cancel() {
handle?.cancel()
}
}
That wraps the starting and canceling of a network request in an actor. Then you can do things like:
func doSomething() async throws -> ResultData {
let responseDataRequest = ResponseDataRequest()
return try await withTaskCancellationHandler {
Task { await responseDataRequest.cancel() }
} operation: {
return try await withCheckedThrowingContinuation { continuation in
Task {
await responseDataRequest.start { result, error in
if let error = error {
continuation.resume(throwing: error)
} else {
let resultData = ResultData(result)
continuation.resume(returning: resultData)
}
}
}
}
}
}
You obviously can shift to unsafe continuations when you have verified that everything is working with your checked continuations.
After reviewing the Swift discussion thread again, I see you can do this:
...
var requestHandle: Handle?
let onCancel = { profileHandle?.cancel() }
return try await withTaskCancellationHandler {
onCancel()
}
...

Swift Concurrency - How to get a Result from a task not working

I tried to put this example into a simple project + ViewController but can't get it to compile
https://www.hackingwithswift.com/quick-start/concurrency/how-to-get-a-result-from-a-task
I'm trying to call fetchQuotes() from an IBAction via a button tap, however because fetchQuotes is marked async, I'm getting an error.
I know that I can wrap the call to fetchQuotes() in a task:
Task {
fetchQuotes()
}
, but this doesn't make sense to me since fetchQuotes is already creating tasks.
Can anyone advise?
Here is my code:
// https://www.hackingwithswift.com/quick-start/concurrency/how-to-get-a-result-from-a-task
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func buttonTap(_ sender: Any) {
fetchQuotes()
}
func fetchQuotes() async {
let downloadTask = Task { () -> String in
let url = URL(string: "https://hws.dev/quotes.txt")!
let data: Data
do {
(data, _) = try await URLSession.shared.data(from: url)
} catch {
throw LoadError.fetchFailed
}
if let string = String(data: data, encoding: .utf8) {
return string
} else {
throw LoadError.decodeFailed
}
}
let result = await downloadTask.result
do {
let string = try result.get()
print(string)
} catch LoadError.fetchFailed {
print("Unable to fetch the quotes.")
} catch LoadError.decodeFailed {
print("Unable to convert quotes to text.")
} catch {
print("Unknown error.")
}
}
}
enum LoadError: Error {
case fetchFailed, decodeFailed
}
Here's a screenshot of my code + Xcode error:
this doesn't make sense to me since fetchQuotes is already creating tasks
What fetchQuotes is internally doing is an implementation detail, it might as well not explicitly create any tasks, or not even be doing async calls.
Now, the async part of the method declaration tells the compiler the method might suspend the caller thread, thus any caller needs to provide an async context when calling the method. Note that I said might suspend not will suspend, this is a subtle, but important difference.
The tasks created by the method are valid only during the method execution, so if you need to be able to call the method from a non-async context you'll have to create new tasks.
However, you can tidy-up your code by providing a non-async overload:
func fetchQuotes() {
Task {
await fetchQuotes()
}
}
, which can be called from your #IBAction
#IBAction func buttonTap(_ sender: Any) {
fetchQuotes()
}
There's no escaping out of this, if you want to be able to call an async function from a non-async context, then you'll need a Task, regardless on how the called method is implemented. Structured concurrency tasks are not reusable.
but this doesn't make sense to me since fetchQuotes is already creating tasks
Irrelevant; you've internalized a false rule, or failed to internalize the real rule. This has nothing to do with who is "creating tasks". It has to do with the real rule, the most fundamental rule of Swift's async/await, which is:
You can only call an async method from within an async context.
Well, the method buttonTap is not an async method, so your call to fetchQuotes is not being made within an async context. That's illegal, and the compiler stops you dead.
But if you wrap the call to fetchQuotes in a Task initializer, that is an async context. Success!
Basically, the effect you're seeing is what I call the regress of doom. Since every call to an async method must be in an async context, but since your events from Cocoa, such as an IBAction, are not async, how can your code ever call an async method? The answer is a Task initializer: it gives you a place to get started.

iOS: "An instance of SPAudioStreamingController is already in use."

i'm developping app using spotify-iOS-SDK, i have succesfully connect my app to Spotify and the audio is playing, but the problem is: When i close my PlaySongViewController, my app will be crash
"An instance of SPAudioStreamingController is already in use."
unless i stop my spotifyPlayer with this code after i logout
var spotifyPlayer: SPTAudioStreamingController?
#IBAction func closeView(_ sender: UIButton) {
print("close view")
self.dismiss(animated: true, completion: nil)
self.spotifyPlayer?.logout()
invalidateTimers()
}
func audioStreamingDidLogout(_ audioStreaming: SPTAudioStreamingController!) {
print("after logout")
try! self.spotifyPlayer?.stop()
}
The problem is continue if i close my ViewController directly before this code is working properly
self.spotifyPlayer = SPTAudioStreamingController.sharedInstance()
self.spotifyPlayer!.playbackDelegate = self
self.spotifyPlayer!.delegate = self
try! spotifyPlayer?.start(withClientId: auth.clientID)
self.spotifyPlayer!.login(withAccessToken: authSession.accessToken)
When i pick another song to open my PlaySongViewController again, it will be crashed with
"An instance of SPAudioStreamingController is already in use."
Another problem is when i try to log in with non-premium account, when i open PlaySongViewController, it will show "Spotify Premium Required" and when i close my PlaySongViewController and open another PlaySongViewController to play another song, it will be crashed again with the 'already in use' error
Can i bypass this code if i have start my spotifyPlayer?
try! spotifyPlayer?.start(withClientId: auth.clientID)
Or Are there any solutions?
Well there are two things I am seeing here:
try! spotifyPlayer?.start(withClientId: auth.clientID)
That should be a do and catch, if you look at the full signature for the start signature it states that it will throw errors. Instead make sure to catch errors when you can.
do {
try self.spotifyPlayer?.start(withClientId: auth.clientID)
} catch {
print("Failed to start with clientId")
}
It is important not force a try but instead handle errors.
You use a do-catch statement to handle errors by running a block of
code. If an error is thrown by the code in the do clause, it is
matched against the catch clauses to determine which one of them can
handle the error.
Additionally on the SPTAudioStreamingController.sharedInstance() is a property that is worth checking before the do and catch which is player?.loggedIn you can use this method to check before you attempt to call login, I've put an example of this in the singleton's login method below.
/** Returns `YES` if the receiver is logged into the Spotify service, otherwise `NO`. */
open var loggedIn: Bool { get }
Secondly you might be better off creating a singleton to handle all the logic of playing music that the View controller interfaces with so you don't end up with multiple view controllers trying to use the same spotifyPlayer and call start when it isn't necessary.
class MusicPlayer: {
static let shared = MusicPlayer()
fileprivate let player = SPTAudioStreamingController.sharedInstance()
override init() {
player?.playbackDelegate = self
player?.delegate = self
}
func login() {
if player?.loggedIn { return }
do {
try self.spotifyPlayer?.start(withClientId: auth.clientID)
} catch {
print("Failed to start with clientId")
}
}
}
and then in the view controller link to MusicPlayer.shared.

PromiseKit: delegate-system wrappers seem to return immediately when not used at the beginning of the chain

I am fairly new to PromiseKit and I have been trying for a couple of days to figure out a solution for an unexpected behaviour of promise-wrapped delegate systems (UIALertView+PromiseKit, PMKLocationManager etc..).
In my fairly typical scenario of an app's Setup process, I am trying to chain up a series of operations that the user has to go through when the app loads.
For the sake of this example, let's restrict the case to only two steps:
logging the user into a Restful system followed by presenting an alertView and waiting for user's interaction.
Below is my code, where:
LoginToService is a promise-able version of a block-based method obtained via extending MCUser with PromiseKit. This works as expected and returns once the user had logged in, or fails with an error.
In the 'then' clause of the successful login, I present an alertView by returning its promised version via alert.promise().
I would expect that promise to be fulfilled before the successive .then clause (and in the end the 'finally' clause) gets called - the alert's promise should be fulfilled when the the user clicks on button to dismiss it, as per implementation of PromiseKit's delegate system wrappers: this works just fine the observed behaviour when I use alert.promise().then to start the chain of Promises -
// Doesn't work: alert.promise returns immediately
let user = MCUser.sharedInstance()
user.logInToService(.RestServiceOne, delegate: self).then { _ -> AnyPromise in
MCLogInfo("LogInToService: Promise fulfilled")
let alert = UIAlertView(title: "Title", message: "Msg", delegate: nil, cancelButtonTitle: "Cancel", otherButtonTitles: "Hello")
return alert.promise()
}.then { (obj:AnyObject) -> Void in
print("Clicked")
}.finally {
print("Finally")
}.catch_ { error in
print("Error")
}
What I observe is that the chain continues immediately without waiting for the user to click, with the 'Clicked' and 'Finally' messages being printed to console and the alert on screen waiting for an action. Am I obviously missing something or ar those delegate-system wrappers not meant to be used if not at the beginning of the Promise chain?
Thanks in advance for any hint
It should work as you expect. You may check whether the returned promise is incorrectly completed.
What makes me gripes, though, is that alert.promise() should return a Promise<Int> - but the closure is explicitly typed to return a AnyPromise. So, your code should not compile.
I setup a test project myself, and indeed, the compiler complains. I used PromiseKit v3.x. Your's is likely an older version (finally and catch are deprecated).
After fixing the return type of the closure to Promise<Int>, the code compiles. But the important fact is, that the behaviour is as you described and experienced in your code - and not as it should, IMHO. So, there seems to be a bug.
Edit:
OK, it turned out that there is an issue with "overload resolution" and "type inference". Given your code in your OP, the Swift compiler resolves to an unexpected overload of the then method:
expected:
func then<U>(on q: dispatch_queue_t = dispatch_get_main_queue(), _ body: (T) throws -> Promise<U>) -> Promise<U>
actual:
func then<U>(on q: dispatch_queue_t = dispatch_get_main_queue(), _ body: (T) throws -> U) -> Promise<U>
This is caused by the succeeding finally and catch methods.
In order to solve it here in this scenario, you should either correctly fully specify the type of the closure, or let the compiler figure it out themselves by not specifying the type. I finally got this, which works as expected using PromiseKit v3.x with Swift:
import UIKit
import PromiseKit
// helper
func fooAsync() -> Promise<String> {
return Promise { fulfill, reject in
let delay = dispatch_time(DISPATCH_TIME_NOW, Int64(1.0 * Double(NSEC_PER_SEC)))
dispatch_after(delay, dispatch_get_global_queue(0,0)) {
fulfill("OK")
}
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
fooAsync()
.then { str in
print("String: \(str)")
let alert = UIAlertView(title: "Title", message: "Msg", delegate: nil, cancelButtonTitle: "Cancel", otherButtonTitles: "Hello")
let promise: Promise<Int> = alert.promise()
return promise
}.then { (n: Int) -> Void in // notice: closure type specified!
print("Clicked")
}.ensure {
print("Finally")
}.report { error in
print("Error")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
This above code may not solve it in your case, since you are using a different PromiseKit library. I would recommend to use the newest Swift version.
Nonetheless, there seem to be some subtle pitfalls here with PromiseKit. Hope you can now solve your issue.

How to return a value to a function while in background process subfunction in swift

I first tried this solution to return a bool in the spot I want to return it. However, due to the parse.com function saveInBackgroundWithBlock() being a void return function, I got the error "Unexpected non-void return value in void function".
func saveObjectToParse(gameLocal: Game) -> Bool {
let game = PFObject(className:"Game")
game["sport"] = gameLocal.sport.rawValue
var saved = false
game.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
if (success) {
print("Object has been saved.")
saved = true
return saved
} else {
print("parse error")
return saved
}
}
}
So, I tried moving the return statements out of the subfunction like this:
func saveObjectToParse(gameLocal: Game) -> Bool {
let game = PFObject(className:"Game")
game["sport"] = gameLocal.sport.rawValue
var saved = false
game.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
if (success) {
print("Object has been saved.")
saved = true
} else {
print("parse error")
}
}
return saved
}
However, this returns saved before the saveInBackgroundWithBlock() block executes because it is a background process. Therefore, saved will never be true, even when it is intended to be. I have tried adding a boolean flag called done and tried waiting with a while(!done) loop, but this freezes the program on the loop and the background process never executes. How can I fix these problems?
I agree with restructuring not needing a bool returned, but if you really, really need this set up, you could save your object synchronously (so your code will wait) like so,
do {
try game.save()
} catch {
print(error)
}
Returning a value from a function but from another function doesn't make architectural sense. Nor is it possible.
You either will need to change your implementation and make both methods independent or think of using a semaphore.
http://www.g8production.com/post/76942348764/wait-for-blocks-execution-using-a-dispatch
What you are trying to do (create a helper function to wrap the Parse save function) makes perfect sense and can be easily accomplished.
You do not need to use semaphores and you certainly don't want to perform the operation synchronously. Instead, use a completion hander to let you know when the save has completed. For more information on completion handlers see this link
func saveObjectToParse(gameLocal: Game, completion: (gameSaved: Bool) -> Void) {
let game = PFObject(className:"Game")
game["sport"] = gameLocal.sport.rawValue
game.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
// Set the completion handler to be result of the Parse save operation
completion(gameSaved: success)
}
}
You may then call this function like so
saveObjectToParse(someGameObject) { (gameSaved: Bool) in
if gameSaved {
print("The game has been saved.")
} else {
print("Error while saving the game")
}
}
Using this technique, you could similarly propagate the entire callback of saveInBackgroundWithBlock through your function so you could inspect errors when they occur.
Edit: It looks like you may also be using your own custom class to represent the Game object. I would recommend looking into subclassing PFObject so you can easily and directly model your Parse classes. More details in the documentation