Polymorphism and Protocols in Swift (Xcode 6.0.1)


The word polymorphism is not to be found anywhere in a search of Apple's two iBooks about the Swift language (and its use with Objective-C). But don't take this to mean that polymorphism is absent from Swift.

Polymorphism

Polymorphism is the ability of a class instance to be substituted by a class instance of one of its subclasses. For example here we have two classes, where B is a subclass of A:
class A {
    func c()->String {
    return "A"
    }
}
class B: A {
    override func c()->String {
        return "B"
    }
}
Creating a variable with an instance of class A the variable is inferred to be of type A, but polymorphism enables us to switch the value of a to an instance of class B() like so:
var a = A()
a = B()
There are no compiler complaints about this but if we were to attempt to substitute the value of a for a different class that wasn't a subclass of A then the compiler would raise an error. For example:
class A {
    func c()->String {
    return "A"
    }
}
class B: A {
    override func c()->String {
        return "B"
    }
}
class C {
    func c()->String {
        return "B"
    }
}
var a = A()
a = B()
a = C() // error C is not convertible to A
And this is because Swift is a strongly-typed language, meaning that types are fixed at creation. (But as we see, the rules of polymorphism mean that this strong typing is not totally inflexible.)

Casting

Hand-in-hand with polymorphism goes type-casting. Here we see that a can be assigned to a new variable b and in doing so we have decided that we would like to define b as being of type B (and so we use casting via the as keyword).
var a = A()
a = B()

var b = a as B
b = A() // error b is not convertible to A

var c = b as A
c = B() // OK
But now b cannot be assigned an instance of A because A is not a subclass of B, it is a superclass. However, we can cast b to type A and create a new variable c. And variable c can be assigned an instance of B, because B is a subclass of its type (A).

Inheritance

Since a subclass inherits the properties and methods of its superclass, a superclass cast to a subclass doesn't lose any of its functionality (although elements might be overridden by the subclass). The reverse cannot be said to be true of a subclass assigned to a variable or constant of its superclass type because there might be additional properties and methods added by the subclass that are then inaccessible.
class A {
    func c()->String {
    return "A"
    }
}
class B: A {
    var a = "hello world"
    func d() {
        
    }
    override func c()->String {
        return "B"
    }
}


var a:A = B()

a.a // error 'A' does not have a member named 'a'
a.d() // error 'A' does not have a member named 'd'

var b = a as B

b.a // OK
b.d() // OK
It is important to note, however, that casting to a superclass, the missing members do not vanish but instead are inaccessible until the instance is cast to its original type. It must also be noted that the missing members are not in any way reset by being cast to the superclass, everything remains intact as can be seen here:
var b = B()
b.a = "help me"
var a = b as A
var c = a as B
c.a // returns "help me"

Polymorphism and protocols

While class-based polymorphism is available in Swift, NSHipster tells us to consider polymorphism through protocols in Swift as our first method of approach:
Within the Object-Oriented paradigm, types are often conflated with class identity. When programming in Swift, though, think about polymorphism through protocols first, before resorting to inheritance.
And we see in this code example how protocols behave in the same polymorphic manner as classes:
protocol P {
    func c()->String
}

class A: P {
    func c()->String {
    return "A"
    }
}
class B: P {
    var a = "hello world"
    func d() {
        
    }
    func c()->String {
        return "B"
    }
}


var b:P = B()
b = A()
But because protocols can also be applied to structs and enums in Swift, the polymorphism is extended beyond classes:
protocol P {
    func c()->String
}

class A: P {
    func c()->String {
    return "A"
    }
}
class B: P {
    var a = "hello world"
    func d() {
        
    }
    func c()->String {
        return "B"
    }
}

struct S: P {
    func c()->String {
        return "S"
    }
}


var b:P = B()
b = A()
b = S()
And I think this is one reason that NSHipster asks us to favour protocols, but also protocols mean that we can detach polymorphism from inheritance and that combining the two becomes a choice, not a necessity.

Casting and protocols

While adopting the same protocol doesn't enable type instances to be cast to other types, optionals do enable this situation to be dealt with
var b:P = B()
var a = b as? A
a // 'a' is a optional of type A? but has a value of nil
and if one type does happen to be a subclass of another class then casting works in the normal way (except that an optional is returned rather than a regular instance).
protocol P {
    func c()->String
}

class A: P {
    func c()->String {
    return "A"
    }
}
class B: A, P {
    var a = "hello world"
    func d() {
        
    }
    override func c()->String {
        return "B"
    }
}


var b:P = B()

var a = b as? A
a // 'b' is now of type A? with a value

Conclusion

This post began as a short stroll, but grew into a longer ramble. And I hope it makes some contribution to discussions about polymorphism and protocols. For further discussion of where protocols can take us in Swift see these posts.

To close, I think it sensible to repeat the warning supplied by Zigurd Mednieks et al. in Programming Android:
While occasionally necessary, excessive use of casting is an indication that the code is missing the point. Obviously, by the rules of polymorphism, all variables could be declared to be of type Object, and then cast as necessary. To do that, however, is to abandon the value of static typing. 
So if you use the knowledge about typecasting contained in this post, then use it wisely.

Comments