viktart / Ultimate-Swift-Code-Style-Guidelines

Ultimate Swift Code Style Guidelines

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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

1. Guidelines

1.1. Project

1.1.1. Files and Directories

1.1.2. CVS

1.2. File Structure

1.2.1. Imports

1.2.2. Members' Order

1.2.3. Protocol Conformances

1.3. Explicit Documentation

1.3.1. Comments

1.4. Naming

1.4.1. Types

1.4.2. Methods

1.5. API

1.5.1. Encapsulation

1.6. Implementation

1.6.1. Methods

1.6.2. Optionality

1.6.3. State and Side-Effects

1.6.4. Testing

1.7. Formatting

1.7.1. Methods

1.7.2. Closures

1.7.3. Control Flow

1.7.4. Literals

1.7.5. Constants

1.7.6. Colons

1.8. Miscellaneous

2. Links

Guidelines

Project

Projects don't tolerate any warnings.

Type and function names, file names, documentation, comments, everything, shall be written in American English.

Return to Table of Contents

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).

Return to Table of Contents

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.

Return to Table of Contents

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.

Return to Table of Contents

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.

Return to Table of Contents

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.

Return to Table of Contents

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.

}

Return to Table of Contents

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.

Return to Table of Contents

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.

Return to Table of Contents

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

Return to Table of Contents

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).

Return to Table of Contents

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.

Return to Table of Contents

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 structs and classes – 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).

Return to Table of Contents

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.

Return to Table of Contents

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)

Return to Table of Contents

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).

Return to Table of Contents

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.

Return to Table of Contents

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.

Return to Table of Contents

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.

Return to Table of Contents

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

Return to Table of Contents

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
}

Return to Table of Contents

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.

Return to Table of Contents

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.

Return to Table of Contents

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.

Return to Table of Contents

Constants

Global constants are avoided.

The constants within a type declaration are grouped logically into private case-less enums as their static properties. Using case-less enums instead of structures or classes prevents unwanted initialization of the containing entity, without adding the explicit private empty initializer.

Return to Table of Contents

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 {

Return to Table of Contents

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()

Return to Table of Contents

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.

Java Code Conventions

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.

About

Ultimate Swift Code Style Guidelines