Swift The Easiest Tutorial about Swift Opaque Type

KD Knowledge Diet
4 min readMay 24, 2023

Swift is a popular programming language that was first introduced by Apple in 2014. One of the language’s defining features is its strong type system, which allows developers to write code that is both safe and efficient. However, sometimes developers need to use types that are not known until runtime, which can make it difficult to work with the language’s type system. This is where Swift’s opaque types come in.

An opaque type is a type whose concrete implementation is not known at compile time. This allows developers to create abstractions that can be used to write code that is both safe and efficient, while still allowing for flexibility at runtime. In Swift, opaque types are created using the some keyword.

One of the primary use cases for opaque types is when working with protocols. Protocols are used in Swift to define a set of requirements that a type must conform to. This allows developers to write code that can work with a variety of different types, as long as they conform to the same set of requirements. However, sometimes it’s useful to define a protocol that can return a type that is not known until runtime. This is where opaque types come in.

Here’s an example of how opaque types can be used with protocols in Swift:

protocol Printer {
associatedtype Output
func print() -> Output
}

struct IntPrinter: Printer {
func print() -> Int {
return 42
}
}
struct StringPrinter: Printer {
func print() -> String {
return "Hello, world!"
}
}
func printSomething(p: Printer) {
let output = p.print()
print(output)
}
let intPrinter = IntPrinter()
let stringPrinter = StringPrinter()
printSomething(p: intPrinter) // prints "42"
printSomething(p: stringPrinter) // prints "Hello, world!"

In this example, we define a protocol called Printer that has an associated type called Output. We then define two structs, IntPrinter and StringPrinter, that conform to this protocol and return different types (Int and String, respectively). Finally, we define a function called printSomething that takes a value of type Printer and calls its print() method, printing the result to the console.

The problem with this code is that we have to define the associated type Output in the protocol, which means that we have to know the concrete type that will be returned by the print() method at compile time. If we want to be able to define a protocol that can return a type that is not known until runtime, we can use an opaque type instead:

protocol Printer {
func print() -> some Any
}

struct IntPrinter: Printer {
func print() -> some Any {
return 42
}
}
struct StringPrinter: Printer {
func print() -> some Any {
return "Hello, world!"
}
}
func printSomething(p: Printer) {
let output = p.print()
print(output)
}
let intPrinter = IntPrinter()
let stringPrinter = StringPrinter()
printSomething(p: intPrinter) // prints "42"
printSomething(p: stringPrinter) // prints "Hello, world!"

In this modified example, we define the Printer protocol with an opaque return type (some Any) instead of an associated type. This allows us to define the print() method to return a value of any type, as long as it conforms to the Any protocol. We can then define the IntPrinter and StringPrinter structs to return values of different types.

When we call the printSomething() function with an instance of IntPrinter or StringPrinter, the Swift compiler will automatically infer the return type of the print() method as the type returned by each specific implementation (either Int or String, respectively). This allows us to write code that can work with a variety of different types, even when we don't know the concrete type until runtime.

One important thing to note about opaque types is that they can only be used as return types, not as parameter types. This means that you can’t define a function that takes an opaque type as a parameter. However, you can define a function that returns an opaque type, which can be useful when you need to return a value of a type that is not known until runtime.

func createPrinter() -> some Printer {
if Bool.random() {
return IntPrinter()
} else {
return StringPrinter()
}
}

let printer = createPrinter()
let output = printer.print()
print(output)

In this example, we define a function called createPrinter() that returns an opaque type that conforms to the Printer protocol. The implementation of the function randomly chooses whether to return an instance of IntPrinter or StringPrinter. We can then call the print() method on the result of the function, and the Swift compiler will infer the correct return type based on the specific implementation that was returned.

Overall, opaque types are a powerful feature of the Swift language that allow developers to write code that is both safe and efficient, while still allowing for flexibility at runtime. They are particularly useful when working with protocols that need to return types that are not known until runtime. By using opaque types, developers can write more flexible and reusable code that can work with a wider variety of types.

--

--

KD Knowledge Diet

Software Engineer, Mobile Developer living in Seoul. I hate people using difficult words. Why not using simple words? Keep It Simple Stupid!