Skip to content

airbnb/swift

Repository files navigation

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.

Swift Package Manager command plugin

This repo includes a Swift Package Manager command plugin that you can use to automatically reformat or lint your package according to the style guide. To use this command plugin with your package, all you need to do is add this repo as a dependency:

dependencies: [
  .package(url: "https://github.com/airbnb/swift", from: "1.0.0"),
]

and then run the format command plugin in your package directory:

$ swift package format
Usage guide
# Supported in Xcode 14+. Prompts for permission to write to the package directory.
$ swift package format

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

Table of Contents

  1. Xcode Formatting
  2. Naming
  3. Style
    1. Functions
    2. Closures
    3. Operators
  4. Patterns
  5. File Organization
  6. Objective-C Interoperability
  7. Contributors
  8. Amendments

Xcode Formatting

You can enable the following settings in Xcode by running this script, e.g. as part of a "Run Script" build phase.

  • (link) Each line should have a maximum column width of 100 characters. SwiftFormat: wrap

    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.

  • (link) Use 2 spaces to indent lines. SwiftFormat: indent

  • (link) Trim trailing whitespace in all lines. SwiftFormat: trailingSpace

⬆ back to top

Naming

  • (link) Use PascalCase for type and protocol names, and lowerCamelCase for everything else.

    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.

⬆ back to top

