ResultBuilders

My name is Pavan and I am an iOS engineer working in Rakuten Ichiba App team which belongs to Commerce TECH, my responsibility is to provide architecture direction, technical support and build tools,  which save developers effort. This is my first R-Hack blog which I got inspired by many other creative articles from our TECH teams.

“Work together, dream together.”

The mission of our team is to provide an excellent front line customer service experience that focusses on delivering positive outputs which puts customers atthe heart of what we do, and we believe “shopping that changes the future”, We integrate most of Rakuten services by adapting latest technologies and best practices to keep our product stronger.

Our team recently reorganized in such a way that each sub-team can focus on core functionality of respective domains i.e., shop/item, navigation and core part to bring high quality features and experience to customers.

f:id:R-Hack:20220318150017j:plainOK, let me end our introduction and get down to business now.

 

Overview

Swift's result builder type is one of the most valuable features introduced in the language and it's a vital part of building SwiftUI views in a declarative way. As part of the core language, this style is not limited to SwiftUI, so developers can take advantage of result builders themselves to write their custom declarative components.

Result builders were initially introduced as "function builders" in Swift 5.1. After various community reviews and feedbacks, it then officially landed in Swift 5.4 as @resultBuilder. SwiftUI View declarations use a @ViewBuilder property wrapper under the hood, which is an implementation of a result builder.

f:id:R-Hack:20220318150053p:plain

In Ichiba iOS app we use this framework extensively since we believe future app development is declarative and this is the main theme of this framework which provides various features in declarative way.

We have published our utility ResultBuilders as open-source. Please go through the open source project to see other available result builders. We are using this open-source in the Rakuten Ichiba app and with help of this our code got simplified, code review become simpler, readability, reduction in boilerplate code.

Requirements

- iOS 11.0+

- macOS 10.11+

- watchOS 4.0+

- Xcode 12.5+

What do we build?

We are going to build a custom RRequestBuilder which is built based on @resultBuilder. The main goal of this custom RRequestBuilder is to build a network API request in a declarative way. So in the following article let us explore more about @resultBuilder and how to use it in an efficient way to build our custom RRequestBuilder. By the end of this article, you should be able to see how easy it is to construct a network API request in a declarative way just with the power of  @resultBuilder. The following diagrams shows a quick overview of custom RRequestBuilder.

f:id:R-Hack:20220318150720p:plain

How does it work?

Like it sounds, the Swift @resultBuilder feature essentially allows us to build a result by combining multiple expressions into a single value. The best example is how SwiftUI's HStack and VStack combine child components. Just like property wrappers, Swift result builders are implemented as a normal Swift type annotated with the special attribute @resultBuilder. So let us try to build the RRequestBuilder  for making network requests using the power of @resultBuilder.

f:id:R-Hack:20220318150203p:plain

In the example above, the return type of the buildBlock(_ params: RequestParameter...) function is a custom protocol of type RequestParameter. Types that conform to this protocol will be used to modify a URLRequest (i.e., add headers, modify the http body, include query parameters, etc...).

f:id:R-Hack:20220318150313p:plainWith the above in place, we can now call  makeRequestParameter with an empty trailing closure and we’ll get an EmptyParameter

f:id:R-Hack:20220318150327p:plainWhile our new API is not yet very useful, it’s already shown us a few aspects of how result builders work. Now that you have a glimpse of how RRequestBuilder works, Let's dive into some more concrete examples...

Combining multiple values into a single result

To make RRequestBuilder more powerful, it needs additional overloads of build blocks with arguments matching the input that we expect RRequestBuilder to receive.  In our case, we’ll simply implement a single method that accepts a list of RequestParameters, which RRequestBuilder returns as CombinedParameters conforming to RequestParameter.

f:id:R-Hack:20220318150348p:plain

With that new buildBlock overload in place, we’ll now be able to fill any closure that we’re passing to makeRequestParameter with RequestParameters. Our result builder (with some help from the compiler) will then combine all of those expressions into a new RequestParameter(CombinedParameters), which is then returned:

f:id:R-Hack:20220318150356p:plain

All of the above child components conform to RequestParameter. While the above is arguably already a slight improvement over the empty request parameter, let's continue enhancing the function by adding more building blocks and building an actual API Request.

- to use an optional statement in the DSL body, the following build block needs to be defined

f:id:R-Hack:20220318150403p:plain

- to use if else statements in the DSL body, the following build blocks needs to be defined

f:id:R-Hack:20220318150411p:plain

- to use loop statements in the DSL body, the following build block needs to be defined

f:id:R-Hack:20220318150423p:plain

For complete build blocks please look into this guide.

API Request

We are almost ready with the required build blocks. Now it's time to define the Request component which makes use of our powerful RRequestBuilder.

f:id:R-Hack:20220318150452p:plain

Request is the custom type that has a RRequestBuilder as a trailing parameter in its initializer. So calling builder() will execute the necessary build blocks to construct the parameters. Now, you might be asking yourself, "Where exactly is the URLRequest constructed?".

f:id:R-Hack:20220318150512p:plain

The plain URLRequest is created in the Request. It will then be passed into the root RequestParameter, which modifies it internally by injecting the necessary properties to the URLRequest.

Connecting the dots...

Cool! All necessary tools are ready. It's time to connect all of them and build our fantastic API request tool.

Before we going to see declarative way of implementing Request, let us see how this will be constructed in imperative way:

f:id:R-Hack:20220318150529p:plain

Declarative way:

f:id:R-Hack:20220318150550p:plain

Building an API request is simple, isn't it? It's all because of the powerful result builder support from the language. `onObject`, `onError`, etc. are all just helper methods that do a specific task for making an API call.

By looking at the above code it’s obvious that imperative code has lot’s cons like readability length of code, error prone etc. So from this we can conclude that declarative way wins over imperative code for its simplicity and type safeness.

**NOTE:** for a full reference of the above component please check RRequestBuilder.

Wrap up

Great! Finally we created our custom RRequestBuilder with the help of  @resultBuilder. RRequestBuilder is one of the tools available from our great open-source library from CC TECH. Likewise, there are other tools (result builders) available from our open-source. Please look at them for more info.

- RAttributedStringBuilder

- RAlertControllerBuilder

What do you think? Are you looking forward to using @resultBuilder within your own code, and did you gain some additional insight into how to build RRequestBuilder by reading this article?

If so, feel free to share it.

Thanks for reading! 🚀

Reference

Open source by CC TECH

Apple doc

Come work with us

We are looking for iOS engineers who are enthusiastic about new technologies and take challenges with various background teammates.
Does it interests you? Check our corporate site.