Multiple Inheritance in Swift
Although Swift does not support multiple inheritance, it offers rich API that gives possibility to simulate it. Let’s take an in-depth look at multiple inheritance and its implementation in Swift.
Introduction
There are two major ways of structuring data in programming languages. The first one can be said to derive from standard branches of mathematics. Data is organized as cartesian products and languages supporting this kind of organization are known as functional.
The second approach can be considered to derive from biology and taxonomy. Data is organized in a hierarchy of classes and subclasses, where lower levels inherit all the attributes higher up in the hierarchy. This approach is called object-oriented programming.
Inheritance can be single and multiple. While the former is supported in Swift by default, we can approach the latter very closely by applying certain programming techniques.
Let’s see what options do we have with regards to multiple inheritance implementation in Swift and which restrictions does it impose.
Understanding Multiple Inheritance
Multiple inheritance is an object-oriented concept in which a class can inherit behavior and attributes from more than one parent class.
Along with single inheritance and composition, multiple inheritance offers another way of sharing code between classes that can be very beneficial if used correctly.
Through the rest of the article we’ll elaborate on its proper usage as well as provide its Swift implementation.
Multiple Inheritance in Swift
Although multiple inheritance is a standard feature of some programming languages, like C++, it is not the case for Swift. In Swift a class can conform to multiple protocols, but inherit from only one class. Value types, such as struct and enum, can conform to multiple protocols only.
Swift supports only multiple inheritance of protocols.
Protocols with default implementations give us just enough flexibility to approach multiple inheritance very closely. Here is how it looks:
protocol HelloPrinter {
func sayHello()
}
extension HelloPrinter {
func sayHello() {
print("Hello")
}
}
Now when you create a new type conforming to that protocol, it gets the implementation of sayHello
for free:
struct MyStruct: HelloPrinter {}
let myStruct = MyStruct()
myStruct.print() // Prints "Hello"
However, conforming to more than one protocol with default implementation is not enough to call it multiple inheritance. More importantly, the protocols must satisfy the notion of mixin.
Understanding Mixins
A mixin is a class that contains methods for use by other classes without having to be the parent class of those other classes.
The idea behind mixins is simple: we would like to specify an extension without pre-determining what exactly it can extend. This is equivalent to specifying a subclass while leaving its superclass as a parameter to be determined later.
Mixins are generally not intended to be instantiated and used on their own. They provide standalone behavior that is supposed to be added to other types.
Here are the key points to understand about mixins. A mixin:
- Can contain both behavior and state.
- Is not supposed to be initialized.
- Is highly specialized and narrow in its functionality.
- Is not intended to be subclassed by other mixins.
With the help of mixins we can approach multiple inheritance implementation in Swift very closely.
Implementing a Mixin in Swift
Animating and applying visual decorations to UIView
are among the frequent tasks that iOS developers encounter. To demonstrate the practical use of multiple inheritance, we define several mixins and make UIView
inherit from them.
// MARK: - Blinkable
protocol Blinkable {
func blink()
}
extension Blinkable where Self: UIView {
func blink() {
alpha = 1
UIView.animate(
withDuration: 0.5,
delay: 0.25,
options: [.repeat, .autoreverse],
animations: {
self.alpha = 0
})
}
}
// MARK: - Scalable
protocol Scalable {
func scale()
}
extension Scalable where Self: UIView {
func scale() {
transform = .identity
UIView.animate(
withDuration: 0.5,
delay: 0.25,
options: [.repeat, .autoreverse],
animations: {
self.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
})
}
}
// MARK: - CornersRoundable
protocol CornersRoundable {
func roundCorners()
}
extension CornersRoundable where Self: UIView {
func roundCorners() {
layer.cornerRadius = bounds.width * 0.1
layer.masksToBounds = true
}
}
Next we make UIView
conform to all these protocols.
extension UIView: Scalable, Blinkable, CornersRoundable {}
Each view and it’s subclass are getting methods from mixins for free.
aView.blink()
aView.scale()
aView.roundCorners()
And the visuals look like this:
The Diamond Problem
The Diamond Problem is best described with next diagram.
On the diagram MyClass
conforms to ChildA
and ChildB
protocols, which in their turn both conform to Root
. method()
is defined only in Root
, but is extended in each from 3 protocols. As a result, compiler cannot define which default implementation of method()
is inherited by MyClass
.
Here is the code that demonstrates the problem:
protocol Root {
func method()
}
extension Root {
func method() {
print("Method from Root")
}
}
protocol ChildA: Root {}
extension ChildA {
func method() {
print("Method from ChildA")
}
}
protocol ChildB: Root {}
extension ChildB {
func method() {
print("Method from ChildB")
}
}
class MyClass: ChildA, ChildB {} // Error: Type 'MyClass' does not conform to protocol 'Root'
The above situation can be referred to as a Diamond Problem. The next shortened case is also valid for Swift:
And here is the code that demonstrates the issue:
protocol ProtocolA {
func method()
}
extension ProtocolA {
func method() {
print("Method from ProtocolA")
}
}
protocol ProtocolB {
func method()
}
extension ProtocolB {
func method() {
print("Method from ProtocolB")
}
}
class MyClass: ProtocolA, ProtocolB {} // Error: Type 'MyClass' does not conform to protocol 'ProtocolA'
In this variation of the diamond problem both ProtocolA
and ProtocolB
have default implementations of method()
which results in name collision. The compiler cannot decide on which default implementation of method()
MyClass
should inherit and the code fails with a Swift compiler error.
The diamond problem arises when a class or a value type conforms to a protocol along multiple paths in the inheritance graph.
Solution to Diamond Problem
The answer might disappoint you: there is no silver bullet for the diamond problem in Swift. Two possible solutions exist, each not without its flaws.
- Re-implement the conflicting methods in derived class. Might be achieved by means of composition and delegation. Problems: introduces extra indirections, increases code complexity.
- Change conflicting methods in one of the remote ancestors. Problems: requires global knowledge of the inheritance graph. You might not have the ownership over the adverse code, thus unable to change it.
Before applying each solution, I suggest to asses the benefits you gain from multiple inheritance against the overhead introduced by these recipes.
Wrapping Up
Multiple inheritance is the fundamental concept of the object-oriented programming and when applied right, it can be very beneficial to your code.
Swift does not have default language mechanisms for implementing multiple inheritance. However, by applying protocols with default implementations that comply to the notion of mixin, multiple inheritance in Swift can be approached very closely.
Mixins eradicate the boundary between the inheritance and composition. This kind of software design can be called compositional inheritance.
When implementing multiple inheritance, you must be aware of the Diamond Problem and its variations. The solutions to that problem are non-optimal and might outweigh the potential benefits.
Thanks for reading!
If you enjoyed this post, be sure to follow me on Twitter to keep up with the new content. There I write daily on iOS development, programming, and Swift.