Swift The Easiest Tutorial about Swift Opaque Type
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.