Swift: how to wrap `completion` into an async/await? - swift

I have a callback based API.
func start(_ completion: #escaping () -> Void)
I'd like to write an async/await wrapper on top of that API, deferring the implementation to original callback based API.
func start() async {
let task = Task()
start {
task.fulfill()
}
return await task
}
Obviously, this code doesn't connect - it's not real, there is no method fulfill on Task.
Question: is there a way to use unstructured concurrency in Swift so that it would help me achieve this?

You are looking for continuations.
Here is how to use it in your example:
func start() async {
await withCheckedContinuation { continuation in
start(continuation.resume)
}
}

Related

Is it possible to Extend functions in swift

I would like to ask if it's possible to extend functions or have some way to use them like this. Because I don't want to open task closure for every async function. I couldnt find any Nominal Type for functions in swift to extend.
func doSometingAsync() async -> Void {
}
// Non - desired
Task {
await doSometingAsync()
}
func makeItAsync() {
Task {
await self()
}
}
// Desired
doSometingAsync().makeItAsync()
// Extending those also not working
typealias Function = () -> Void
typealias AsnyFunction = () async -> Any?
Thank you!
You definitely shouldn't create a Task for every async function. If you are, something else is likely wrong. But your desired syntax is definitely impossible. You cannot attach methods to functions. Swift does not support it. It's not logically impossible (Go uses it regularly). It's just not a Swift feature, and I expect it will never be a Swift feature. It's not the direction Swift is evolving.
The following is legal if you want it:
func makeAsync(_ f: #escaping () async -> Void) {
Task { await f() }
}
makeAsync(doSometingAsync)
This is shorter than your proposed solution by a few characters, if that matters to you.
All said, though, if you need this a lot, you probably have an async design problem, and should investigate that.

Swift: can I use async/await syntax with a function that already returns synchronously a value?

I know that I can use async/await to replace this :
func test(_ completion: #escaping (Int) -> Void) {
// ...
completion(foundValue)
}
with this:
func test async -> Int {
let result = await calculate()
return result
}
However, can I do something if the initial function is like this?
func test(_ completion: #escaping (Int) -> Void) -> Int {
// ...
}
Thank you for your help
If your function doesn't need to wait for completion handler call then you can create a top level task and call your completion handler from the task after doing asynchronous work:
func test(_ completion: #escaping (Int) -> Void) -> Int {
// synhronous calls
Task {
// async funtion calls
completion(value)
}
return value
}
If you need to wait for all your asynchronous task completion before returning, you can use a semaphore for that:
func test(_ completion: #escaping (Int) -> Void) -> Int {
let semaphore = DispatchSemaphore(value: 0)
// synhronous calls
Task {
// async funtion calls
completion(value)
// signal async task completion
semaphore.signal()
}
// wait for async task completion
semaphore.wait()
return value
}
Or, you can wrap your asynchronous task inside an operation like here. You can signal the operation finish once the underlying async task finishes and wait for operation completion using waitUntilFinished():
let op = TaskOperation {
try await Task.sleep(nanoseconds: 1_000_000_000)
}
op.waitUntilFinished()
Note that using semaphore.wait() or op.waitUntilFinished() blocks the current thread and blocking the thread can cause undefined runtime behaviors in modern concurrency as modern concurrency assumes all threads are always making forward progress. If you are planning to use this method only in contexts where you are not using modern concurrency, with Swift 5.7 you can provide attribute mark method unavailable in asynchronous context:
#available(*, noasync, message: "this method blocks thread use the async version instead")
func test(_ completion: #escaping (Int) -> Void) -> Int {
// do work that can block thread
}
By using this attribute you can only invoke this method from a non-async context. But some caution is needed as you can invoke non-async methods that call this method from an async context if that method doesn't specify noasync availability.

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.

Closures vs returning an value/object from a function

I'm currently learning Swift(4)/iOS programming. I'm fairly new to OOP, but do have experience in Functional programming. There's one concept that confuses me somewhat...
In the courses/examples I followed a function mostly looks like this:
func getUsername(forUid uid: String, handler: #escaping (_ username: String) -> Void) {
//Do stuff
handler("MyName")
}
The name is passed by the closure, I hardly ever see a function like this:
func getUsername(forUid uid: String) -> String) {
//Do stuff
return "MyName"
}
Is the second way deprecated, or is there still a use for such a function. When and why do we use the first variant ?
We use the first variant for asynchronous programming. For example, see
func getUsername(forUid uid: String, handler: #escaping (_ username: String) -> Void) {
DispatchQueue.main.async {
// do some asynchronous stuff
handler("MyName")
}
}
Note handler has to be inside the async closure or else the handler will be called immediately because async does not block. Now if we used return instead of handler, there will be a compile error because your function doesn't return any value, so to fix the compile error it has to be in the function level (not in the async block). If it's not in the async block, it will be returned immediately (same as the second handler case above, so you must use closures if you are doing asynchronous tasks. However, if you are not using asynchronous stuff, you can safely use the second variant in your code.
In addition to asynchronous programming, closures are used in synchronous programming too, for example, the map function uses a closure as a parameter to define how should the object be mapped.
Of course it's not deprecated.
You should use closures for async tasks otherwise return value.
It is easier to get it with an example. Here's I get some places from an API:
func getPlaces(onSuccess: #escaping(_ places: [Place]?) -> Void, onFailure: #escaping(Error, _ title: String?, _ message: String?) -> Void) {
//perform API request...
//[...]
//...
// Session
session.dataTask(with: requestURL) { (data, response, error) in
guard error == nil else {
//handling Error
onFailure(error!, "Error", error?.localizedDescription)
group.leave()
return
}
//...
//handling succes
else {
var places: [Place] = []
places = responseJson.places!
onSuccess(places)
}
group.leave()
}.resume()
}
group.wait()
return
}
As you can see I want to handle success and error. Here's how I use it:
APIManager.shared.getPlaces(onSuccess: { (places) in
//handling success
}
}) { (error, title, message) in
DispatchQueue.main.async {
self.present(getAlertFromError(title: title, message: message), animated: true)
}
}

