Creating Traits or Mixins in Swift

By · Published · swift, macos, ios

Object oriented programming is great, but sometimes things don't fit neatly into a superclass/subclass hierarchy. You may have a piece of code that would be needed in several contexts, but for technical reasons beyond your control you cannot merge them into a single hierarchy.

Some languages have the concept of multiple inheritence, where a subclass can specifically inherit from several parents. But this has it's own set of problems. Many other languages, however, solve this through the use of traits or mixins. These allow us to have a set of methods that are basically copied into the object at compile time. This way they can be used anywhere they are needed.

Swift doesn't have the concept of mixins or traits per se. But, starting with Swift 3, you can get very equivalent functionality using protocol default implementations.

Protocol Default Implementations

Swift is very big on protocols. They are used everywhere, so by now you should be reasonably familiar with protocols. Swift is also big on extensions, which allow you to extend existing objects (even those whose source you do not have access to) with your own functionality.

All protocol default implementations are is a merging of these two things. So you create a protocol, then extend the protocol with your functionality.

protocol DoesSomething {
    public func doSomething()
}

extension DoesSomething {
    public func doSomething() {
        print("Hello world!")
    }
}

Pretty easy, huh? And using it is just as easy:

class Foo: DoesSomething {
    public func printSomething() {
        doSomething()
    }
}

let f = Foo()
f.printSomething()

Should print:

Hello world!

A Word of Warning

I sometimes think that traits/mixins/protocol default implementations may be an anti-pattern. My conclusion so far has been (like most things), "it depends on how they are used" if they are an anti-pattern or not.

My main problem with them is that they hide functionality in a way that might not be entirely clear to another developer (or even you, months later when you go to fix a bug). While the inheritence model of object-oriented programming is implicitly understood by most developers, traits throw a monkey wrench into that. They're not in the hierarchy, but are brought in at compile time.

Especially if I'm in an object that descends from an object that pulls in a trait, at that point it is usually faster for me to just search the repository for the method implementation rather than try to find where it was done by tracing back through the parents. I have to check all the parents and any traits they pull in at any level. And changing the trait may have unintended effects that may be difficult find and fix because it is used in many contexts.

I guess what I'm saying is, it is very easy to create unmaintainable spaghetti code using this kind of functionality. It can be tempting to use this functionality as a code organization method. I disagree with that approach for the reasons listed above except in some very, very specific and limited circumstances. Tools like these are best used sparingly, when you literally have no other option because code is needed in multiple places that cannot otherwise descend from a common ancestor.

But that can happen, especially when using code or system APIs that you don't control (such as Cocoa and CocoaTouch). Sometimes, you really do have no choice, so I'm glad this functionality is here.

( Comments )

Did something I wrote help you out?

That's great! I don't earn any money from this site - I run no ads, sell no products and participate in no affiliate programs. I do this solely because it's fun; I enjoy writing and sharing what I learn.

All the same, if you found this article helpful and want to show your appreciation, here's my Amazon.com wishlist.


Related Posts

Debugging the Responder Chain in Swift

The Responder Chain: Bubbling Events using NSResponder and UIResponder in Swift


comments powered by Disqus