Heretical iOS & Swift Coding Conventions
Over the years I've picked up a mix of coding conventions from various different languages. Mixing them in together in any OOP language I use brings me great results but outrages native language users
Since I started coding professionally I’ve worked with a number of languages and platforms all influencing my thinking and understanding what good code looks like.
I worked with Java, Ruby, Objective-C, Swift, Kotlin, Typescript, Dart, and many others spanning iOS, Android, Ruby on Rails, Web, and many other platforms.
As some of the highlights:
Java taught me the value of good design patterns
Ruby and Objective-C the value of message sending and duck-typing in OOP systems
Swift, the value of reducing optional ambiguity
Typescript and Dart showed that web development doesn’t have to be the Javascript insanity
Kotlin, that a language could actually have good defaults
Learning from all of those languages and their respective communities and mixing in Uncle Bob’s Clean Code ideas would always put me at odds with particularities and conventions of each individual language though.
A lot of conventions and heuristics I picked up will sound heretical to Swift/iOS developers because they don’t follow the “standard convention” or “what Apple says you should do” but they all aim to make the code more unified, decoupled, and clear to anyone reading regardless of the background, language, and the platform they come from.
The List
Here’s the list with some brief explanations of why for each heuristic:
no side-effects in setters or getters. setters should be only used to write/set the data, getters only to get/return it. if you need observation use Rx or other types of streams, if you need a side effect on write - implement a method that does data setting and a side effect
no side effects or “launch” or business logic methods in the object constructors. just like with setters you should be able to construct it (init an object) and not worry about some process starting or side effects happening
always final on classes that are not intended to be subclassed. Absence of final should always mean that there is a subclass in existence.
alway use private by default for any property or method declaration. only make internal/public/open if it is intentional.
use structs only for data objects or DAO-style viewmodels, everything else should be a class. (exception is of course where it’s unavoidable such us in SwiftUI case)
absolutely no force ! unwrapping throughout the codebase
no nested functions or classes
no
guard let
useif let
insteadno extensions, unless you don’t have access to the original type, i.e. when you extend a third party library (apple’s code considered to be 3rd party)
no private extensions for code organization. if you need to organize code use // MARK: - BEGIN Something and // MARK: END Something -
no protocol extensions
no extensions for protocol conformance, if you need to add more interfaces to an object, add them to its main interface/protocol
inject all dependencies via constructor. property DI is only allowed when necessary to avoid circular reference counting and for weak listeners/delegates
aim to have one single interface for a type. If a type has to implement multiple interfaces have the main interface conform to them if possible
adhere to SOLID principles as much as possible for better code quality
adhere to Clean Code principles as much as possible for better code quality
err on the side of more verbose, longer names, for variables, classes, objects, properties, etc. rather than short
prefer more generic, rather then “swifty”, naming convention for interfaces/protocols. i.e.
SomethingInteface
orSomethingServicable
. Avoid naming them the other way around whereSomethingImp
is the implementation andSomething
is the interfaceprefer RIBs naming convention over default Swift convention i.e. listeners, streams, components, etc.
keep control over the app behavior and code as much as possible and away from 3rd party libraries and frameworks including Apple’s. For example, override back button and trigger route away actions explicitly instead of relying on apple navbar behavior or the VC/component to dismiss itself
prefer one way data stream/flow, the RIBs style, even in non-RIBs code if possible (i.e. broadcast data down to children via streams or pass one time at creation, and communicate up to parent via one-to-one listener interface)
avoid singletons across the codebase, explicit or implicit (i.e. static methods). Having a single object for the whole codebase is not only permitted but encouraged
no standalone objects implementing
UITableViewDataSource
andUITableViewDelegate
. If you need to have a table view delegate/data source make the view that creates/adds the table view as a subview the delegate/data source. The way Apple implemented those two interfaces isn’t very SRP adhering which makes it too hard reason about. Plus, separating table source/delegate into an object tends to breed too much unnecessary state which really should just belong to the view.
Conclusion
Swift and iOS devs, you are welcome to throw tomatoes at me now 😂