Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
KRiedelsheimer
Community Manager
Community Manager
Asynchronously marked functions were introduced with SE-0296 for Swift 5.5 to make asynchronous calls easier to implement and to read. This allows for functions to opt into being declared and handled as async, allowing for complex asynchronous operations using the known control flow. Calling such a function can simply be achieved by using the await or, if the function throws, the await throws keywords. This addition to the Swift programming language was made to remove the complexity of using completion handlers to handle asynchronous callbacks.

In version 9.1, the SAP BTP SDK for iOS has changed it's own OData frameworks to apply to this improved way of implementing asynchronous functions by providing function definitions within the framework's own API using the SAP BTP SDK for iOS Assistant (Assistant). That means if you are using the SAP BTP SDK for iOS version 9.1 and higher to create a new app you will have the async/await feature out of the box. The team of the SDK has introduced the SAP BTP SDK for iOS - OfflineDataServiceAsync class which is basically using the Async/Await methodology to introduce this new language feature within the SAP BTP SDK for iOS.

To understand why the introduction of these language changes within Swift 5.5 are such a great improvement let us look at an example.

Important to mention is that if you have an app already in place you can use the Assistant to re-generate the Proxy Classes applying this new feature to your Proxy Classes easily.

Understanding the changes


Before Swift 5.5 the common way of declaring and implementing asynchronous functions was by using completion handlers. Completion Handlers allow us to send back values after a function returns. This sounds great but comes with hard to read syntax in most cases. In the following example we will fetch a product from the backend. For simplicity reasons I have left out the actual networking code and just return hard coded values:
import Foundation
import SAPOData

// Not including error handling here
func fetchProduct(withKey: Int, completion: @escaping (Product) -> Void) {
DispatchQueue.global().async {
let product = executeQuery(service: self, query: query.fromDefault(ESPMContainerMetadata.EntitySets.customers)
completion(product)
}
}

func fetchProductImage(from: Product, completion: @escaping (UIImage) -> Void) {
DispatchQueue.global().async {
let image = UIImage(data: executeQuery(service: self, query: query.fromDefault(product))
completion(image)
}
}

So far, so good, but the code above has multiple issues in itself even if it is syntactically correct:

  • The parameter syntax @escaping((Customer?, Error?) -> Void) makes the code harder to read.

  • Functions which call their completion handler might call it more than once, or forget to call it at all.

  • Calling such a function can end up in a so-called pyramid of doom, code can get increasingly indented for each completion handler.


Calling these functions will end up in indentation:
fetchProduct(222344) { product in
fetchProductImage(product) { image in
displayProductImage(image)
}
}

With Swift 5.5, such functions can be cleaned up by simply marking them as asynchronous, returning a value instead of relying on completion handlers:
import Foundation
import SAPOData

// Not including error handling here
func fetchProduct(withKey: Int) async -> Product {
return executeQuery(service: self, query: query.fromDefault(ESPMContainerMetadata.EntitySets.customers)
}
}

func fetchProductImage(from: Product) async -> UIImage {
return UIImage(data: executeQuery(service: self, query: query.fromDefault(product))
}
}

Calling them now is way simpler now:
func displayProductImage() async {
let product = await fetchProduct(222344)
let productImage = await fetchProductImage(product)
print(productImage)
}
}

Important to notice is that just because we define a function as asynchronous it does not mean it runs concurrently with our other code. That being said, if you don't specify differently calling these functions will still execute sequentially.


SAP BTP SDK for iOS Assistant 9.1 - Async/Await Data Service


I have blown out one of these methods to give you a clearer picture on what the changes involve:
private func fetchCustomer(matching query: DataQuery, headers: HTTPHeaders? = nil, options: RequestOptions? = nil) throws -> Customer {
return try CastRequired<Customer>.from(ProxyInternal.executeQuery(service: self, query: query.fromDefault(ESPMContainerMetadata.EntitySets.customers), headers: headers, options: options).requiredEntity())
}

open func fetchCustomer(matching query: DataQuery, headers: HTTPHeaders? = nil, options: RequestOptions? = nil) async throws -> Customer {
return try await withUnsafeThrowingContinuation {
(continuation: UnsafeContinuation<Customer, Error>) in
asyncFunction {
do {
try self.checkIfCancelled(options?.cancelToken)
let result = try self.fetchCustomer(matching: query, headers: headers, options: options)
continuation.resume(returning: result)
} catch {
continuation.resume(throwing: error)
}
}
}
}

The above shown code sample includes to methods, one private and one which is the actual open API method. The first is basically calling the OData service by executing the fetch query against the Customer entity using a given ID. The second method is implementing the Async/Await approach of doing asynchronous calls in Swift 5.5 and higher. This call is basically saying, "let us execute the private method doing the query execution, wait for the call to complete and resume with the continuation returning the customer or throw the given error. A continuation in Swift helps you to interface asynchronous tasks with synchronous calls. This is because the private method is calling the OData service in a synchronous way but we want to actually execute this within our new Async/Await implementation.

With this Blog Post I simply wanted to introduce the idea SE-0296 has in mind and emphasize that the Assistant has been changed to fully adapt the Async/Await change, by giving you all the magic out of the box through generating a new app.

To better grasp the new changes and the idea behind what I am explaining here, I would encourage you to download the latest SAP BTP SDK for iOS and generate yourself a new app to see and experience this new API.

In the Further Reading section you will find additional resources going into more detail on how the Async/Await works.

In the next Blog Post I will introduce you to the approach of changing a legacy app to fully adapt the Async/Await feature.

Until next time - Happy Coding! 🧑‍💻👩‍💻👨‍💻

Further Reading:


6 Comments
perage
Participant
kevin.muessig Nice post! I used the assistant to create a new application and all classes are now async, but then using the CLI to update the classes - they're reverted back to the old style completionHandler. Is there a parameter I can set to have classes generated as Async/await?
KRiedelsheimer
Community Manager
Community Manager
0 Kudos
Hi perge.themte ,

thanks for reaching out! Let me check and get back to you as fast as possible.

Cheers,

Kevin
KRiedelsheimer
Community Manager
Community Manager
0 Kudos
Hi perge.themte ,

did you install the CLI tool from the assistant of version 9.1 or higher?
perage
Participant
0 Kudos
Hi kevin.muessig.

It's been installed for about a year or two, but I installed it again from 9.2.2 - not sure if it has to be uninstalled in any way first?

In this post, Evan proposed a new parameter, but those are not valid in my CLI

 
nicoschoenteich
Developer Advocate
Developer Advocate
0 Kudos
Hi perge.themte,

Replying on Kevin‘s behalf:

“The command for generating the Async await ready proxy classes would be: “sapcpsdk-proxygenerator --metadata metadata.xml --destination ODataProxies -online -dio -async:result -async:await” for online proxy classes. If you want offline ready proxy classes replace the -online with -offline.“
perage
Participant
0 Kudos
nicolai.geburek

I tried tha one, but I only get error

2023-09-14T21:15:10.979+02:00 ERROR [main] Unrecognised option: -async:result

I have installed tools and CLI from Assistant 9.2.2 and 9.2.3 . Seems like it might be using an older version on my computer?