Nerd For Tech

NFT is an Educational Media House. Our mission is to bring the invaluable knowledge and experiences of experts from all over the world to the novice. To know more about us, visit https://www.nerdfortech.org/.

Using URL Sessions with Swift 5.5 Async/await & Codable

--

Creating an API manager with URL Session that will support async/await.

Photo by Zan on Unsplash

As we all are familiar that in WWDC-21 apple has introduced asynchronous (async) functions into Swift, allowing us to run complex asynchronous code almost as if it were synchronous. That being said we now don't have to write complex callback completion handlers or manage delegates for our result as async/await will take care of it.
As of now it's only introduced for iOS 15 and above, but I am sure Apple must be considering giving its support to lower iOS versions as well.

This is a visual representation of code from WWDC session https://developer.apple.com/videos/play/wwdc2021/10132/ where they distinguished between the syntax and complexity difference between completion handlers and aysnc/await using fetch thumbnail as an example.

Cleaner code with async/await.

Let’s now dive into our main topic.

We already know there are huge networking libraries available out there like Alamofire, AFNetworking and Moya but here we are creating very simple methods without any dependency to support our networking calls with URL Sessions.

Let's first create all the needful that we will be needing while creating our API Manager.

Create an enum to state the type of request we will initiate.

enum RequestType: String {case postRequest = "POST"case getRequest = "GET"}

Create an enum of String type that will contain all our endpoints.

enum Endpoint: String {case getProducts = "get/products"}

Creating a Class For our Api Manager with properties that would be useful.

class ApiManager {var baseURL = "https://api.spoonacular.com/"static var shared = ApiManager()private var request: URLRequest?private init () {} }

Now let's create all the helper methods that will support our get/post calls and can be customizable according to everyone’s need.

For GET API calls in which we need to send parameters as URL components. We can write a method that will append all the parameters into URL and then we can create a URL request that we can send to our URLSession.

private func createGetRequestWithURLComponents(url:URL,parameters: [String:Any],requestType: RequestType) -> URLRequest? {var components = URLComponents(string: url.absoluteString)!components.queryItems = parameters.map { (key, value) inURLQueryItem(name: key, value: "\(value)")}components.percentEncodedQuery = components.percentEncodedQuery?.replacingOccurrences(of: "+", with: "%2B")request = URLRequest(url: components.url ?? url)request?.httpMethod = requestType.rawValuereturn request}

For our POST request, we would need to create a URL request and a httpBody that we need to send to our API.

private func createPostRequestWithBody(url:URL, parameters: [String:Any], requestType: RequestType) -> URLRequest? {request = URLRequest(url: url)request?.httpMethod = requestType.rawValuerequest?.addValue("application/json", forHTTPHeaderField: "Content-Type")request?.addValue("application/json", forHTTPHeaderField: "Accept")if let requestBody = getParameterBody(with: parameters) {request?.httpBody = requestBody}request?.httpMethod = requestType.rawValuereturn request}

This method converts our parameters Dictionary into data that we need to assign to httpBody.

private func getParameterBody(with parameters: [String:Any]) -> Data? {guard let httpBody = try? JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted) else {return nil}return httpBody}

Creating a method to distinguish between our get/post requests and behave accordingly.

private func createRequest(with url: URL, requestType: RequestType, parameters: [String: Any]) -> URLRequest? {if requestType == .getRequest {return createGetRequestWithURLComponents(url: url,parameters: parameters,requestType: requestType)}else {return createPostRequestWithBody(url: url,parameters: parameters,requestType: requestType)}}

Now finally the interesting Part!!! :D
Make sure you are using Codable protocol in your models.

We will have a single method that will be called whenever we make an API request and this method will take care of everything. We have used generics to cast our Codable models directly so we don't have to create methods separately to cast every particular Model.

func sendRequest<T:Codable>(model: T.Type,with endpoint: Endpoint,requestType: RequestType,parameters: [String:Any]) async -> Result<T, Error>? {if #available(iOS 15.0, *) {do {let url = URL(string: baseURL+endpoint.rawValue)!guard let urlRequest = createRequest(with: url,requestType: requestType,parameters: parameters) else {return nil}let (data, _) = try await URLSession.shared.data(for: urlRequest)let parsedData = try JSONDecoder().decode(model.self, from: data)return .success(parsedData)}catch {return .failure(error)}}return nil}

Three things to notice:

  1. We have marked our method with async, as we use await inside our method. An aysnc function can be suspended and can hand over the control of the thread in a very unique way.
  2. We have used await keyword that tells the compiler to suspend the control of the function and hands over the control to the system rather than giving control back to our function and the system decides which task is more important. Cool Right? !!
  3. For using async functions we always need to mark our function that we call them in as async as well.

Now add all our code in to ApiManager class to use it. ;)

Let's create a demo Model with Codable protocol

struct MyModel: Codable {var id: String?var description: String?}

Now just call it like this in any of your method!

class YourClass {func sendApiCall() async {guard let result = await ApiManager.shared.sendRequest(model: MyModel.self, with: .getProducts, requestType: .postRequest, parameters: ["":""]) else {return}switch result {case .success(let mymodel):print(mymodel)case .failure(let error):print(error.localizedDescription)}}}

That’s it for now, please let me know if you guys have any questions.

Thank you!
Happy Coding.

Shiraz Khan

--

--

Nerd For Tech
Nerd For Tech

Published in Nerd For Tech

NFT is an Educational Media House. Our mission is to bring the invaluable knowledge and experiences of experts from all over the world to the novice. To know more about us, visit https://www.nerdfortech.org/.

Sheeraz Ahmed
Sheeraz Ahmed

Written by Sheeraz Ahmed

Software Engineer | Mobile Apps

Responses (1)