Ultimate Swift Code Style Guidelines
The main purpose of these guidelines is to determine specific rules following which may make an engineers's code having a unified style – which improves readability and maintainability, hence less fallibility. Most of the other existing similar guidelines don't provide exhaustive requirements on any code style aspect, or aren't sufficiently accurate, or have their own purposes which don't correlate with the general software development. This document is a try to create one that does.
An engineer is free to use these guidelines, changing any rules according to their wish. However, if any rule is broken it must be broken the same way across the whole project. I.e. any broken rule shall be replaced by another, instead of carelessly neglecting it.
One handy way to base own guidelines on these ones is to fork the repository and change any rule. However, if an engineer feels that something is completely wrong and should be fixed, they are free to fork and create a pull-request for initiating a discussion.
Table of Сontents
Guidelines
1.Project
1.1.Files and Directories
1.1.1.CVS
1.1.2.File Structure
1.2.Imports
1.2.1.Members' Order
1.2.2.Protocol Conformances
1.2.3.Explicit Documentation
1.3.Comments
1.3.1.Naming
1.4.Types
1.4.1.Methods
1.4.2.API
1.5.Encapsulation
1.5.1.Implementation
1.6.Methods
1.6.1.Optionality
1.6.2.State and Side-Effects
1.6.3.Testing
1.6.4.Formatting
1.7.Methods
1.7.1.Closures
1.7.2.Control Flow
1.7.3.Literals
1.7.4.Constants
1.7.5.Colons
1.7.6.Miscellaneous
1.8.Links
2.Guidelines
Project
Projects don't tolerate any warnings.
Type and function names, file names, documentation, comments, everything, shall be written in American English.
Files and Directories
All folders and files in a directory or a project section are sorted alphabetically, first directories, then files.
Files sorting doesn't consider files' extensions. However, an engineer is strongly encouraged to name files to have files which are close to each other semantically remaining closer to each other in the list.
Files are called after their containing types, or the primary one if multiple type declarations are contained.
If the primary content of the file is a type extension, the file shall be named after this type concatenating a +
sign and conformance protocol name (e.g. String+ConvertibleToNumber.swift
). It's acceptable to name a file without a protocol name after the +
sign if the file contains multiple protocol conformances or helper and convenience methods (e.g. String+.swift
).
CVS
Not going deeply into the topic, since each CVS has its own guidelines.
Every commit is atomic and presents a meaningful and working state of the project. Checking out at any commit results in a working software. Considering these two rules, an engineer shall think thoroughly about implementation phases, or refactoring steps.
Commit names are formulated in the imperative case ("Do the thing"). Comprehensive details are optional and separated by a blank line if present.
Any temporary code, debug console output, debug commits shall never be committed.
File Structure
File is divided into sections by // MARK:
comments. Root declarations (e.g. two classes on the same level) are separated by // MARK: -
. Declarations within the same group of a type are gathered together in sections, like // MARK: - Methods
. Multiple declarations within a group – like // MARK: Private methods
. This type of comments (as well as any other) is kept close to grammatically correct language.
Don't:
//MARK:
//MARK :
// MARK: -Methods
Indentation shall be made with whitespaces, because tab symbols can be represented differently in different environments. One level of indentation is four whitespaces.
Braces should be weighted: If there's a whitespace after the opening brace, there always must be one before the closing brace. Type declarations shall always have such whitespaces coming with braces, methods shall not.
Methods with implementation shall be always separated with an empty line. Method declarations and any properties shall not.
Files shall end with a single blank line.
Examples of file structure:
Do:
import GatewayFoundation
import Foundation
protocol GatewayServiceDelegate {
// MARK: - Methods
func dataUpdated()
}
// MARK: -
protocol GatewayService {
// MARK: - Properties
var delegate: GatewayServiceDelegate? { get set }
var active: Bool { get }
var data: [GatewayDataObject] { get }
// MARK: - Methods
func connect()
func disconnect()
}
// MARK: -
final class GatewayStandartService: GatewayService {
enum GatewayType {
case demo
case stage
}
// MARK: - Properties
// MARK: GatewayService protocol properties
weak var delegate: GatewayServiceDelegate?
var active: Bool {
return server.active
}
var data: [GatewayDataObject] {
return server.data
}
// MARK: Private properties
private static let gatewayStandardAddress = URL(string: "https://standard.gateway.edu/")!
private let serviceType: GatewayType
private lazy var server: DataServer {
let server = DataServer(serviceType: serviceType)
server.delegate = self
return server
}
// MARK: - Initialization
init(serviceType: GatewayType = .stage) {
self.serviceType = serviceType
}
// MARK: - Methods
// MARK: GatewayService protocol methods
func connect() {
try? server.establishConnection()
}
func disconnect() {
server.breakConnection()
}
}
// MARK: - DataServerDelegate
extension GatewayStandartService: DataServerDelegate {
// MARK: - Methods
// MARK: GatewayStandartService protocol methods
func connectionEstablished() {
notifyDelegate()
}
func dataUpdated() {
notifyDelegate()
}
// MARK: Private methods
private func notifyDelegate() {
delegete?.dataUpdated()
}
}
Don't:
import Foundation // FIXME: Wrong imports order
import GatewayFoundation
import UIKit // FIXME: Unused import
import Darwin // FIXME: Excess import, Foundation includes Darwin
// FIXME: Extra empty line
protocol GatewayServiceDelegate { // FIXME: Absent section dividers
func dataUpdated() // FIXME: Broken indentation
}
protocol GatewayService { // FIXME: Absent file structural divider and an empty line
// MARK: - Properties
var active: Bool { get }
var data: [GatewayDataObject] { get }
var delegate: GatewayServiceDelegate? { get set } // FIXME: Less strict accessable properties should go first.
// MARK: - Methods
func connect()
func disconnect()
} // FIXME: Absent empty line before type declaration closing brace
// MARK: -
final class GatewayStandartService: GatewayService, DataServerDelegate { // FIXME: Additional conformances should be declared in extensions.
// MARK: - Properties
// MARK: GatewayService protocol properties
private static let gatewayStandardAddress: URL = URL(string: "https://standard.gateway.edu/")! // FIXME: Private and "public" members are mixed.
private let serviceType: GatewayType
weak var delegate: GatewayServiceDelegate?
var data: [GatewayDataObject] { server.data }
var active: Bool { // FIXME: Properties of the same access level should be sorted alphabetically.
server.active // FIXME: Single statement scopes are preferred to be placed on a single line with both braces.
}
private lazy var server: DataServer {
let server = DataServer(serviceType: serviceType)
server.delegate = self
return server
}
// MARK: - Initialization
init(serviceType: GatewayType = .stage) {
self.serviceType = serviceType
}
// MARK: - Methods
// MARK: GatewayService protocol methods
func connect() {
try? server.establishConnection()
}
func disconnect() {
// FIXME: Empty lines in the beginning and the end of the method.
server.breakConnection()
}
// MARK: GatewayStandartService protocol methods
func connectionEstablished() {
notifyDelegate()
}
func dataUpdated() {
notifyDelegate()
}
// MARK: Private methods
private func notifyDelegate() {
delegete?.dataUpdated()
}
enum GatewayType { // FIXME: Nested types accessible from the outside of the type should go first.
case demo
case stage
} // FIXME: No whitespaces after the opening and befre the closing brace.
}
File length doesn't have a specific line number limitation, since basically, code shall respect contemporary software development principles (mainly, S.O.L.I.D., and also complemented by their small siblings – D.R.Y., K.I.S.S., and Y.A.G.N.I.). Following the principles won't let an engineer create an unacceptably long file. However, if an engineer isn't experienced and doesn't have an experienced reviewer, the number of 200 lines (including all formatting and comments) could be used for guidance. Though, this doesn't mean that well organized files of the length of 201 or even 300 lines are necessarily evil – an engineer shall be guided by their common sense.
Imports
All imports go in the very beginning of the file and sorted lexicographically. Empty lines between imports are not used.
More specific imports (like Darwin
) are preferable over less specific (like Foundation
) to keep namespace cleaner and probably, the resulting build thinner.
More specific imports (like Foundation
) are not used explicitly if less specific ones (like UIKit
) are also used.
Members' Order
Properties within a subgroup, as well as enum cases, shall be ordered, firstly, by its access modifier, then by the class/instance membership type, then lexicographically.
Logical sorting is acceptable, but not encouraged and may be used only if all neighboring declarations can be sorted logically. Partially logical and lexicographical ordering is out of use.
Root declarations start from the beginning of the line. Each level of nesting adds a step of indentation.
Public and internal methods shall be sorted in a logical order. It's always possible to decide on one for methods. For example, by remembering the type's lifecycle and methods' calling order.
Do:
func loadView() {
// ...
}
func viewDidLoad() {
// ...
}
func viewWillLayoutSubviews() {
// ...
}
func viewDidLayoutSubviews() {
// ...
}
func viewWillAppear() {
// ...
}
func viewDidAppear() {
// ...
}
func viewWillDisappear() {
// ...
}
func viewDidDisappear() {
// ...
}
Private methods are sorted by their first mention (earlier mentions go first).
Mixing public and private APIs shall be strictly avoided. However, the latter is discussible. An alternative could be to place methods in order of their usage and closer to the first calling. I.e. "as an engineer would read" the source file in order to understand the flow.
Protocol Conformances
Protocol conformances are added within separate extensions unless it's the main role of the type.
Do:
protocol Presenter {
// ...
}
struct DefaultPresenter: Presenter {
// ...
}
protocol Presentable {
// ...
}
final class ViewController: UIViewController {
// ...
}
extension ViewController: Presentable {
// ...
}
Don't:
protocol Presenter {
// ...
}
struct DefaultPresenter {
// ...
}
extension DefaultPresenter: Presenter {
// ...
}
Protocol extensions go after the main declaration and are separated by a // MARK: -
comment with the protocol name. It results in a nice "table of contents" in the file navigator window of an IDE:
Do:
struct SomeType {
// Main implementation goes here.
}
// MARK: - DifferentProtocol
extension SomeType: DifferentProtocol {
// MARK: - Methods
// MARK: DifferentProtocol protocol methods
// Protocol conformance implementation goes here.
}
Explicit Documentation
Each non-private API has HeaderDoc or DocC comments, even if the API is well self-documented. The main assignment of having exhaustive documentation of public APIs is to be visible from different code base places through IDE's help features, such as quick help popovers.
Private APIs are well self-documented and have no explicit documentation in order not to distract from reading implementation details.
Indicating a method's time complexity in documentation generally is a good idea, unless irrelevant.
The basic structure of multi-line descriptions:
Do:
/// Summary.
///
/// Detailed description.
///
/// - Parameters:
/// - parameter parameter1: Parameter description.
/// - parameter parameter2: Parameter description.
/// - Returns: Return value description.
/// - Throws: Exception description.
Multi-line documentation put between /**
and */
is not used, mainly because the former way is generated by Xcode by default.
Don't:
/**
Summary.
Detailed description.
- Parameter parameter1: Parameter description.
- Parameter parameter2: Parameter description.
- returns: Return value description.
- Throws: Exception description.
*/
Everything but summary is optional. The order shall be:
- Summary
- Discussion
Parameter
/Parameters
Returns
Throws
Though, if a method has any parameters, they are necessary to be documented. A single parameter must be documented as - Parameter: Description.
Multiple parameters must be documented by the help of - Parameters:
syntax. If the method has functions as parameters, their arguments must have labels and be documented, as well.
If a method's return value type is not Void
, it's documented.
If a method throws an exception it's documented.
Two HeaderDoc parts are divided by a single blank line. However, if there's no description, blank lines are omitted:
/**
Summary.
- Parameter parameter: Parameter description.
- Returns: Return value description.
- Throws: Exception description.
*/
Links, code snippets, etc. must make use of Apple Markup syntax.
Links to related methods are encouraged and implemented by means of tags. That is, the referenced method is marked with - Tag: SomeTag
and the referencing method uses it as this: [click here](x-source-tag://SomeTag)
.
There's no blank lines between a documentation comment and the method being documented.
Comments
Self-documented code is preferred over comments.
If a commented code is being changed, the comments are either updated or deleted.
One line comments start with \\
(although \\\
makes comments look fancier, it's reserved for HeaderDoc).
Multi-line comments start with \*
and end with *\
. Those are placed on separate lines. No blank lines after symbols denoting the beginning of comment and before ones at the end.
Comments, both single- and multi-line, are placed on the line above the commented code and separated from a preceding code by a blank line. The latter is not relevant if a comment is the first line of the scope. Also, it's acceptable to place one-line comments at the end of the commented line of code, separating it by a space, if and only if the comment doesn't make this line too long.
Naming
All declarations should be self-explanatory and consist of American English words and form correct human-readable phrases when read aloud.
Everything but types is lowerCamelCase
. The types are UpperCamelCase
(a.k.a. PascalCase
).
Abbreviations don't have different cases within and are cased up or down according to the rule above (e.g. let asciiTable: [String : String]
, class SMTPServer
). (A common mistake to use Id
instead of ID
or id
is also not acceptable.)
No underscores, even at the beginning of property names, for making it recognizable, are used. No Hungarian notation for constants (e.g. leading "k") too.
In most cases variables have noun phrase names: let number: Int
. The most significant exception is booleans which are expected to have assertive names (e.g., let necessary: Bool
). Booleans names are preferred without a verb at the beginning if it doesn't introduce ambiguity:
Do:
let necessary: Bool
Don't:
let isNecessary: Bool
Types
Classes and structures are named using noun phrases, as well as protocols describing what an object is. Protocols which add abilities are named descriptively (e.g., Sortable
). Generalization protocols can have the word Protocol
at the end of their name, though, it's not encouraged.
Types implementing design patterns are usually named with the pattern name at the end (e.g., ViewBuilder
, DisplayingStrategy
).
No prefixes shall be used (e.g. just PriceCalculator
instead of XYZPriceCalculator
), since there's no necessity for that in Swift (other than to maintain consistency with Objective-C libraries and components).
Methods
Methods without side-effects have names in the form of noun phrases: let size = view.originalSize()
.
Methods with side effects are called in the form of an imperative verb: list.sort()
.
Similar non-mutating methods that return new values must be called using past participle: let newList = oldList.sorted()
. Or present participle: let newList = oldList.sortingLexicographically()
.
Argument names follow rules for variable names. An engineer makes use of Swift syntax possibilities to make the full declaration a human-readable phrase. For example, func insert(_ element: Element, at index: Int)
is nicely read insert(someElement, at: someIndex)
at the call site.
Factory method names start with the word "make". Both factory methods and initializers have their arguments as a list of items, they are not required to form phrases.
Do:
init(name: String, id: Int)
func makeView(position: CGPoint, size: CGSize) -> UIView
Don't:
init(withName name: String, andID id: Int)
It's very common to force engineers to put an object of delegation as the first argument of delegation methods. This is not strictly necessary and is used only in cases when it makes sense.
Do:
protocol ScreenPresenter {
// MARK: - Methods
func buttonTapped()
}
Don't (most probably):
protocol ScreenPresenter {
// MARK: - Methods
func buttonTapped(_ screen: UIViewController, button: UIButton)
}
UIKit's UIControl
actions are called with the control's name in the beginning and the "action" word in the end:
Do:
@objc
private func nextButtonAction(_ sender: UIButton) { // ...
The sender argument is optional since it's often unused.
Don't:
@objc
private func onNextButtonTapped(_ sender: UIButton) { // ...
The latter naming convention is confusing because makes you think of delegation. However, actions may be thought of as of a kind of delegation, and the naming style might make sense for some code bases.
API
Basically, types representing pure values which could be fairly interchanged by values themselves should be struct
, otherwise (e.g., objects containing state or having a lifecycle) – class
. Other considerations for choosing over struct
s and class
es – expected semantics, memory management, etc., are not the topic of this document.
Properties are expected to have a constant time complexity. If one doesn't it's changed to a method.
Optional booleans and collections are avoided, because they bring degraded states: for example, what's the difference between an empty array and a nil
array within the same context?
Free functions are usually a design flaw. An engineer shall double-check, whether free functions really shouldn't correspond to some type.
Methods that do some actions don't return objects of these actions (e.g., a presenting view controller method shall not return the presented view controller).
Do:
func presentErrorAlert()
Don't:
func presentErrorAlert() -> UIAlertViewController
However, if the return value might be useful in some specific situations, it doesn't force one to use it (@discardableResult
annotation serves this purpose). Though, such return values are not encouraged. An engineer shall follow the principle of the command-query separation.
Abbreviations (except for the common ones) and shortenings are avoided. (Possible exceptions are local variables within implementation blocks of code.)
Do: let currentValue = 1
Don't: let curValue = 1
(Though, it's acceptable in a very limited scope, like a method implementation).
Encapsulation
Any class declaration gains of adding final
modifier, an engineer adds it by default and remove in case of a real necessity. (An engineer prefers composition over inheritance.)
fileprivate
access modifier is usually a code smell. An engineer shall re-consider an alternative design approach (e.g., nested types).
The default access modifier, namely internal
, is not specified explicitly (as well as any other default, generally).
class
type members are rarely useful because of discouraging to use inheritance, especially for the static members. An engineer must be aware of the use-cases of class
and static
modifiers (confusing them is a common mistake).
Generally, encapsulation is honored in any way. E.g. @IBOutlet
properties and @IBAction
methods are always private. Implementation details are hidden behind meaningful API.
Implementation
An engineer makes use of type inference and the current namespace. Compiler notifies them when a type should be stated explicitly. However, there might be occasions when specifying explicit types makes code more effective.
Similar rules are applied for nested types and enum cases. The shorthand notation starting with a dot is preferred whenever is possible.
An engineer makes use of lazy initialization of the properties that aren't immediately needed. They also prefer complex lazily initialized properties for subviews of UIKit's views and view controllers, especially over configuring them within view controller's lifecycle methods (a common mistake is to write big and awkward viewDidLoad()
implementations consisting of the full view controller's initial configuration).
Conditional code checks "the golden path" (the most probable option) first. Inverted checks (like !incorrect
) are discouraged because of poor readability. When both rules appear to be mutually exclusive, possible alternatives shall be considered.
Initialization
If the initial or constant value of a property doesn't depend on the initializer's parameters, a default value is preferred over setting it within the initialization code.
.init()
is not used for initialization:
Do:
let color = UIColor(displayP3Red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
Don't:
let color: UIColor = .init(displayP3Red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
Methods
Any scope contains only elements of the same level of abstraction (i.e., variables and related direct calculations, or method calls of the same layer of abstraction).
Do:
func viewDidLoad() {
super.viewDidLoad()
presenter.viewLoaded()
}
Don't:
func viewDidLoad() {
super.viewDidLoad()
let button = UIButton()
button.addTarget(self, action: #selector(buttonAction), event: .touchUpInside)
layoutButton()
view.backgroundColor = .black
}
Methods, as well as any code part, follow the Single Responsibility Principle. Beside maintainability, this doesn't let methods grow long and difficult to read. However, as for length, there might be exceptions. For example, complex drawing methods that can't be broken up into several small ones (or breaking up would make the code less readable).
Optionality
An engineer avoids force-unwraps. Basically, if an object is optional that's because it really might be nil
in certain conditions. If an engineer is 100% sure (which is usually impossible) that the optional value can't be nil
, it's better to state their point of view explicitly. For instance, by adding some kind of assertion inside guard let
-else
statement.
A possibly justified reason to have a force-unwrapped variable is late initialization. However, the latter may also be error-prone and avoided where possible.
Another acceptable reason to have a force-unwrap is an optional API which returns nil
only if there's a programming error (e.g. creating a URL from String
in Foundation : let url = URL(string: "https://www.apple.com")!
). However, an engineer doesn't rely on third-party APIs' implementation details since they might change any moment and without notice.
In testing code force-unwraps are welcomed because if they fail, the test fails too, and this is the desired behavior in this case.
State and Side Effects
Clean functions are preferred. Unidirectional flow is preferred over side effects. That makes the code less error-prone, improves readability and testability.
Don't:
private var button: UIButton!
// ...
func viewDidLoad() {
super.viewDidLoad()
button = UIButton()
configureButton()
}
// ...
private func configureButton() {
// ...
}
In the example above it's hard to track the actions' sequence.
Do:
private var button: UIButton! {
didSet { configure(button: button) }
}
// ...
func viewDidLoad() {
super.viewDidLoad()
button = UIButton()
}
// ...
private func configure(button: UIButton) {
// ...
}
In the latter example, button
is initialized once, within the viewDidLoad()
method. Once it's set, configure(button:)
is called – a pure function that doesn't rely on any kind of state and has no side effects. That is, it always results in the same way for the same arguments.
I'm aware that it's not the canonical, functional, definition of the term "pure function". Here I use it in the OOP context, for a basic understanding of the idea.
Testing
Basically, an engineer tests interfaces, not implementation details – by calling public APIs with some input data and asserting expected outputs.
Testing purposes don't intervene in the principles of encapsulation. If anything is wanted to be overridden or substantiated in testing code, protocols and their mock or stub implementations are always to the rescue.
Formatting
Multiple variables declarations on a single line using the single var
/let
keywords (like var x: Int, y: Int
) are restricted. Readability is not sacrificed for brevity.
Custom horizontal alignment isn't used because it welcomes further maintenance problems:
Don't:
let top: CGPoint
let middle: CGPoint
let bottom: CGPoint
Type aliases
typealias
declaration is used only for the sake of brevity when it doesn't prevent clarity.
Do:
typealias Task = (_ token: Sting) -> (_ result: Result<Data, Error>)
func enqueue(task: Task)
Don't:
typealias T = (Int, Int) -> (String)
func process(task: T) -> String
Methods
A method declaration is placed on a single line if it can fit most display screen widths without a carry-over. Otherwise, each parameter is placed on its own line and matches the beginning of the previous one. Return type carries on to the last parameter's line.
Do:
func fetchResults(from endpoint: URL,
transferringTo device: Device,
compressed: Bool,
completionHandler: (() -> Void)?) –> [Data]
If any of the parameters don't fit the maximum line width, the option with parentheses on separate lines is acceptable.
Do (or rather an acceptable formatting option):
func fetchResults(
from endpoint: URL = .remoteServerPath,
transferringTo device: Device = .current,
compressed: Bool = true,
completionHandler: ((_ success: Bool) -> ())? = nil
) –> [Data]
Don't:
func fetchResults(from endpoint: URL, transferringTo device: Device,
compressed: Bool, completionHandler: (() -> Void)?) –> [Data]
In the latter example parameters, which go second on the lines are hard to notice.
Custom operators
Operator implementing functions have a whitespace between the implemented operator's name and the left parens.
Do:
static func == (lhs: StreetAddress, rhs: StreetAddress) -> Bool {
Line length
Java Code Conventions can be used for deducing a maximum symbols-per-line value. Or common sense, as an option – from 80 to 120 symbols per line.
Annotations
Attributes starting with a @
symbol go onto a separate line before the declaration.
Do:
@testable
import ServiceFoundation
@objc
private func acceptanceButtonAction(_ sender: UIButton) {
Line breaks
Long expressions are broken into several parts on different lines so that the symbol that connects two parts of expression starts the new line. Each new line is indented with one additional indentation level. Having a special character starting a new line avoids creating the illusion that a new line is a new statement.
Do:
let a = (a + b)
+ (a + c)
Don't:
let a = (a + b) +
(a + c)
Return
Code within the scope (e.g. method's implementation) is separated into logical blocks by empty lines. If all blocks are one line, no separation is used. return
statement is usually separated, the only exception is if there's only two lines.
Do:
func makeOrderViewController(orderNumber: Int) -> UIViewController {
guard let order = Order(orderNumber: Int) else {
fatalError("Unexisting order.")
}
let model = OrderModel(orderNumber: orderNumber)
let vc = OrderViewController(model: model)
return vc
}
func makeOrderViewController(orderNumber: Int) -> UIViewController {
let model = OrderModel(orderNumber: orderNumber)
let vc = OrderViewController(model: model)
return vc
}
func makeOrderViewController(model: OrderModel) -> UIViewController {
let vc = OrderViewController(model: model)
return vc
}
Don't:
func makeOrderViewController(orderNumber: Int) -> UIViewController {
let model = OrderModel(orderNumber: orderNumber)
let vc = OrderViewController(model: model)
return vc // FIXME: The return statement is not separated.
}
func makeOrderViewController(model: OrderModel) -> UIViewController {
let vc = OrderViewController(model: model)
return vc // FIXME: Senseless blank line between the two lines.
}
If the implementation consists only of a return statement, the return
keyword is omitted:
Do:
func getViewController() -> UIViewController {
vc
}
Closures
Trailing closure syntax is preferable anywhere possible. Therefore, methods with closures as arguments aren't overloaded differing only by the name of trailing closures – that leads to ambiguity on the call site.
Empty parentheses are not used on the call site for the methods with the only closure as an argument.
Do:
requestMoreMoney { spend($0) }
Don't:
requestMoreMoney() { spend($0) }
requestMoreMoney(completionHandler: { spend($0) })
The functional style is preferable whenever possible: explicit argument names are omitted (placeholders like $0
are used), one line closure content scope is placed on the same line with braces (with respect to the line width limits, though).
Don't:
requestMoreMoney { money in
spend(money)
}
Chained functional-style calls, if don't fit the single line have line breaks before a dot. Even the first call of the chain is not left on the first line. Each new line of this call has exactly one extra level of indentation.
Do:
array
.filter { $0 > 0 }
.sort { $0 > $1 }
.map { Int($0) }
Don't:
array.filter { $0 > 0 }
.sort { $0 > $1 }.map { Int($0) }
The latter snippet gives a false impression of only two elements in the call chain.
Control Flow
Braces are open on the same line with declaration and closed on a separate line. No empty lines are left after opening brace and before the closing one. (The only exception is functional-style calls or, say, property observers which syntactically might be considered as functional statements. See the Methods and Closures sections.)
Do:
if name.isEmpty {
router.showSettings()
} else {
router.showProfile()
}
Don't:
if name.isEmpty
{
router.showSettings()
}
else
{
router.showProfile()
}
if name.isEmpty {
router.showSettings()
}
else {
router.showProfile()
}
if name.isEmpty {
router.showSettings()
} else {
router.showProfile()
}
Multiple conditions of a single if
-statement are placed on separate lines. Using commas instead of logic "and" (&&
) is preferred.
Do:
if password.isCorrect,
user.exist {
// ...
Don't:
if password.isCorrect, user.exist {
// ...
if password.isCorrect
&& user.exist {
// ...
If multiple paths exist, the most likely one goes first. Early returns are preferred over multi-level conditional paths.
Do:
if currency.supported {
router.proceedToWithdrawal()
return
}
if country.isAlly {
router.proceedToAgreement()
return
}
createAgreement()
Don't:
if currency.supported {
return withdrawnAmount
} else if country.isAlly {
return withdrawnAmount / 2
} else {
return 0
}
In any guard
-statement, with either one or multiple conditions, else
(and its left brace) goes on the same line after the last condition.
Do:
guard !array.isEmpty else {
// ...
Don't:
guard !array.isEmpty
else {
// ...
Multiple and complex logical conditions are distinguished with braces. Distinguished conditions are placed on their own lines, with corresponding indentation:
let goodToGo = ((firstNumber > 0) || (firstNumber < -10)) // Two levels of indentation, because it's a sub-condition of the first part of the main condition expression. && isFirstTry
. // One level of indentation, because it's the second part of the main condition expression.
Unnecessary parentheses within if
-statement conditions are avoided:
Do:
if happens {
// ...
Don't:
if (number != 0) {
// ...
If the control flow's main token is followed by left parentheses, exactly one standard whitespace is inserted between them.
The ternary operator ?
-:
is not used where the single if
-check is sufficient, because although it can save lines, it makes the intention unclear and spawns extra entities (empty tuples or functions).
Do:
if error == nil {
completion()
}
Don't:
error == nil
? completion()
: ()
Ternary operator expressions are placed on three lines, with line breaks before the first and the second symbols of the operator.
The for
-where
clause is preferred over the if
-statements nested inside loops.
Clearly named, easy-to-understand, local constants are preferred over inlined conditions:
Do:
let withinSolarSystem = ((2 * 2) == 4)
if withinSolarSystem {
// ...
Don't:
if (2 * 2) == 4 {
// ...
Switch Statements
All statements inside the cases of a switch
statement start on separate lines.
Do:
switch direction {
case .left:
turnLeft()
case .right:
turnRight():
case .straight:
break
}
Don't:
switch direction {
case .left: turnLeft()
case .right: turnRight():
case .straight: break
}
default
cases are not used (unless it's an imported, non NS_CLOSED_ENUM
, Objective-C enumeration) because they may lead to erroneous states if new cases are added.
Literals
Arrays
Array literals don't contain spaces after the left square bracket and before the right one. The included items are listed one below another, aligned. The first element is on the declaration's line. The closing bracket goes on the same line with the last item. If items are rather short and can be read easily (e.g., integer literals) it's acceptable to have them all on the one line.
Do:
var numbers = [1, 2, 3]
let airVehicles = [helicopter,
airLiner,
carrierRocket,
wings]
Don't:
var numbers = [
1,
2,
3
]
let airVehicles = [helicopter, airLiner, carrierRocket, wings]
The option with braces on separate lines is used when elements don't fit the line width:
Do:
let airVehicles = [
assumeThisNameDoesNotFitTheLineWidth,
airLiner
]
The trailing comma after the last element is not used.
Strings
Multi-line strings are preferred over concatenation.
Do:
let fullName = """
Nikita
Lazarev-Zubov
"""
Don't:
let fullName = "Nikita\n"
+ "Lazarev-Zubov"
An exception is NSLocalizedString
, because the genstrings tool cannot handle Swift's multi-line strings.
Numbers
Long numbers have underscores separating groups of digits: 1_000_000_000
Floating-point numbers all carry an explicit decimal fraction, even when zero. Breaking this rule leads to ambiguity when decimal number variables are assigned with new values: let interval: TimeInterval = 2.0
Don't:
let coordinate: CGFloat = 2.1
// ...
coordinate = 2 // Without looking at the declaration it's impossible to know that it's a floating point number.
Constants
Global constants are avoided.
The constants within a type declaration are grouped logically into private case-less enum
s as their static properties. Using case-less enum
s instead of structures or classes prevents unwanted initialization of the containing entity, without adding the explicit private empty initializer.
Colons
Colons always have no space on the left and one space on the right. Exceptions are ternary operators (?
-:
), dictionaries, and the selector syntax (e.g., addTarget(_:action:)
).
Do: let scoreTable: [String : Int] = [:]
Don'ts (three of them in a row – spot them all): let scoreTable : [String: Int] = [ : ]
The ampersand in composition statements (Codable & Encodable
) is surrounded by spaces too.
Operators
All operators (including =
, but excludinh already discussed :
) have exactly one standard whitespace before and after.
Do: let a = b + c
Don't: if (b+c) == 1 {
Miscellaneous
Semicolons are not used. The only case in Swift when they are necessary is multiple statements on a single line, which are strongly discouraged.
Only the Latin alphabet and numbers are used in the source code (no Cyrillic alphabet, hieroglyphs, emojis, etc.). Although the Swift programming language supports any UTF symbols, they are hard to type using some of the various localized keyboards and encourage error-prone copying-and-pasting.
Unused code (including imports and resources) is always removed, as wella as empty or inherited methods implementation.
Although empty blocks of code indicate design flaws, if they are unavoidable they are filled with a comment explaining why it's empty:
Do:
func buttonAction() {
// The button is inactive in this state and don't need an action.
}
Void
()
, Void
and (Void)
are syntactically interchangeable. However, the first one means an empty tuple that can be used, for example, as empty list of argumants of a closure. The second one is used as an empty return from a closure. The latter is just a pointless usage of a possibility to put any statement between parentheses and is not used.
Do: let completionHandler: () -> Void
Don't: let completionHandler: () -> ()
Access modifiers
Access modifiers of types, methods etc. go first.
Do: public static func goPublic()
Don't: static public func goPublic()
Links
Finding the Ultimate Swift Code-Style Guidelines
A brief introduction to this guidelines.
Swift.org API Design Guidelines
Non-neglectable official guideliness, focused mainly on APIs and naming.
A good example of an inspiring exhaustive code style for a programming language. Though, it has many atavisms and old-fashioned rules. It's really surprising to come across such an ugly formatting every here and there:
Don't:
void layoutScreen() {
layoutHeader()
layoutFooter()
}
Steve McConnell – Code Complete: A Practical Handbook of Software Construction
An impressive work, guiding software development in general, paying a lot of reader's attention on code style, Exhaustive explanations included.