How to include completion handlers for multiple functions in swift?

Consider this code:
func test() {
A()
B()
C()
D()
E()
}
Each function here have their own set of actions like calling API's, parsing them, writing results in file, uploading to servers, etc etc.
I want to run this functions one by one. I read about completion handlers. My problem with completion handlers are:
All examples given to understand completion handlers was having just two methods
I don't want to place this functions in some other function. I want all function calls (A to E) inside Test() function alone
Can someone help on this?
This is easy to do, you just need to add a closure argument to call on completion. For example:
func a(completion: (() -> Void)) {
// When all async operations are complete:
completion()
}
func b(completion: (() -> Void)) {
// When all async operations are complete:
completion()
}
func c(completion: (() -> Void)) {
// When all async operations are complete:
completion()
}
func d(completion: (() -> Void)) {
// When all async operations are complete:
completion()
}
func e(completion: (() -> Void)) {
// When all async operations are complete:
completion()
}
func test() {
a {
b {
c {
d {
e {
// All have now completed.
}
}
}
}
}
}
As you can see, this looks horrible. One problem with multiple async operations, non concurrently is that you end up with this horrible nesting.
Solutions to this do exist, I personally recommend PromiseKit. It encapsulates blocks in easy chaining methods, which is far cleaner.
Take a look at reactive programming and observable chains. RxSwift will be an excellent library for you. Reactive programming is very popular right now and was created exactly to solve problems like yours. It enables you to easily create a process (like an assembly line) that transforms inputs into your desired outputs. For example, your code in Rx would look like:
A()
.flatMap { resultsFromA in
B(resultsFromA)
}
.flatMap { resultsFromB in
C(resultsFromB)
}
.flatMap { resultsFromC in
D(resultsFromC)
}
.flatMap { resultsFromD in
E(resultsFromD)
}
.subscribe(onNext: { finalResult in
//result from the process E, subscribe starts the whole 'assembly line'
//without subscribing nothing will happen, so you can easily create observable chains and reuse them throughout your app
})
This code sample would create a process where you'd transform results from your initial API call (A), into parsed results (B), then write parsed results to a file (C) etc. Those processes can be synchronous or asynchronous. Reactive programming can be understood as Observable pattern on steroids.