Opaque types in Swift introduces developers to a powerful feature that enhances code abstraction, flexibility, and performance. By using the some keyword, opaque types allow developers to hide implementation details while maintaining type safety, unlike other techniques like Any or AnyObject.

The aim of thi post is clarify their usage in real-world scenarios, help bridge the knowledge gap for developers who are familiar with protocols and generics but not opaque types, and showcase how this feature leads to cleaner, more maintainable code.

Opaque types

Opaque types in Swift, introduced with the some keyword, allow you to define a type that hides its concrete implementation while still conforming to a specific protocol or set of constraints. This enables you to return a value from a function without exposing the exact type, providing flexibility and abstraction while maintaining type safety. Essentially, the compiler knows the specific type behind the opaque type, but users of the type only know that it conforms to the expected protocol, which helps in writing cleaner, more maintainable code without sacrificing performance or type safety.

Lets start by a generic sample:

protocol Animal {
    func makeSound() -> String
}

struct Dog: Animal {
    func makeSound() -> String { "Woof!" }
}

struct Cat: Animal {
    func makeSound() -> String { "Meow!" }
}

func getAnimal() -> some Animal {
    return Dog()
}

In this example:

  • The getAnimal() function returns an opaque type (some Animal), indicating that it returns a type conforming to the Animal protocol.

  • The specific type (Dog) is hidden from the caller.

Benefits:
  1. Abstraction: Hides implementation details while exposing only necessary behavior.

  2. Type Safety: Ensures that the returned type is consistent and conforms to the specified protocol.

  3. Optimization: Allows the compiler to optimize code by knowing the exact underlying type.

Now let me introduce you another example that you most probably have seen in swiftUI:

struct ContentView: View {
    var body: some View {
        VStack {
            myCustomSomeViewA()
            myCustomSomeViewB()
        }
    }
    
    func myCustomSomeViewA() -> some View {
        Text("myCustomSomeViewA")
     }
    
    func myCustomSomeViewB() -> some View {
        Text("myCustomSomeViewB")
     }
}

The some keyword in this SwiftUI code is used to specify opaque return types for the body property and the helper functions myCustomSomeViewA() and myCustomSomeViewB(), which return SwiftUI views without exposing their exact types. This allows the compiler to enforce type consistency while keeping implementation details hidden. In practice, it ensures that each function returns a single, consistent view type, improving type safety and optimization.

any keyword

In Swift, the any keyword is used to explicitly indicate existential types, meaning a value can be of any type that conforms to a given protocol. Introduced in Swift 5.6, it improves clarity by distinguishing between existentials (any Protocol) and generics (<T: Protocol>), helping developers understand when dynamic dispatch and runtime type checking are involved. Using any makes code more explicit but may introduce performance overhead compared to generics, which enable static dispatch. It is recommended when working with heterogeneous types but should be avoided when generics provide a more efficient alternative.

We will continue with previous generic example:

func printAnimal(_ animal: any Animal) -> String {  // Explicit existential
    animal.makeSound()
}

The function printAnimal(_ animal: any Animal) -> String takes an existential any Animal as a parameter and calls its makeSound() method but has a compilation error because it declares a return type of String without returning a value. To fix it, makeSound() should return a String, and the function should return that value. For example, if Animal is a protocol with func makeSound() -> String, and a Dog struct implements it by returning "Woof!", calling printAnimal(Dog()) would correctly return "Woof!".

On extending previous SwiftUI example with any:

The error …this expreession cannot conform to ‘View’ occurs because the any keyword creates an existential type, which cannot directly conform to the View protocol in SwiftUI. This is a common issue when working with protocols and generic types in SwiftUI.

For fixing this issue we have to address in the following way:

    let views: [AnyView] = [AnyView(myCustomAnyViewA()),
                            AnyView(myCustomAnyViewB())]

AnyView in SwiftUI is a type-erased wrapper that allows for changing the type of view used in a given view hierarchy13. It serves as a container that can hold any type of SwiftUI view, enabling developers to return multiple view types from a single function or computed property4.

Conclusions

In this post, I clarify the concept of Opaque Types in Swift and the usage of the some and any keywords. You can find source code used for writing this post in following repository

References

Copyright © 2024-2025 JaviOS. All rights reserved