Structuring your Swift files for simpler code

Let’s reshape our code to better reflect the categories of complexity

Robert j Chatfield
4 min readSep 7, 2017

There are many things we can do to improve our codebases and make it easier to build features, maintain, debug, and also onboard new developers. Good architecture, consistent naming and code style can all help, but one thing I’ve noticed recently is the feeling I get while reading messy code. I found myself getting bogged down in certain files and I wanted to see if I could do something about it. So with a little bit of careful refactoring and restructuring, I’ve managed to settle on a simple template that made each of our classes seem more straight forward and less messy.

Before I share my preferred code structure, it’s important to take a moment and notice the chaos of every day code, and get in touch with how taxing it can feel. Let’s aim to find out what these problems are, then categorise them, and separate the simple code from the complex.

So let’s jump in.

PART 1: JUST 6 LINES OF SWIFT

Let’s now imagine you open up a file in a codebase, either in a PR of your own codebase, or a new codebase of a team you’ve just joined, or perhaps something that you once wrote but you can’t remember the details of…

class Foo: Engine { 
let x = Bar()
var y: Any? = nil
func doF() -> Any { ... }
override func doA() -> Any { ... }
}

How does this code make you feel? What do you know about it by just looking at it? What questions can you ask?

  • What does this Foo class do?
  • Who creates and/or owns a Foo?
  • Is Engine a class or a protocol?
  • Is this @objc?
  • What behaviour and state am I inheriting?
  • Are there other sub classes of Foo?
  • Should this really be a reference type or a value type?
  • If Engine is a protocol, what did I need to do to conform to it?
  • Are there any optional implementations I’m not implementing?
  • Is Bar a value type, or is it mutable?
  • Who changes y, and why?
  • Do I need Copy-on-Write for y?
  • Is y thread safe?
  • What is a valid y? (If I set a String, will it crash somewhere else in the app?)
  • Are any of those Anys semantically the same type?
  • Who calls doF()? Does anyone call this?
  • Could I make it static?
  • Do I expect others to override this somewhere?
  • When does doA() get called in super?
  • In doA(), am I meant to call super or not?

Did I miss any questions you’d ask? I’m sure there are plenty more. But for just 6 lines of code, I think we have enough.

While we can do our best to write clean code, this confusion is sometimes unavoidable. But admitting it’s a problem is the first step. Let’s reshape our code to better reflect the categories of complexity that cause all these questions.

PART 2: MY TEMPLATE

Here is my preferred template for new Swift files (no matter how large or small).

protocol FooDelegate: class {}
// MARK: -
final class Foo: UISomething, Engine {
// MARK: - Types
// MARK: - Properties
// MARK: - IBOutlets
// MARK: - Life cycle
// MARK: - Overrides
// MARK: - <Engine> Conformance
// MARK: - Methods
// MARK: - Private methods
}
// MARK: - Baz Conformance
extension Foo: Baz {
...
// MARK: - Private methods
}
  • Keep protocols and nested types should be towards the top.
  • Keep subclassing to a minimum.
  • Keep state to a minimum. Chris Eidhof’s pointed this out in a recent tweet:
  • I regularly challenge the addition of properties to classes that previously were stateless. Best advice is to try to keep your state in one place, and pass that state to functions when they need it. Helps testing too.
  • Keep read and write access to a minimum. Use private and letwherever possible. private lazy var is also nice for cases where you would setup later, but it helps to explain that it should only be set once.
  • Keep init, deinit, viewDidLoad, awakeFromNib, prepareForReuse up near your properties. If you’re new to a class, it’s good to see what’s in it and how to make one.
  • Keep the overrides together. Overriden properties and methods are complex. As a user, you have to now understand the super class and the intricacies of when these methods will be called and how often. It’s not always clear if you’re meant to call super, or if you were refactoring would you know if you can delete the override all together. If the overrides are scattered throughout a long class, then the class’s complexity can be underestimated.
  • Sometimes protocol conformances on generic classes can’t be in separate extensions, so I like these together under the Overrides. Otherwise I like protocol conformance in their own extensions.
  • Keep your public (or internal) methods in one clear spot. If this class is being used by other classes, it’s helpful to have all the public API methods together. This is what it does.
  • Finally, keep all the private methods together. Private methods are great when I’m refactoring because I know I can rename or merge methods as needed, free from breaking changes. Extensions are allowed to have their own private helper methods sections. Swift 3 private used to keep these methods separate from the other extensions, but access control is opening back up in Swift 4.

That was a big brain dump, but I hope there’s something useful in there for you and your code base. If I’ve missed something you think I should add, please let me know.

Thanks for reading.

If you liked this, read my other rants and follow me on Twitter.

--

--