Style

  • (link) Don't include types where they can be easily inferred. SwiftFormat: redundantType

    // WRONG
    let sun: Star = Star(mass: 1.989e30)
    let earth: Planet = Planet.earth
    
    // RIGHT
    let sun = Star(mass: 1.989e30)
    let earth = Planet.earth
    
    // NOT RECOMMENDED. However, since the linter doesn't have full type information, this is not enforced automatically.
    let moon: Moon = earth.moon // returns `Moon`
    
    // RIGHT
    let moon = earth.moon
    let moon: PlanetaryBody? = earth.moon
    
    // WRONG: Most literals provide a default type that can be inferred.
    let enableGravity: Bool = true
    let numberOfPlanets: Int = 8
    let sunMass: Double = 1.989e30
    
    // RIGHT
    let enableGravity = true
    let numberOfPlanets = 8
    let sunMass = 1.989e30
    
    // WRONG: Types can be inferred from if/switch expressions as well if each branch has the same explicit type.
    let smallestPlanet: Planet =
      if treatPlutoAsPlanet {
        Planet.pluto
      } else {
        Planet.mercury
      }
    
    // RIGHT
    let smallestPlanet =
      if treatPlutoAsPlanet {
        Planet.pluto
      } else {
        Planet.mercury
      }
  • (link) Prefer letting the type of a variable or property be inferred from the right-hand-side value rather than writing the type explicitly on the left-hand side. SwiftFormat: propertyType

    Prefer using inferred types when the right-hand-side value is a static member with a leading dot (e.g. an init, a static property / function, or an enum case). This applies to both local variables and property declarations:

    // WRONG
    struct SolarSystemBuilder {
      let sun: Star = .init(mass: 1.989e30)
      let earth: Planet = .earth
    
      func setUp() {
        let galaxy: Galaxy = .andromeda
        let system: SolarSystem = .init(sun, earth)
        galaxy.add(system)
      }
    }
    
    // RIGHT
    struct SolarSystemBuilder {
      let sun = Star(mass: 1.989e30)
      let earth = Planet.earth
    
      func setUp() {
        let galaxy = Galaxy.andromeda
        let system = SolarSystem(sun, earth)
        galaxy.add(system)
      }
    }

    Explicit types are still permitted in other cases:

    // RIGHT: There is no right-hand-side value, so an explicit type is required.
    let sun: Star
    
    // RIGHT: The right-hand-side is not a static member of the left-hand type.
    let moon: PlantaryBody = earth.moon
    let sunMass: Float = 1.989e30
    let planets: [Planet] = []
    let venusMoon: Moon? = nil

    There are some rare cases where the inferred type syntax has a different meaning than the explicit type syntax. In these cases, the explicit type syntax is still permitted:

    extension String {
      static let earth = "Earth"
    }
    
    // WRONG: fails with "error: type 'String?' has no member 'earth'"
    let planetName = String?.earth
    
    // RIGHT
    let planetName: String? = .earth
    struct SaturnOutline: ShapeStyle { ... }
    
    extension ShapeStyle where Self == SaturnOutline {
      static var saturnOutline: SaturnOutline { 
        SaturnOutline() 
      }
    }
    
    // WRONG: fails with "error: static member 'saturnOutline' cannot be used on protocol metatype '(any ShapeStyle).Type'"
    let myShape2 = (any ShapeStyle).myShape
    
    // RIGHT: If the property's type is an existential / protocol type, moving the type
    // to the right-hand side will result in invalid code if the value is defined in an
    // extension like `extension ShapeStyle where Self == SaturnOutline`.
    // SwiftFormat autocorrect detects this case by checking for the existential `any` keyword.
    let myShape1: any ShapeStyle = .saturnOutline
  • (link) Don't use self unless it's necessary for disambiguation or required by the language. SwiftFormat: redundantSelf

    final class Listing {
    
      init(capacity: Int, allowsPets: Bool) {
        // WRONG
        self.capacity = capacity
        self.isFamilyFriendly = !allowsPets // `self.` not required here
    
        // RIGHT
        self.capacity = capacity
        isFamilyFriendly = !allowsPets
      }
    
      private let isFamilyFriendly: Bool
      private var capacity: Int
    
      private func increaseCapacity(by amount: Int) {
        // WRONG
        self.capacity += amount
    
        // RIGHT
        capacity += amount
    
        // WRONG
        self.save()
    
        // RIGHT
        save()
      }
    }
  • (link) Bind to self when upgrading from a weak reference. SwiftFormat: strongifiedSelf

    // WRONG
    class MyClass {
    
      func request(completion: () -> Void) {
        API.request() { [weak self] response in
          guard let strongSelf = self else { return }
          // Do work
          completion()
        }
      }
    }
    
    // RIGHT
    class MyClass {
    
      func request(completion: () -> Void) {
        API.request() { [weak self] response in
          guard let self else { return }
          // Do work
          completion()
        }
      }
    }
  • (link) Add a trailing comma on the last element of a multi-line array. SwiftFormat: trailingCommas

    // WRONG
    let rowContent = [
      listingUrgencyDatesRowContent(),
      listingUrgencyBookedRowContent(),
      listingUrgencyBookedShortRowContent()
    ]
    
    // RIGHT
    let rowContent = [
      listingUrgencyDatesRowContent(),
      listingUrgencyBookedRowContent(),
      listingUrgencyBookedShortRowContent(),
    ]
  • (link) There should be no spaces inside the brackets of collection literals. SwiftFormat: spaceInsideBrackets

    // WRONG
    let innerPlanets = [ mercury, venus, earth, mars ]
    let largestObjects = [ .star: sun, .planet: jupiter  ]
    
    // RIGHT
    let innerPlanets = [mercury, venus, earth, mars]
    let largestObjects = [.star: sun, .planet: jupiter]
  • (link) Name members of tuples for extra clarity. Rule of thumb: if you've got more than 3 fields, you should probably be using a struct.

    // WRONG
    func whatever() -> (Int, Int) {
      return (4, 4)
    }
    let thing = whatever()
    print(thing.0)
    
    // RIGHT
    func whatever() -> (x: Int, y: Int) {
      return (x: 4, y: 4)
    }
    
    // THIS IS ALSO OKAY
    func whatever2() -> (x: Int, y: Int) {
      let x = 4
      let y = 4
      return (x, y)
    }
    
    let coord = whatever()
    coord.x
    coord.y
  • (link) Place the colon immediately after an identifier, followed by a space. SwiftLint: colon

    // WRONG
    var something : Double = 0
    
    // RIGHT
    var something: Double = 0
    // WRONG
    class MyClass : SuperClass {
      // ...
    }
    
    // RIGHT
    class MyClass: SuperClass {
      // ...
    }
    // WRONG
    var dict = [KeyType:ValueType]()
    var dict = [KeyType : ValueType]()
    
    // RIGHT
    var dict = [KeyType: ValueType]()
  • (link) Place a space on either side of a return arrow for readability. SwiftLint: return_arrow_whitespace

    // WRONG
    func doSomething()->String {
      // ...
    }
    
    // RIGHT
    func doSomething() -> String {
      // ...
    }
    // WRONG
    func doSomething(completion: ()->Void) {
      // ...
    }
    
    // RIGHT
    func doSomething(completion: () -> Void) {
      // ...
    }
  • (link) Omit unnecessary parentheses. SwiftFormat: redundantParens

    // WRONG
    if (userCount > 0) { ... }
    switch (someValue) { ... }
    let evens = userCounts.filter { (number) in number.isMultiple(of: 2) }
    let squares = userCounts.map() { $0 * $0 }
    
    // RIGHT
    if userCount > 0 { ... }
    switch someValue { ... }
    let evens = userCounts.filter { number in number.isMultiple(of: 2) }
    let squares = userCounts.map { $0 * $0 }
  • (link) Omit enum associated values from case statements when all arguments are unlabeled. SwiftFormat: redundantPattern

    // WRONG
    if case .done(_) = result { ... }
    
    switch animal {
    case .dog(_, _, _):
      ...
    }
    
    // RIGHT
    if case .done = result { ... }
    
    switch animal {
    case .dog:
      ...
    }
  • (link) When destructuring an enum case or a tuple, place the let keyword inline, adjacent to each individual property assignment. SwiftFormat: hoistPatternLet

    // WRONG
    switch result {
    case let .success(value):
      // ...
    case let .error(errorCode, errorReason):
      // ...
    }
    
    // WRONG
    guard let case .success(value) else {
      return
    }
    
    // RIGHT
    switch result {
    case .success(let value):
      // ...
    case .error(let errorCode, let errorReason):
      // ...
    }
    
    // RIGHT
    guard case .success(let value) else {
      return
    }

    Why?

    1. Consistency: We should prefer to either always inline the let keyword or never inline the let keyword. In Airbnb's Swift codebase, we observed that inline let is used far more often in practice (especially when destructuring enum cases with a single associated value).

    2. Clarity: Inlining the let keyword makes it more clear which identifiers are part of the conditional check and which identifiers are binding new variables, since the let keyword is always adjacent to the variable identifier.

    // `let` is adjacent to the variable identifier, so it is immediately obvious
    // at a glance that these identifiers represent new variable bindings
    case .enumCaseWithSingleAssociatedValue(let string):
    case .enumCaseWithMultipleAssociatedValues(let string, let int):
    
    // The `let` keyword is quite far from the variable identifiers,
    // so it is less obvious that they represent new variable bindings
    case let .enumCaseWithSingleAssociatedValue(string):
    case let .enumCaseWithMultipleAssociatedValues(string, int):
  • (link) Place attributes for functions, types, and computed properties on the line above the declaration. SwiftFormat: wrapAttributes

    // WRONG
    @objc class Spaceship {
    
      @ViewBuilder var controlPanel: some View {
        // ...
      }
    
      @discardableResult func fly() -> Bool {
        // ...
      }
    
    }
    
    // RIGHT
    @objc
    class Spaceship {
    
      @ViewBuilder
      var controlPanel: some View {
        // ...
      }
    
      @discardableResult
      func fly() -> Bool {
        // ...
      }
    
    }
  • (link) Place simple attributes for stored properties on the same line as the rest of the declaration. Complex attributes with named arguments, or more than one unnamed argument, should be placed on the previous line. SwiftFormat: wrapAttributes

    // WRONG. These simple property wrappers should be written on the same line as the declaration. 
    struct SpaceshipDashboardView {
    
      @State
      private var warpDriveEnabled: Bool
    
      @ObservedObject
      private var lifeSupportService: LifeSupportService
    
      @Environment(\.controlPanelStyle) 
      private var controlPanelStyle
    
    }
    
    // RIGHT
    struct SpaceshipDashboardView {
    
      @State private var warpDriveEnabled: Bool
    
      @ObservedObject private var lifeSupportService: LifeSupportService
    
      @Environment(\.controlPanelStyle) private var controlPanelStyle
    
    }
    // WRONG. These complex attached macros should be written on the previous line.
    struct SolarSystemView {
    
      @Query(sort: \.distance) var allPlanets: [Planet]
    
      @Query(sort: \.age, order: .reverse) var moonsByAge: [Moon]
    
    }
    
    // RIGHT
    struct SolarSystemView {
    
      @Query(sort: \.distance)
      var allPlanets: [Planet]
    
      @Query(sort: \.age, order: .reverse)
      var oldestMoons: [Moon]
    
    }
    ```swift

    // WRONG. These long, complex attributes should be written on the previous line. struct RocketFactory {

    @available(*, unavailable, message: "No longer in production") var saturn5Builder: Saturn5Builder

    @available(*, deprecated, message: "To be retired by 2030") var atlas5Builder: Atlas5Builder

    @available(*, iOS 18.0, tvOS 18.0, macOS 15.0, watchOS 11.0) var newGlennBuilder: NewGlennBuilder

    }

    // RIGHT struct RocketFactory {

    @available(*, unavailable, message: "No longer in production") var saturn5Builder: Saturn5Builder

    @available(*, deprecated, message: "To be retired by 2030") var atlas5Builder: Atlas5Builder

    @available(*, iOS 18.0, tvOS 18.0, macOS 15.0, watchOS 11.0) var newGlennBuilder: NewGlennBuilder

    }

    
    #### Why?
    
    Unlike other types of declarations, which have braces and span multiple lines, stored property declarations are often only a single line of code. Stored properties are often written sequentially without any blank lines between them. This makes the code compact without hurting readability, and allows for related properties to be grouped together in blocks:
    
    ```swift
    struct SpaceshipDashboardView {
      @State private var warpDriveEnabled: Bool
      @State private var lifeSupportEnabled: Bool
      @State private var artificialGravityEnabled: Bool
      @State