ProgrammingiOS Developer

Explain how protocol abstraction works in Swift and how it differs from class inheritance? When should you use protocols instead of class hierarchy?

Pass interviews with Hintsage AI assistant

Answer.

Background:

Protocol abstraction appeared in Swift as a counter to classical object inheritance in OOP. While Objective-C and other OOP languages predominantly used the "general to specific" inheritance model, Swift has promoted protocols as the primary means of achieving abstraction from the beginning, emphasizing composition over inheritance.

Problem:

Classical inheritance implies a rigid hierarchy: a tree of subclasses with compulsory extensions through override. This limits flexibility, leads to "fragile" code, complicates refactoring, and bloats base ancestor classes. Moreover, Swift does not support multiple class inheritance, meaning that code reuse is only possible through other mechanics.

Solution:

Protocol abstraction allows declaring a "set of requirements" that a type must implement. Protocols can be extended (extension) to inject common logic, bringing them closer to the concept of "mixins":

Code example:

protocol Drawable { func draw() } extension Drawable { func draw() { print("Default drawing") } } struct Circle: Drawable {} let c = Circle() c.draw() // Will output "Default drawing"

Key features:

  • Protocols support multiple composition (multiple requirements).
  • Do not create rigid hierarchies — easier to extend, do not break the architecture.
  • Can work with both value types (struct/enum) and class, which is impossible with inheritance.

Trick questions.

What is the difference between protocol extension and regular implementation in a class?

Extending a protocol via extension adds a default implementation only for cases when the user hasn't implemented this method in their type. If the method is explicitly implemented in the type, that one is called.

Example:

protocol Demo { func foo() } extension Demo { func foo() { print("default") } } struct X: Demo { func foo() { print("custom") } } X().foo() // "custom"

What happens if a type implementing a protocol and its extension is accessed as protocol data?

If the protocol declares a method as mandatory (requirement), the specific type's implementation will be used even when cast to the protocol type. However, if a new (previously undeclared in the protocol) property is added in the extension, it will only be available through the extension, not through the protocol type.

Can you store instances of different structs implementing the same protocol in an array?

Yes — thanks to "existential" types (e.g., [Drawable]) you can store heterogeneous collections:

struct Tri: Drawable { func draw() { print("Triangle") } } let arr: [Drawable] = [Circle(), Tri()] arr.forEach { $0.draw() }

Common mistakes and anti-patterns

  • Inheriting from a fat superclass for common interface instead of splitting into protocols
  • Overusing extension without explicit requirements in protocols
  • Attempting to store a protocol with associatedtype in a collection ([SomeProtocol]) — this is not supported

Real-life example

Negative case

In the company, there was a base superclass Shape, from which all shapes (Circle, Square, Polygon) inherited. The base class grew larger because each new shape had to be supported through override. It became increasingly difficult to expand the system — each new type broke ABI and forced rewriting of existing code.

Pros:

  • Quick implementation of new common methods via base class

Cons:

  • Monolithic hierarchy with excessive dependencies
  • Poor reusability outside the hierarchy
  • Conflicts of overridden methods

Positive case

They started using several protocols: Drawable, Colorable, Animatable. Now each shape can easily be made "animatable and colorable" simultaneously without changing the other structures. New functionality is added through extension.

Pros:

  • Flexibility, easy support and extensibility
  • Improved reusability in different contexts

Cons:

  • Requires careful API design and knowledge of associatedtype