Learn iOS Development

Opaque Return Types vs Protocol-Based in Swift

Exploring the Contrast With Example

Shashank Thakur
Mobile App Development Publication

--

Opaque Return Types Vs Protocol-Based in Swift
Photo by Dietmar Becker on Unsplash

Swift, a versatile programming language, offers developers a myriad of tools to design elegant and adaptable code. Two such tools are opaque return types and protocol-based types. These concepts enhance code readability and maintainability while fostering modularity and flexibility. In this blog post, we will embark on a journey to uncover the disparities between opaque return types and protocol-based types, illustrating their dissimilarities through practical examples.

Opaque Return Types: A Closer Look

In Swift 5.1, opaque return types emerged as a way to encapsulate implementation details while exposing a clear interface to the caller. These types facilitate code evolution by enabling modifications to the underlying implementation without affecting external code.

Let’s examine a scenario where we use an opaque return type:

protocol Shape {
func area() -> Double
}

struct Circle: Shape {
let radius: Double
func area() -> Double {
return .pi * radius * radius
}
}

struct Square: Shape {
let side: Double
func area() -> Double {
return side * side
}
}

func createShape(isCircle: Bool) -> some Shape {
return isCircle ? Circle(radius: 5.0) : Square(side: 4.0)
}

let shape = createShape(isCircle: true)
print(shape.area()) // We can call methods defined in the Shape protocol

In this example, the createShape the function returns an opaque type conforming to the Shape protocol. The returned value could be either a Circle or a Square, but the caller doesn't need to know which one. This allows us to maintain encapsulation while accommodating future changes.

Protocol-Based Types: A Different Perspective

Protocol-based types, integral to Swift since its inception, enable a higher level of abstraction by defining a contract that various types can adhere to. Protocols enable a more modular codebase and facilitate better code reuse.

Consider the following protocol-based example:

protocol Animal {
func makeSound()
}

struct Dog: Animal {
func makeSound() {
print("Woof woof!")
}
}

struct Cat: Animal {
func makeSound() {
print("Meow!")
}
}

func playSound(for animal: Animal) {
animal.makeSound()
}

let dog = Dog()
let cat = Cat()

playSound(for: dog) // Outputs: Woof woof!
playSound(for: cat) // Outputs: Meow!

Here, the Animal protocol defines the makeSound method, and both Dog and Cat conform to it. The playSound the function takes an argument of type Animal, emphasizing the power of protocol-based types in allowing different types to share a common behavior.

Key Differences

1. Abstraction Level

  • Opaque return types conceal the exact type, offering encapsulation with flexibility.
  • Protocol-based types emphasize shared behaviors and interfaces, promoting a common contract.

2. Function Signatures

  • Opaque return types are often used in function signatures to provide abstract return types.
  • Protocol-based types can be used for various purposes, such as method parameters, property types, and more.

3. Implementation Changes

  • Opaque return types facilitate future implementation modifications while maintaining compatibility.
  • Protocol-based types ensure adherence to specific behaviors, encouraging consistent implementations.

4. Type Inference

  • Opaque return types demand explicit annotation with the some keyword.
  • Protocol-based types offer better type inference by design.

Conclusion

Opaque return types and protocol-based types are essential components of Swift’s toolkit that empower developers to craft robust, adaptable, and comprehensible code. The choice between these approaches depends on your project’s requirements and your goals for abstraction and encapsulation. Opaque return types are perfect for concealing implementation specifics, while protocol-based types foster cohesive architectures built on shared behaviors.

By exploring the distinctions through practical examples, you can gain a deeper understanding of when to employ opaque return types and protocol-based types. Armed with this knowledge, you can design code that balances encapsulation with flexibility and encourages a modular, maintainable, and extensible codebase.

--

--