Opaque return types in swift

Finding some use cases

Saravanakumar S
4 min readJun 30, 2021

Motivation:

When I wanted to trial SwiftUI and experiment by creating a new SwiftUI Project in Xcode. The default project code has ContentView.swift looked something like this.

struct ContentView: View {     
var body: some View {
Text("Hello, world!!")
}
}

To give some context for those who are new to SwiftUI, View is a protocol with single mandatory type body: some View. Meaning, body property should build the (page)layout, with desired View objects and return it. In the above code, The ContentView is a view component (can be rendered as a whole page or subview in a page) composed with single Text(Label) view with text Hello, world!!.

The first thing that puzzled me was the some keyword. What does this mean? Obviously because, I never have came across this before. My first guess was that, the body property is allowed to return any type that conforms to View. Well that makes sense, but do we really need to add the keyword some, even without the keyword, return type is just View and it makes sense to return any view thats conformed to the View.

struct ContentView: View {
var body: View {
Text("Hello, world!!")
}
}

The above code may sound correct, but there are few catches in having the some keyword that we're going to discover soon.

Opaque Return Types:

With Swift 5.1 onwards, functions can return types that are opaque (with some). some must follow a protocol type that the return value of the function conforms. To quote an example,

protocol Shape {
func draw()
}
struct Triangle: Shape {
func draw() {
print("DRAWING Triangle")
}
}
struct Square: Shape {
func draw() {
print("DRAWING SQUARE")
}
}
func drawSquare() -> some Shape {
//Create square and return it.
}

The swift language guide states “A function or method with opaque return type hides its return value’s type information. Instead of providing a concrete return type, the return value is described in terms of the protocol it supports.”

The essence:

So, if your function wants to hide the actual type information from its consumers, that is when you might want to use Opaque Return Types.

Hiding type information is useful at module boundaries, because the clients of your module need not be aware of the internal types that your function uses. That information stays within your module. In case of SwiftUI (the example we seen above), View protocol is the module boundary (that is the most we interact with SwiftUI’s internals). We provide view object and SwiftUI takes care of laying out in the display, without bothering what type of view it is. (i.e) You are hiding your implementation detail from SwiftUI, by returning an opaque type View.

Even though the clients of your code does not know the underlying type, the compiler will know the concrete type being returned. So, you cannot return two different types based on a condition. Thus ensuring type safety.

struct ContentView: View {
var body: some View {
Text("Hello, world!!")
if isFirstPage {
Text("Hello, world!!")
} else {
Button(action: {}) {
Text("Click Me!")
}
}
}
}

This above code will not compile, because compiler can’t understand the type you return, because the return type mismatches at different places in a function.

The trade-off between Protocol types (returning protocol type without some ) and Opaque return types is primarily the flexibility vs stronger guarantees. With Protocol types there is flexibility of returning any type that the protocol conforms, and with Opaque return types, a function can return only single type because compiler knows underlying concrete type under the hoods.

Generics vs Opaque Return Types:

In both Generics and Opaque Return Types, the underlying concrete type should be resolved at compile time. This means, we should let the compiler know the concrete type that we are going to use in placeholder during compilation. However the key difference is that, in case of generics the calling method protects underlying concrete type from the caller. For example,

func add<T: Numeric>(lhs: T, rhs: T) → T {
return lhs + rhs
}

In the above function add method performs addition as long as the provided type conforms to numeric protocol. (i.e) the add method is not aware of concrete types that it is operating.

Where as in OpaqueReturnTypes, the called method (callee) is protecting the concrete type from its caller.

func renderView() -> some View {
return Button(action: { presenter.fetchRecords() }) {
Text("Fetch")
}
}

Hope it is helpful in understanding opaque return types. Leave your thoughts in the comments section. Thanks for reading!!

Reference:

--

--

Saravanakumar S
Saravanakumar S

Written by Saravanakumar S

iOS App developer. Trying to be a better developer than yesterday.

No responses yet