ProgrammingSwift middle developer

What is protocol composition in Swift, how does it work, and what is it used for? What are the pitfalls when using multiple protocols at the same time?

Pass interviews with Hintsage AI assistant

Answer.

In Swift, the experience of many OOP languages has been generalized and enhanced through the ability to compose (rather than just inherit) protocols. Protocol composition allows you to declare a variable, function parameter, or generic with the requirement to conform to multiple protocols simultaneously. This mechanism is extremely useful when it is necessary to work with objects that exhibit the behavior of multiple contracts (interfaces), while flexibly avoiding the downsides of multiple inheritance. The problem that composition solves is the need to express "an object must satisfy a group of requirements," rather than just one.

Swift uses a special syntax for this: the union of protocols is denoted by the & (ampersand) symbol, for example, protocolA & protocolB. Under the hood, runtime checks are performed (for instance, during type casting and within generic contexts). This minimizes the number of types and flexibly implements the "separation of concerns" pattern.

Example code:

protocol Drawable { func draw() } protocol Movable { func move() } struct Sprite: Drawable, Movable { func draw() { print("Sprite draws") } func move() { print("Sprite moves") } } func animate(object: Drawable & Movable) { object.draw() object.move() } let s = Sprite() animate(object: s)

Key features:

  • Allows flexible expression of behavior composition without inheritance hierarchies
  • Guarantees compliance with all contracts at once
  • Compatible with generic parameters and type aliases

Trick questions.

Can you create a variable of type only protocolA & protocolB without tying it to a specific struct or class?

Yes, you can declare a variable as conforming to multiple protocols at once, for example:

var obj: protocolA & protocolB

But it's important: such variables can only reference objects (and not value types), if at least one protocol in the composition is constrained to class types (protocol: AnyObject).

Can a class type be included in the composition, for example, SomeClass & Drawable?

Yes, but with nuances: a composition of the form SomeClass & Protocol requires that the values must be instances of that class (or its subclasses) that implement the protocol. This approach is used to constrain generic types.

Can protocol composition be used as an associated type in protocol extension?

Yes, but there are limitations: you cannot declare an associatedtype as a composition, but you can use where in an extension to constrain protocol compositions, for example, an extension that applies only to types conforming to multiple protocols.

Typical mistakes and anti-patterns

  • Using composition with eight or nine protocols: this is a sign of overloaded architecture and poor separation of concerns
  • Casting a value type (struct) to a variable of protocol composition constrained by AnyObject will always result in an error
  • Using the same composition in different parts of the application without a typealias: complicates readability

Real-life example

Negative case

In a project, 5 similar protocols were implemented — Drawable, Movable, Resizable, Colorable, Animatable. Composition was applied everywhere as Drawable & Movable & Resizable & Colorable & Animatable. Typical mistakes were accompanied by complex bugs due to entities failing to implement one of the contracts.

Pros:

  • Deep inheritance is not required
  • Easy to add or remove functionality

Cons:

  • Difficult to track discrepancies
  • Complicated testing
  • Poor readability of declarations

Positive case

Instead of a complex composition, two main protocols (for example, Actor and Viewable) were highlighted, a typealias for the composition "DynamicEntity" was created, and it was used everywhere. Responsibilities were clearly delineated.

Pros:

  • Code is easier to read and maintain
  • Tests clearly highlight behavior for DynamicEntity
  • Quick modification of the list of requirements

Cons:

  • Requires rethinking of the architecture
  • Sometimes it is necessary to break existing classes to meet the requirements