Airbnb Swift Style Guide
Goals
Following this style guide should:
- Make it easier to read and begin understanding unfamiliar code.
- Make code easier to maintain.
- Reduce simple programmer errors.
- Reduce cognitive load while coding.
- Keep discussions on diffs focused on the code’s logic rather than its style.
Note that brevity is not a primary goal. Code should be made more concise only if other good code qualities (such as readability, simplicity, and clarity) remain equal or are improved.
Guiding Tenets
- This guide is in addition to the official Swift API Design Guidelines. These rules should not contradict that document.
- These rules should not fight Xcode’s ^ + I indentation behavior.
- We strive to make every rule lintable:
- If a rule changes the format of the code, it needs to be able to be reformatted automatically (either using SwiftFormat or SwiftLint autocorrect).
- For rules that don’t directly change the format of the code, we should have a lint rule that throws a warning.
- Exceptions to these rules should be rare and heavily justified.
When using the Xcode 13 toolchain, or a noninteractive shell, you must use:
$ swift package –allow-writing-to-package-directory format
To just lint without reformatting, you can use --lint
:
$ swift package format –lint
By default the command plugin runs on the entire package directory.
You can exclude directories using exclude
:
$ swift package format –exclude Tests
Alternatively you can explicitly list the set of paths and/or SPM targets:
\( swift package format --paths Sources Tests Package.swift \) swift package format –targets AirbnbSwiftFormatTool
The plugin infers your package’s minimum Swift version from the swift-tools-version
in your Package.swift
, but you can provide a custom value with --swift-version
:
$ swift package format –swift-version 5.3
The package plugin returns a non-zero exit code if there is a lint failure that requires attention.
- In `--lint` mode, any lint failure from any tool will result in a non-zero exit code.
- In standard autocorrect mode without `--lint`, only failures from SwiftLint lint-only rules will result in a non-zero exit code.
</details>
## Table of Contents
1. [Xcode Formatting](#xcode-formatting)
1. [Naming](#naming)
1. [Style](#style)
1. [Functions](#functions)
1. [Closures](#closures)
1. [Operators](#operators)
1. [Patterns](#patterns)
1. [File Organization](#file-organization)
1. [Objective-C Interoperability](#objective-c-interoperability)
1. [Contributors](#contributors)
1. [Amendments](#amendments)
## Xcode Formatting
_You can enable the following settings in Xcode by running [this script](resources/xcode_settings.bash), e.g. as part of a "Run Script" build phase._
* <a id='column-width'></a>(<a href='#column-width'>link</a>) **Each line should have a maximum column width of 100 characters.** [![SwiftFormat: wrap](https://img.shields.io/badge/SwiftFormat-wrap-7B0051.svg)](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#wrap)
<details>
#### Why?
Due to larger screen sizes, we have opted to choose a page guide greater than 80.
We currently only "strictly enforce" (lint / auto-format) a maximum column width of 130 characters to limit the cases where manual clean up is required for reformatted lines that fall slightly above the threshold.
</details>
* <a id='spaces-over-tabs'></a>(<a href='#spaces-over-tabs'>link</a>) **Use 2 spaces to indent lines.** [![SwiftFormat: indent](https://img.shields.io/badge/SwiftFormat-indent-7B0051.svg)](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#indent)
* <a id='trailing-whitespace'></a>(<a href='#trailing-whitespace'>link</a>) **Trim trailing whitespace in all lines.** [![SwiftFormat: trailingSpace](https://img.shields.io/badge/SwiftFormat-trailingSpace-7B0051.svg)](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md#trailingSpace)
**[⬆ back to top](#table-of-contents)**
## Naming
* <a id='use-camel-case'></a>(<a href='#use-camel-case'>link</a>) **Use PascalCase for type and protocol names, and lowerCamelCase for everything else.**
<details>
```swift
protocol SpaceThing {
// ...
}
class SpaceFleet: SpaceThing {
enum Formation {
// ...
}
class Spaceship {
// ...
}
var ships: [Spaceship] = []
static let worldName: String = "Earth"
func addShip(_ ship: Spaceship) {
// ...
}
}
let myFleet = SpaceFleet()
Exception: You may prefix a private property with an underscore if it is backing an identically-named property or method with a higher access level.
#### Why? There are specific scenarios where a backing property or method that is prefixed with an underscore could be easier to read than using a more descriptive name.
- Type erasure
public final class AnyRequester<ModelType>: Requester {
public init<T: Requester>(_ requester: T) where T.ModelType == ModelType {
_executeRequest = requester.executeRequest
}
@discardableResult
public func executeRequest(
_ request: URLRequest,
onSuccess: @escaping (ModelType, Bool) -> Void,
onFailure: @escaping (Error) -> Void)
-> URLSessionCancellable
{
return _executeRequest(request, onSuccess, onFailure)
}
private let _executeRequest: (
URLRequest,
@escaping (ModelType, Bool) -> Void,
@escaping (Error) -> Void)
-> URLSessionCancellable
}
- Backing a less specific type with a more specific type
final class ExperiencesViewController: UIViewController {
// We can't name this view since UIViewController has a view: UIView property.
private lazy var _view = CustomView()
loadView() {
self.view = _view
}
}
(link) Name booleans like
isSpaceship
,hasSpacesuit
, etc. This makes it clear that they are booleans and not other types.(link) Acronyms in names (e.g.
URL
) should be all-caps except when it’s the start of a name that would otherwise be lowerCamelCase, in which case it should be uniformly lower-cased.
// WRONG
class UrlValidator {
func isValidUrl(_ URL: URL) -> Bool {
// ...
}
func isProfileUrl(_ URL: URL, for userId: String) -> Bool {
// ...
}
}
let URLValidator = UrlValidator()
let isProfile = URLValidator.isProfileUrl(URLToTest, userId: IDOfUser)
// RIGHT
class URLValidator {
func isValidURL(_ url: URL) -> Bool {
// ...
}
func isProfileURL(_ url: URL, for userID: String) -> Bool {
// ...
}
}
let urlValidator = URLValidator()
let isProfile = urlValidator.isProfileURL(urlToTest, userID: idOfUser)
- (link) Names should be written with their most general part first and their most specific part last. The meaning of “most general” depends on context, but should roughly mean “that which most helps you narrow down your search for the item you’re looking for.” Most importantly, be consistent with how you order the parts of your name.
// WRONG
let rightTitleMargin: CGFloat
let leftTitleMargin: CGFloat
let bodyRightMargin: CGFloat
let bodyLeftMargin: CGFloat
// RIGHT
let titleMarginRight: CGFloat
let titleMarginLeft: CGFloat
let bodyMarginRight: CGFloat
let bodyMarginLeft: CGFloat
- (link) Include a hint about type in a name if it would otherwise be ambiguous.
// WRONG
let title: String
let cancel: UIButton
// RIGHT
let titleText: String
let cancelButton: UIButton
- (link) Event-handling functions should be named like past-tense sentences. The subject can be omitted if it’s not needed for clarity.
// WRONG
class ExperiencesViewController {
private func handleBookButtonTap() {
// ...
}
private func modelChanged() {
// ...
}
}
// RIGHT
class ExperiencesViewController {
private func didTapBookButton() {
// ...
}
private func modelDidChange() {
// ...
}
}
- (link) Avoid Objective-C-style acronym prefixes. This is no longer needed to avoid naming conflicts in Swift.
// WRONG
class AIRAccount {
// ...
}
// RIGHT
class Account {
// ...
}
- (link) Avoid
*Controller
in names of classes that aren’t view controllers.
#### Why? Controller is an overloaded suffix that doesn’t provide information about the responsibilities of the class.
Functions
- (link) Omit
Void
return types from function definitions.
// WRONG
func doSomething() -> Void {
...
}
// RIGHT
func doSomething() {
...
}
- (link) Separate long function declarations with line breaks before each argument label, and before the return signature or any effects (
async
,throws
). Put the open curly brace on the next line so the first executable line doesn’t look like it’s another parameter.
class Universe {
// WRONG
func generateStars(at location: Point, count: Int, color: StarColor, withAverageDistance averageDistance: Float) -> String {
// This is too long and will probably auto-wrap in a weird way
}
// WRONG
func generateStars(at location: Point,
count: Int,
color: StarColor,
withAverageDistance averageDistance: Float) -> String
{
// Xcode indents all the arguments
}
// WRONG
func generateStars(
at location: Point,
count: Int,
color: StarColor,
withAverageDistance averageDistance: Float) -> String {
populateUniverse() // this line blends in with the argument list
}
// WRONG
func generateStars(
at location: Point,
count: Int,
color: StarColor,
withAverageDistance averageDistance: Float) throws
-> String {
populateUniverse() // this line blends in with the argument list
}
// WRONG
func generateStars(
at location: Point,
count: Int,
color: StarColor,
withAverageDistance averageDistance: Float) async throws // these effects are easy to miss since they're visually associated with the last parameter
-> String
{
populateUniverse()
}
// RIGHT
func generateStars(
at location: Point,
count: Int,
color: StarColor,
withAverageDistance averageDistance: Float)
-> String
{
populateUniverse()
}
// RIGHT
func generateStars(
at location: Point,
count: Int,
color: StarColor,
withAverageDistance averageDistance: Float)
async throws -> String
{
populateUniverse()
}
}
- (link) Long function invocations should also break on each argument. Put the closing parenthesis on the last line of the invocation.
// WRONG
universe.generateStars(at: location, count: 5, color: starColor, withAverageDistance: 4)
// WRONG
universe.generateStars(at: location,
count: 5,
color: starColor,
withAverageDistance: 4)
// WRONG
universe.generateStars(
at: location,
count: 5,
color: starColor,
withAverageDistance: 4
)
// WRONG
universe.generate(5,
.stars,
at: location)
// RIGHT
universe.generateStars(
at: location,
count: 5,
color: starColor,
withAverageDistance: 4)
// RIGHT
universe.generate(
5,
.stars,
at: location)
(link) Name unused function parameters as underscores (
_
).#### Why? Naming unused function parameters as underscores makes it more clear when the parameter is unused within the function body. This can make it easier to catch subtle logical errors, and can highlight opportunities to simplify method signatures. ```swift // WRONG // In this method, the `newCondition` parameter is unused. // This is actually a logical error, and is easy to miss, but compiles without warning. func updateWeather(_ newCondition: WeatherCondition) -> Weather { var updatedWeather = self updatedWeather.condition = condition // this mistake inadvertently makes this method unable to change the weather condition return updatedWeather } // In this method, the `color` parameter is unused. // Is this a logical error (e.g. should it be passed through to the `universe.generateStars` method call), // or is this an unused argument that should be removed from the method signature? func generateUniverseWithStars( at location: Point, count: Int, color: StarColor, withAverageDistance averageDistance: Float) { let universe = generateUniverse() universe.generateStars( at: location, count: count, withAverageDistance: averageDistance) } ``` ```swift // RIGHT // Automatically reformatting the unused parameter to be an underscore // makes it more clear that the parameter is unused, which makes it // easier to spot the logical error. func updateWeather(_: WeatherCondition) -> Weather { var updatedWeather = self updatedWeather.condition = condition return updatedWeather } // The underscore makes it more clear that the `color` parameter is unused. // This method argument can either be removed if truly unnecessary, // or passed through to `universe.generateStars` to correct the logical error. func generateUniverseWithStars( at location: Point, count: Int, color _: StarColor, withAverageDistance averageDistance: Float) { let universe = generateUniverse() universe.generateStars( at: location, count: count, withAverageDistance: averageDistance) } ```(link) Remove blank lines between chained functions.
#### Why?
Improves readability and maintainability, making it easier to see the sequence of functions that are applied to the object.
// WRONG
var innerPlanetNames: [String] {
planets
.filter { $0.isInnerPlanet }
.map { $0.name }
}
// WRONG
var innerPlanetNames: [String] {
planets
.filter { $0.isInnerPlanet }
// Gets the name of the inner planet
.map { $0.name }
}
// RIGHT
var innerPlanetNames: [String] {
planets
.filter { $0.isInnerPlanet }
.map { $0.name }
}
// RIGHT
var innerPlanetNames: [String] {
planets
.filter { $0.isInnerPlanet }
// Gets the name of the inner planet
.map { $0.name }
}
Closures
- (link) Favor
Void
return types over()
in closure declarations. If you must specify aVoid
return type in a function declaration, useVoid
rather than()
to improve readability.
// WRONG
func method(completion: () -> ()) {
...
}
// RIGHT
func method(completion: () -> Void) {
...
}
(link) Name unused closure parameters as underscores (
_
).#### Why? Naming unused closure parameters as underscores reduces the cognitive overhead required to read closures by making it obvious which parameters are used and which are unused. ```swift // WRONG someAsyncThing() { argument1, argument2, argument3 in print(argument3) } // RIGHT someAsyncThing() { _, _, argument3 in print(argument3) } ```(link) Closures should have a single space or newline inside each brace. Trailing closures should additionally have a single space or newline outside each brace.
// WRONG
let evenSquares = numbers.filter{$0.isMultiple(of: 2)}.map{ $0 * $0 }
// RIGHT
let evenSquares = numbers.filter { $0.isMultiple(of: 2) }.map { $0 * $0 }
// WRONG
let evenSquares = numbers.filter( { $0.isMultiple(of: 2) } ).map( { $0 * $0 } )
// RIGHT
let evenSquares = numbers.filter({ $0.isMultiple(of: 2) }).map({ $0 * $0 })
// WRONG
let evenSquares = numbers
.filter{
$0.isMultiple(of: 2)
}
.map{
$0 * $0
}
// RIGHT
let evenSquares = numbers
.filter {
$0.isMultiple(of: 2)
}
.map {
$0 * $0
}
- (link) Omit
Void
return types from closure expressions.
// WRONG
someAsyncThing() { argument -> Void in
...
}
// RIGHT
someAsyncThing() { argument in
...
}
- (link) Prefer trailing closure syntax for closure arguments with no parameter name.
// WRONG
planets.map({ $0.name })
// RIGHT
planets.map { $0.name }
// ALSO RIGHT, since this closure has a parameter name
planets.first(where: { $0.isGasGiant })
// ALSO FINE. Trailing closure syntax is still permitted for closures
// with parameter names. However, consider using non-trailing syntax
// in cases where the parameter name is semantically meaningful.
planets.first { $0.isGasGiant }
- (link) Avoid using
unowned
captures. Instead prefer safer alternatives likeweak
captures, or capturing variables directly.
unowned
captures are unsafe because they will cause the application to crash if the referenced object has been deallocated.
// WRONG: Crashes if `self` has been deallocated when closures are called.
final class SpaceshipNavigationService {
let spaceship: Spaceship
let planet: Planet
func colonizePlanet() {
spaceship.travel(to: planet, onArrival: { [unowned self] in
planet.colonize()
})
}
func exploreSystem() {
spaceship.travel(to: planet, nextDestination: { [unowned self] in
planet.moons?.first
})
}
}
weak
captures are safer because they require the author to explicitly handle the case where the referenced object no longer exists.
// RIGHT: Uses a `weak self` capture and explicitly handles the case where `self` has been deallocated
final class SpaceshipNavigationService {
let spaceship: Spaceship
let planet: Planet
func colonizePlanet() {
spaceship.travel(to: planet, onArrival: { [weak self] in
guard let self else { return }
planet.colonize()
})
}
func exploreSystem() {
spaceship.travel(to: planet, nextDestination: { [weak self] in
guard let self else { return nil }
return planet.moons?.first
})
}
}
Alternatively, consider directly capturing the variables that are used in the closure. This lets you avoid having to handle the case where self
is nil, since you don’t even need to reference self
:
// RIGHT: Explicitly captures `planet` instead of capturing `self`
final class SpaceshipNavigationService {
let spaceship: Spaceship
let planet: Planet
func colonizePlanet() {
spaceship.travel(to: planet, onArrival: { [planet] in
planet.colonize()
})
}
func exploreSystem() {
spaceship.travel(to: planet, nextDestination: { [planet] in
planet.moons?.first
})
}
}
File Organization
- (link) Alphabetize and deduplicate module imports within a file. Place all imports at the top of the file below the header comments. Do not add additional line breaks between import statements. Add a single empty line before the first import and after the last import.
#### Why?
- A standard organization method helps engineers more quickly determine which modules a file depends on.
- Duplicated import statements have no effect and should be removed for clarity.
// WRONG
// Copyright © 2018 Airbnb. All rights reserved.
//
import DLSPrimitives
import Constellation
import Constellation
import Epoxy
import Foundation
// RIGHT
// Copyright © 2018 Airbnb. All rights reserved.
//
import Constellation
import DLSPrimitives
import Epoxy
import Foundation
Exception: @testable import
should be grouped after the regular import and separated by an empty line.
// WRONG
// Copyright © 2018 Airbnb. All rights reserved.
//
import DLSPrimitives
@testable import Epoxy
import Foundation
import Nimble
import Quick
// RIGHT
// Copyright © 2018 Airbnb. All rights reserved.
//
import DLSPrimitives
import Foundation
import Nimble
import Quick
@testable import Epoxy
- (link) Limit consecutive whitespace to one blank line or space (excluding indentation). Favor the following formatting guidelines over whitespace of varying heights or widths.
// WRONG
struct Planet {
let mass: Double
let hasAtmosphere: Bool
func distance(to: Planet) { }
}
// RIGHT
struct Planet {
let mass: Double
let hasAtmosphere: Bool
func distance(to: Planet) { }
}
(link) Files should end in a newline.
(link) Declarations that include scopes spanning multiple lines should be separated from adjacent declarations in the same scope by a newline. Insert a single blank line between multi-line scoped declarations (e.g. types, extensions, functions, computed properties, etc.) and other declarations at the same indentation level.
#### Why? Dividing scoped declarations from other declarations at the same scope visually separates them, making adjacent declarations easier to differentiate from the scoped declaration.
// WRONG
struct SolarSystem {
var numberOfPlanets: Int {
…
}
func distance(to: SolarSystem) -> AstronomicalUnit {
…
}
}
struct Galaxy {
func distance(to: Galaxy) -> AstronomicalUnit {
…
}
func contains(_ solarSystem: SolarSystem) -> Bool {
…
}
}
// RIGHT
struct SolarSystem {
var numberOfPlanets: Int {
…
}
func distance(to: SolarSystem) -> AstronomicalUnit {
…
}
}
struct Galaxy {
func distance(to: Galaxy) -> AstronomicalUnit {
…
}
func contains(_ solarSystem: SolarSystem) -> Bool {
…
}
}
- (link) Remove blank lines at the top and bottom of scopes, excluding type bodies which can optionally include blank lines.
// WRONG
class Planet {
func terraform() {
generateAtmosphere()
generateOceans()
}
}
// RIGHT
class Planet {
func terraform() {
generateAtmosphere()
generateOceans()
}
}
// Also fine!
class Planet {
func terraform() {
generateAtmosphere()
generateOceans()
}
}
- (link) Each type and extension which implements a conformance should be preceded by a
MARK
comment.- Types should be preceded by a
// MARK: - TypeName
comment. - Extensions that add a conformance should be preceded by a
// MARK: - TypeName + ProtocolName
comment. - Extensions that immediately follow the type being extended should omit that type’s name and instead use
// MARK: ProtocolName
. - If there is only one type or extension in a file, the
MARK
comment can be omitted. - If the extension in question is empty (e.g. has no declarations in its body), the
MARK
comment can be omitted. - For extensions that do not add new conformances, consider adding a
MARK
with a descriptive comment.
- Types should be preceded by a
// MARK: - GalaxyView
final class GalaxyView: UIView { … }
// MARK: ContentConfigurableView
extension GalaxyView: ContentConfigurableView { … }
// MARK: - Galaxy + SpaceThing, NamedObject
extension Galaxy: SpaceThing, NamedObject { … }
(link) Use
// MARK:
to separate the contents of type definitions and extensions into the sections listed below, in order. All type definitions and extensions should be divided up in this consistent way, allowing a reader of your code to easily jump to what they are interested in.// MARK: Lifecycle
forinit
anddeinit
methods.// MARK: Open
foropen
properties and methods.// MARK: Public
forpublic
properties and methods.// MARK: Package
forpackage
properties and methods.// MARK: Internal
forinternal
properties and methods.// MARK: Fileprivate
forfileprivate
properties and methods.// MARK: Private
forprivate
properties and methods.- If the type in question is an enum, its cases should go above the first
// MARK:
. - Do not subdivide each of these sections into subsections, as it makes the method dropdown more cluttered and therefore less useful. Instead, group methods by functionality and use smart naming to make clear which methods are related. If there are enough methods that sub-sections seem necessary, consider refactoring your code into multiple types.
- If all of the type or extension’s definitions belong to the same category (e.g. the type or extension only consists of
internal
properties), it is OK to omit the// MARK:
s. - If the type in question is a simple value type (e.g. fewer than 20 lines), it is OK to omit the
// MARK:
s, as it would hurt legibility.
(link) Within each top-level section, place content in the following order. This allows a new reader of your code to more easily find what they are looking for.
- Nested types and type aliases
- Static properties
- Class properties
- Instance properties
- Static methods
- Class methods
- Instance methods
(link) Add empty lines between property declarations of different kinds. (e.g. between static properties and instance properties.)
// WRONG
static let gravityEarth: CGFloat = 9.8
static let gravityMoon: CGFloat = 1.6
var gravity: CGFloat
// RIGHT
static let gravityEarth: CGFloat = 9.8
static let gravityMoon: CGFloat = 1.6
var gravity: CGFloat
- (link) Computed properties and properties with property observers should appear at the end of the set of declarations of the same kind. (e.g. instance properties.)
// WRONG
var atmosphere: Atmosphere {
didSet {
print("oh my god, the atmosphere changed")
}
}
var gravity: CGFloat
// RIGHT
var gravity: CGFloat
var atmosphere: Atmosphere {
didSet {
print("oh my god, the atmosphere changed")
}
}
Objective-C Interoperability
- (link) Prefer pure Swift classes over subclasses of NSObject. If your code needs to be used by some Objective-C code, wrap it to expose the desired functionality. Use
@objc
on individual methods and variables as necessary rather than exposing all API on a class to Objective-C via@objcMembers
.
class PriceBreakdownViewController {
private let acceptButton = UIButton()
private func setUpAcceptButton() {
acceptButton.addTarget(
self,
action: #selector(didTapAcceptButton),
forControlEvents: .touchUpInside)
}
@objc
private func didTapAcceptButton() {
// ...
}
}
Contributors
Amendments
We encourage you to fork this guide and change the rules to fit your team’s style guide. Below, you may list some amendments to the style guide. This allows you to periodically update your style guide without having to deal with merge conflicts.