Skip to Content
Technical Articles
Author's profile photo Kevin Riedelsheimer

Make your enterprise apps leverage Swift 5.5 Async/Await feature

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%20BTP%20SDK%20for%20iOS%20Assistant%209.1%20-%20Async/Await%20Data%20Service

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:

Assigned Tags

      6 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Per Åge Themte
      Per Åge Themte

      Kevin Riedelsheimer 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?

      Author's profile photo Kevin Riedelsheimer
      Kevin Riedelsheimer
      Blog Post Author

      Hi Per Åge Themte ,

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

      Cheers,

      Kevin

      Author's profile photo Kevin Riedelsheimer
      Kevin Riedelsheimer
      Blog Post Author

      Hi Per Åge Themte ,

      did you install the CLI tool from the assistant of version 9.1 or higher?

      Author's profile photo Nicolai Schoenteich
      Nicolai Schoenteich

      Hi Per Åge 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.“

      Author's profile photo Per Åge Themte
      Per Åge Themte

      Nicolai Schoenteich

      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?

      Author's profile photo Per Åge Themte
      Per Åge Themte

      Hi Kevin Riedelsheimer.

      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