ProgrammingSenior iOS Developer

What are 'opaque types' (some) in Swift, when and why to use them, and how do they differ from protocols with associated types?

Pass interviews with Hintsage AI assistant

Answer.

Background:

In Swift before version 5, when returning a value conforming to a protocol with associated types, developers often faced limitations — the type couldn't be used as a return type directly, requiring type erasure. To improve readability and performance, opaque types were introduced — return values using the keyword some, allowing to describe abstractions in public interfaces.

Problem:

When it's necessary to hide the actual type of the return value, while maintaining abstraction through the protocol, but also avoiding the overhead of dynamic dispatch and type erasure. For instance, when returning collections, sequences, view components.

Solution:

Opaque types allow returning a type conforming to a protocol while hiding its concrete implementation. The compiler knows the actual type, but the calling side does not.

Example:

protocol Shape { func area() -> Double } struct Circle: Shape { var radius: Double func area() -> Double { Double.pi * radius * radius } } func makeCircle() -> some Shape { return Circle(radius: 3) } let s = makeCircle() print(s.area()) // works

Key features:

  • Opaque type — always the same type, hidden behind the protocol, declared in return via some
  • Faster and stricter than type erasure, enables working with associated types in protocols
  • Does not allow returning different types from different branches (the type must be the same for all returns)

Tricky Questions.

How does an opaque type (some Protocol) differ from a return type Protocol?

An opaque type has a concrete implementation at compile-time (although hidden externally). When returning Protocol, dynamic dispatch occurs, and there is no type safety if there are associated types.

Can different types be returned using some Protocol in one function?

No. All returns must return the same concrete type:

func maker(flag: Bool) -> some Shape { if flag { return Circle(radius: 3) } else { return Square(size: 2) // Error: return type does not match } }

Can associated type inside Protocol be used through some Protocol?

Yes. This is precisely what opaque types are for:

protocol View { associatedtype Body } func makeView() -> some View { /* ... */ }

Common Mistakes and Anti-Patterns

  • Confusion between some Protocol and Protocol — they are different cases with different limitations
  • Violation of the homogeneity rule for the return type across all branches
  • Using some where protocol or typealias would suffice

Real-Life Example

Negative Case

A function returns a protocol without opaque, preventing the use of methods with associated types, requiring complex type erasure, code fails to compile or works suboptimally.

Pros:

  • Flexibility in abstractions like AnySequence

Cons:

  • Loss of type safety, low performance
  • Associated types do not work

Positive Case

ViewBuilder in SwiftUI uses some View, hiding details, increasing type safety, improving compilation speed and runtime.

Pros:

  • Readable API, type safety, no dynamic dispatch
  • Ease of maintenance

Cons:

  • Cannot return different types in one function