• Stars
    star
    2,239
  • Rank 19,709 (Top 0.4 %)
  • Language Markdown
  • License
    MIT License
  • Created almost 8 years ago
  • Updated about 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Airbnb's Swift Style Guide

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

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 host: Host = Host()
    
    // RIGHT
    let host = Host()
    enum Direction {
      case left
      case right
    }
    
    func someDirection() -> Direction {
      // WRONG
      return Direction.left
    
      // RIGHT
      return .left
    }
  • (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 function/type attributes on the line above the declaration. SwiftFormat: wrapAttributes

    // WRONG
    @objc class Spaceship {
    
      @discardableResult func fly() -> Bool {
      }
    }
    
    // RIGHT
    @objc
    class Spaceship {
    
      @discardableResult
      func fly() -> Bool {
      }
    }
  • (link) Multi-line arrays should have each bracket on a separate line. Put the opening and closing brackets on separate lines from any of the elements of the array. Also add a trailing comma on the last element. SwiftFormat: wrapArguments

    // WRONG
    let rowContent = [listingUrgencyDatesRowContent(),
                      listingUrgencyBookedRowContent(),
                      listingUrgencyBookedShortRowContent()]
    
    // WRONG
    let rowContent = [
      listingUrgencyDatesRowContent(),
      listingUrgencyBookedRowContent(),
      listingUrgencyBookedShortRowContent()
    ]
    
    // RIGHT
    let rowContent = [
      listingUrgencyDatesRowContent(),
      listingUrgencyBookedRowContent(),
      listingUrgencyBookedShortRowContent(),
    ]
  • (link) Long type aliases of protocol compositions should wrap before the = and before each individual &. SwiftFormat: wrapArguments

    // WRONG (too long)
    public typealias Dependencies = CivilizationServiceProviding & LawsOfPhysicsProviding & PlanetBuilderProviding & UniverseBuilderProviding & UniverseSimulatorServiceProviding
    
    // WRONG (naive wrapping)
    public typealias Dependencies = CivilizationServiceProviding & LawsOfPhysicsProviding & PlanetBuilderProviding &
      UniverseBuilderProviding & UniverseSimulatorServiceProviding
    
    // WRONG (unbalanced)
    public typealias Dependencies = CivilizationServiceProviding
      & LawsOfPhysicsProviding
      & PlanetBuilderProviding
      & UniverseBuilderProviding
      & UniverseSimulatorServiceProviding
    
    // RIGHT
    public typealias Dependencies
      = CivilizationServiceProviding
      & LawsOfPhysicsProviding
      & PlanetBuilderProviding
      & UniverseBuilderProviding
      & UniverseSimulatorServiceProviding
  • (link) Sort protocol composition type aliases alphabetically. SwiftFormat: sortTypealiases

    Why?

    Protocol composition type aliases are an unordered list with no natural ordering. Sorting alphabetically keeps these lists more organized, which is especially valuable for long protocol compositions.

    // WRONG (not sorted)
    public typealias Dependencies
      = UniverseBuilderProviding
      & LawsOfPhysicsProviding
      & UniverseSimulatorServiceProviding
      & PlanetBuilderProviding
      & CivilizationServiceProviding
    
    // RIGHT
    public typealias Dependencies
      = CivilizationServiceProviding
      & LawsOfPhysicsProviding
      & PlanetBuilderProviding
      & UniverseBuilderProviding
      & UniverseSimulatorServiceProviding
  • (link) Omit the right-hand side of the expression when unwrapping an optional property to a non-optional property with the same name. SwiftFormat: redundantOptionalBinding

    Why?

    Following the rationale in SE-0345, this shorthand syntax removes unnecessary boilerplate while retaining clarity.

    // WRONG
    if
      let galaxy = galaxy,
      galaxy.name == "Milky Way"
    {  }
    
    guard
      let galaxy = galaxy,
      galaxy.name == "Milky Way"
    else {  }
    
    // RIGHT
    if
      let galaxy,
      galaxy.name == "Milky Way"
    {  }
    
    guard
      let galaxy,
      galaxy.name == "Milky Way"
    else {  }
  • (link) **Else statements should start on the same line as the previous condition's closing brace, unless the conditions are separated by a blank line or comments. SwiftFormat: elseOnSameLine

    // WRONG
    if let galaxy {
      
    }
    else if let bigBangService {
      
    }
    else {
      
    }
    
    // RIGHT
    if let galaxy {
      
    } else if let bigBangService {
      
    } else {
      
    }
    
    // RIGHT, because there are comments between the conditions
    if let galaxy {
      
    }
    // If the galaxy hasn't been created yet, create it using the big bang service
    else if let bigBangService {
      
    }
    // If the big bang service doesn't exist, fail gracefully
    else {
      
    }
    
    // RIGHT, because there are blank lines between the conditions
    if let galaxy {
      
    }
    
    else if let bigBangService {
      // If the galaxy hasn't been created yet, create it using the big bang service
      
    }
    
    else {
      // If the big bang service doesn't exist, fail gracefully
      
    }
  • (link) Multi-line conditional statements should break after the leading keyword. Indent each individual statement by 2 spaces. SwiftFormat: wrapArguments

    Why?

    Breaking after the leading keyword resets indentation to the standard 2-space grid, which helps avoid fighting Xcode's ^ + I indentation behavior.

    // WRONG
    if let galaxy,
      galaxy.name == "Milky Way" // Indenting by two spaces fights Xcode's ^+I indentation behavior
    {  }
    
    // WRONG
    guard let galaxy,
          galaxy.name == "Milky Way" // Variable width indentation (6 spaces)
    else {  }
    
    // WRONG
    guard let earth = universe.find(
      .planet,
      named: "Earth"),
      earth.isHabitable // Blends in with previous condition's method arguments
    else {  }
    
    // RIGHT
    if
      let galaxy,
      galaxy.name == "Milky Way"
    {  }
    
    // RIGHT
    guard
      let galaxy,
      galaxy.name == "Milky Way"
    else {  }
    
    // RIGHT
    guard
      let earth = universe.find(
        .planet,
        named: "Earth"),
      earth.isHabitable
    else {  }
    
    // RIGHT
    if let galaxy {
      
    }
    
    // RIGHT
    guard let galaxy else {
      
    }
  • (link) Add a line break before the else keyword in a multi-line guard statement. For single-line guard statements, keep the else keyword on the same line as the guard keyword. The open brace should immediately follow the else keyword. SwiftFormat: elseOnSameLine

    // WRONG (else should be on its own line for multi-line guard statements)
    guard
      let galaxy,
      galaxy.name == "Milky Way" else
    {  }
    
    // WRONG (else should be on the same line for single-line guard statements)
    guard let galaxy
    else {  }
    
    // RIGHT
    guard
      let galaxy,
      galaxy.name == "Milky Way"
    else {  }
    
    // RIGHT
    guard let galaxy else {
      
    }
  • (link) Indent the body and closing triple-quote of multiline string literals, unless the string literal begins on its own line in which case the string literal contents and closing triple-quote should have the same indentation as the opening triple-quote. SwiftFormat: indent

    // WRONG
    var spaceQuote = """
    “Space,” it says, “is big. Really big. You just won’t believe how vastly, hugely, mindbogglingly big it is.
    I mean, you may think it’s a long way down the road to the chemist’s, but that’s just peanuts to space.”
    """
    
    // RIGHT
    var spaceQuote = """
      “Space,” it says, “is big. Really big. You just won’t believe how vastly, hugely, mindbogglingly big it is.
      I mean, you may think it’s a long way down the road to the chemist’s, but that’s just peanuts to space.”
      """
    
    // WRONG
    var universeQuote: String {
      """
        In the beginning the Universe was created.
        This has made a lot of people very angry and been widely regarded as a bad move.
        """
    }
    
    // RIGHT
    var universeQuote: String {
      """
      In the beginning the Universe was created.
      This has made a lot of people very angry and been widely regarded as a bad move.
      """
    }
  • (link) Use constructors instead of Make() functions for NSRange and others. SwiftLint: legacy_constructor

    // WRONG
    let range = NSMakeRange(10, 5)
    
    // RIGHT
    let range = NSRange(location: 10, length: 5)
  • (link) For standard library types with a canonical shorthand form (Optional, Array, Dictionary), prefer using the shorthand form over the full generic form. SwiftFormat: typeSugar

    // WRONG
    let optional: Optional<String> = nil
    let array: Array<String> = []
    let dictionary: Dictionary<String, Any> = [:]
    
    // RIGHT
    let optional: String? = nil
    let array: [String] = []
    let dictionary: [String: Any] = [:]
  • (link) Omit explicit .init when not required. SwiftFormat: redundantInit

    // WRONG
    let universe = Universe.init()
    
    // RIGHT
    let universe = Universe()
  • (link) The opening brace following a single-line expression should be on the same line as the rest of the statement. SwiftFormat: braces

    // WRONG
    if !planet.isHabitable
    {
      planet.terraform()
    }
    
    class Planet
    {
      func terraform()
      {
        generateAtmosphere()
        generateOceans()
      }
    }
    
    // RIGHT
    if !planet.isHabitable {
      planet.terraform()
    }
    
    class Planet {
      func terraform() {
        generateAtmosphere()
        generateOceans()
      }
    }
  • (link) The opening brace following a multi-line expression should wrap to a new line. SwiftFormat: wrapMultilineStatementBraces

    // WRONG
    if
      let star = planet.nearestStar(),
      planet.isInHabitableZone(of: star) {
      planet.terraform()
    }
    
    class Planet {
      func terraform(
        atmosphereOptions: AtmosphereOptions = .default,
        oceanOptions: OceanOptions = .default) {
        generateAtmosphere(atmosphereOptions)
        generateOceans(oceanOptions)
      }
    }
    
    // RIGHT
    if
      let star = planet.nearestStar(),
      planet.isInHabitableZone(of: star)
    {
      planet.terraform()
    }
    
    class Planet {
      func terraform(
        atmosphereOptions: AtmosphereOptions = .default,
        oceanOptions: OceanOptions = .default) 
      {
        generateAtmosphere(atmosphereOptions)
        generateOceans(oceanOptions)
      }
    }
  • (link) Braces should be surrounded by a single whitespace character (either a space, or a newline) on each side. SwiftFormat: spaceInsideBraces SwiftFormat: spaceAroundBraces

    // WRONG
    struct Planet{
      
    }
    
    // WRONG
    if condition{
      
    }else{
      
    }
    
    // RIGHT
    struct Planet {
      
    }
    
    // RIGHT
    if condition {
      
    } else {
      
    }
  • (link) For function calls and declarations, there should be no spaces before or inside the parentheses of the argument list. SwiftFormat: spaceInsideParens SwiftFormat: spaceAroundParens

    // WRONG
    func install ( _ engine: Engine ) { }
    
    install ( AntimatterDrive( ) )
    
    // RIGHT
    func install(_ engine: Engine) { }
    
    install(AntimatterDrive())
  • (link) Comment blocks should use single-line comments (// for code comments and /// for documentation comments), rather than multi-line comments (/* ... */ and /** ... */). SwiftFormat: blockComments

    // WRONG
    
    /**
    * A planet that exists somewhere in the universe.
    *
    * Planets have many properties. For example, the best planets
    * have atmospheres and bodies of water to support life.
    */
    class Planet {
      /**
        Terraforms the planet, by adding an atmosphere and ocean that is hospitable for life.
      */
      func terraform() {
        /* 
        Generate the atmosphere first, before generating the ocean.
        Otherwise, the water will just boil off immediately.
        */
        generateAtmosphere()
    
        /* Now that we have an atmosphere, it's safe to generate the ocean */
        generateOceans()
      }
    }
    
    // RIGHT
    
    /// A planet that exists somewhere in the universe.
    ///
    /// Planets have many properties. For example, the best planets
    /// have atmospheres and bodies of water to support life.
    class Planet {
      /// Terraforms the planet, by adding an atmosphere and ocean that is hospitable for life.
      func terraform() {
        // Generate the atmosphere first, before generating the ocean.
        // Otherwise, the water will just boil off immediately.
        generateAtmosphere()
    
        // Now that we have an atmosphere, it's safe to generate the ocean
        generateOceans()
      }
    }
  • (link) Include spaces or newlines before and after comment delimiters (//, ///, /*, and */) SwiftFormat: spaceAroundComments SwiftFormat: spaceInsideComments

    // WRONG
    
    ///A spacecraft with incredible performance characteristics
    struct Spaceship {
    
      func travelFasterThanLight() {/*unimplemented*/}
    
      func travelBackInTime() { }//TODO: research whether or not this is possible
    
    }
    
    // RIGHT
    
    /// A spacecraft with incredible performance characteristics
    struct Spaceship {
    
      func travelFasterThanLight() { /* unimplemented */ }
    
      func travelBackInTime() { } // TODO: research whether or not this is possible
    
    }
  • (link) Include a single space in an empty set of braces ({ }). SwiftFormat: emptyBraces

    // WRONG
    extension Spaceship: Trackable {}
    
    extension SpaceshipView {
      var accessibilityIdentifier: String {
        get { spaceship.name }
        set {}
      }
    }
    
    // RIGHT
    extension Spaceship: Trackable { }
    
    extension SpaceshipView {
      var accessibilityIdentifier: String {
        get { spaceship.name }
        set { }
      }
    }

Functions

  • (link) Omit Void return types from function definitions. SwiftFormat: redundantVoidReturnType

    // 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. SwiftFormat: wrapArguments SwiftFormat: braces

    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. SwiftFormat: wrapArguments

    // 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 (_). SwiftFormat: unusedArguments

    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.

    // 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)
    }
    // 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)
    }

Closures

  • (link) Favor Void return types over () in closure declarations. If you must specify a Void return type in a function declaration, use Void rather than () to improve readability. SwiftLint: void_return

    // WRONG
    func method(completion: () -> ()) {
      ...
    }
    
    // RIGHT
    func method(completion: () -> Void) {
      ...
    }
  • (link) Name unused closure parameters as underscores (_). SwiftFormat: unusedArguments

    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.

    // 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. SwiftFormat: spaceInsideBraces SwiftFormat: spaceAroundBraces

    // 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. SwiftFormat: redundantVoidReturnType

    // WRONG
    someAsyncThing() { argument -> Void in
      ...
    }
    
    // RIGHT
    someAsyncThing() { argument in
      ...
    }
  • (link) Prefer trailing closure syntax for closure arguments with no parameter name. SwiftFormat: trailingClosures

    // 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 }

Operators

  • (link) Infix operators should have a single space on either side. Prefer parenthesis to visually group statements with many operators rather than varying widths of whitespace. This rule does not apply to range operators (e.g. 1...3) and postfix or prefix operators (e.g. guest? or -1). SwiftLint: operator_usage_whitespace

    // WRONG
    let capacity = 1+2
    let capacity = currentCapacity   ?? 0
    let mask = (UIAccessibilityTraitButton|UIAccessibilityTraitSelected)
    let capacity=newCapacity
    let latitude = region.center.latitude - region.span.latitudeDelta/2.0
    
    // RIGHT
    let capacity = 1 + 2
    let capacity = currentCapacity ?? 0
    let mask = (UIAccessibilityTraitButton | UIAccessibilityTraitSelected)
    let capacity = newCapacity
    let latitude = region.center.latitude - (region.span.latitudeDelta / 2.0)
  • (link) Long ternary operator expressions should wrap before the ? and before the :, putting each conditional branch on a separate line. SwiftFormat: wrap

    // WRONG (too long)
    let destinationPlanet = solarSystem.hasPlanetsInHabitableZone ? solarSystem.planetsInHabitableZone.first : solarSystem.uninhabitablePlanets.first
    
    // WRONG (naive wrapping)
    let destinationPlanet = solarSystem.hasPlanetsInHabitableZone ? solarSystem.planetsInHabitableZone.first :
      solarSystem.uninhabitablePlanets.first
    
    // WRONG (unbalanced operators)
    let destinationPlanet = solarSystem.hasPlanetsInHabitableZone ?
      solarSystem.planetsInHabitableZone.first :
      solarSystem.uninhabitablePlanets.first
    
    // RIGHT
    let destinationPlanet = solarSystem.hasPlanetsInHabitableZone
      ? solarSystem.planetsInHabitableZone.first
      : solarSystem.uninhabitablePlanets.first
  • (link) In conditional statements (if, guard, while), separate boolean conditions using commas (,) instead of && operators. SwiftFormat: andOperator

    // WRONG
    if let star = planet.star, !planet.isHabitable && planet.isInHabitableZone(of: star) {
      planet.terraform()
    }
    
    if
      let star = planet.star, 
      !planet.isHabitable 
      && planet.isInHabitableZone(of: star)
    {
      planet.terraform()
    }
    
    // RIGHT
    if let star = planet.star, !planet.isHabitable, planet.isInHabitableZone(of: star) {
      planet.terraform()
    }
    
    if
      let star = planet.star,
      !planet.isHabitable,
      planet.isInHabitableZone(of: star)
    {
      planet.terraform()
    }
  • (link) When extending bound generic types, prefer using generic bracket syntax (extension Collection<Planet>), or sugared syntax for applicable standard library types (extension [Planet]) instead of generic type constraints. SwiftFormat: genericExtensions

    // WRONG
    extension Array where Element == Star {  }
    extension Optional where Wrapped == Spaceship {  }
    extension Dictionary where Key == Moon, Element == Planet {  }
    extension Collection where Element == Universe {  }
    extension StateStore where State == SpaceshipState, Action == SpaceshipAction {  }
    
    // RIGHT
    extension [Star] {  }
    extension Spaceship? {  }
    extension [Moon: Planet] {  }
    extension Collection<Universe> {  }
    extension StateStore<SpaceshipState, SpaceshipAction> {  }
    
    // ALSO RIGHT. There are multiple types that could satisfy this constraint
    // (e.g. [Planet], [Moon]), so this is not a "bound generic type" and isn't
    // eligible for the generic bracket syntax.
    extension Array where Element: PlanetaryBody { }

back to top

Patterns

  • (link) Prefer initializing properties at init time whenever possible, rather than using implicitly unwrapped optionals. A notable exception is UIViewController's view property. SwiftLint: implicitly_unwrapped_optional

    // WRONG
    class MyClass {
    
      init() {
        super.init()
        someValue = 5
      }
    
      var someValue: Int!
    }
    
    // RIGHT
    class MyClass {
    
      init() {
        someValue = 0
        super.init()
      }
    
      var someValue: Int
    }
  • (link) Avoid performing any meaningful or time-intensive work in init(). Avoid doing things like opening database connections, making network requests, reading large amounts of data from disk, etc. Create something like a start() method if these things need to be done before an object is ready for use.

  • (link) Extract complex property observers into methods. This reduces nestedness, separates side-effects from property declarations, and makes the usage of implicitly-passed parameters like oldValue explicit.

    // WRONG
    class TextField {
      var text: String? {
        didSet {
          guard oldValue != text else {
            return
          }
    
          // Do a bunch of text-related side-effects.
        }
      }
    }
    
    // RIGHT
    class TextField {
      var text: String? {
        didSet { textDidUpdate(from: oldValue) }
      }
    
      private func textDidUpdate(from oldValue: String?) {
        guard oldValue != text else {
          return
        }
    
        // Do a bunch of text-related side-effects.
      }
    }
  • (link) Extract complex callback blocks into methods. This limits the complexity introduced by weak-self in blocks and reduces nestedness. If you need to reference self in the method call, make use of guard to unwrap self for the duration of the callback.

    // WRONG
    class MyClass {
    
      func request(completion: () -> Void) {
        API.request() { [weak self] response in
          if let self {
            // Processing and side effects
          }
          completion()
        }
      }
    }
    
    // RIGHT
    class MyClass {
    
      func request(completion: () -> Void) {
        API.request() { [weak self] response in
          guard let self else { return }
          self.doSomething(with: self.property, response: response)
          completion()
        }
      }
    
      func doSomething(with nonOptionalParameter: SomeClass, response: SomeResponseClass) {
        // Processing and side effects
      }
    }
  • (link) Prefer using guard at the beginning of a scope.

    Why?

    It's easier to reason about a block of code when all guard statements are grouped together at the top rather than intermixed with business logic.

  • (link) Access control should be at the strictest level possible. Prefer public to open and private to fileprivate unless you need that behavior. SwiftFormat: redundantFileprivate

    // WRONG
    public struct Spaceship {
      // WRONG: `engine` is used in `extension Spaceship` below,
      // but extensions in the same file can access `private` members.
      fileprivate let engine: AntimatterEngine
    
      // WRONG: `hull` is not used by any other type, so `fileprivate` is unnecessary. 
      fileprivate let hull: Hull
    
      // RIGHT: `navigation` is used in `extension Pilot` below,
      // so `fileprivate` is necessary here.
      fileprivate let navigation: SpecialRelativityNavigationService
    }
    
    extension Spaceship {
      public func blastOff() {
        engine.start()
      }
    }
    
    extension Pilot {
      public func chartCourse() {
        spaceship.navigation.course = .andromedaGalaxy
        spaceship.blastOff()
      }
    }
    // RIGHT
    public struct Spaceship {
      fileprivate let navigation: SpecialRelativityNavigationService
      private let engine: AntimatterEngine
      private let hull: Hull
    }
    
    extension Spaceship {
      public func blastOff() {
        engine.start()
      }
    }
    
    extension Pilot {
      public func chartCourse() {
        spaceship.navigation.course = .andromedaGalaxy
        spaceship.blastOff()
      }
    }
  • (link) Avoid global functions whenever possible. Prefer methods within type definitions.

    // WRONG
    func age(of person: Person, bornAt: TimeInterval) -> Int {
      // ...
    }
    
    func jump(person: Person) {
      // ...
    }
    
    // RIGHT
    class Person {
      var bornAt: TimeInterval
    
      var age: Int {
        // ...
      }
    
      func jump() {
        // ...
      }
    }
  • (link) Use caseless enums for organizing public or internal constants and functions into namespaces. SwiftFormat: enumNamespaces

    • Avoid creating non-namespaced global constants and functions.
    • Feel free to nest namespaces where it adds clarity.
    • private globals are permitted, since they are scoped to a single file and do not pollute the global namespace. Consider placing private globals in an enum namespace to match the guidelines for other declaration types.

    Why?

    Caseless enums work well as namespaces because they cannot be instantiated, which matches their intent.

    // WRONG
    struct Environment { 
      static let earthGravity = 9.8 
      static let moonGravity = 1.6 
    }
    
    // WRONG
    struct Environment {
    
      struct Earth {
        static let gravity = 9.8
      }
    
      struct Moon {
        static let gravity = 1.6
      }
    }
    
    // RIGHT
    enum Environment {
    
      enum Earth {
        static let gravity = 9.8
      }
    
      enum Moon {
        static let gravity = 1.6
      }
    }
  • (link) Use Swift's automatic enum values unless they map to an external source. Add a comment explaining why explicit values are defined. SwiftFormat: redundantRawValues

    Why?

    To minimize user error, improve readability, and write code faster, rely on Swift's automatic enum values. If the value maps to an external source (e.g. it's coming from a network request) or is persisted across binaries, however, define the values explicitly, and document what these values are mapping to.

    This ensures that if someone adds a new value in the middle, they won't accidentally break things.

    // WRONG
    enum ErrorType: String {
      case error = "error"
      case warning = "warning"
    }
    
    // WRONG
    enum UserType: String {
      case owner
      case manager
      case member
    }
    
    // WRONG
    enum Planet: Int {
      case mercury = 0
      case venus = 1
      case earth = 2
      case mars = 3
      case jupiter = 4
      case saturn = 5
      case uranus = 6
      case neptune = 7
    }
    
    // WRONG
    enum ErrorCode: Int {
      case notEnoughMemory
      case invalidResource
      case timeOut
    }
    
    // RIGHT
    // Relying on Swift's automatic enum values
    enum ErrorType: String {
      case error
      case warning
    }
    
    // RIGHT
    /// These are written to a logging service. Explicit values ensure they're consistent across binaries.
    // swiftformat:disable redundantRawValues
    enum UserType: String {
      case owner = "owner"
      case manager = "manager"
      case member = "member"
    }
    // swiftformat:enable redundantRawValues
    
    // RIGHT
    // Relying on Swift's automatic enum values
    enum Planet: Int {
      case mercury
      case venus
      case earth
      case mars
      case jupiter
      case saturn
      case uranus
      case neptune
    }
    
    // RIGHT
    /// These values come from the server, so we set them here explicitly to match those values.
    enum ErrorCode: Int {
      case notEnoughMemory = 0
      case invalidResource = 1
      case timeOut = 2
    }
  • (link) Use optionals only when they have semantic meaning.

  • (link) Prefer immutable values whenever possible. Use map and compactMap instead of appending to a new collection. Use filter instead of removing elements from a mutable collection.

    Why?

    Mutable variables increase complexity, so try to keep them in as narrow a scope as possible.

    // WRONG
    var results = [SomeType]()
    for element in input {
      let result = transform(element)
      results.append(result)
    }
    
    // RIGHT
    let results = input.map { transform($0) }
    // WRONG
    var results = [SomeType]()
    for element in input {
      if let result = transformThatReturnsAnOptional(element) {
        results.append(result)
      }
    }
    
    // RIGHT
    let results = input.compactMap { transformThatReturnsAnOptional($0) }
  • (link) Prefer immutable or computed static properties over mutable ones whenever possible. Use stored static let properties or computed static var properties over stored static var properties whenever possible, as stored static var properties are global mutable state.

    Why?

    Global mutable state increases complexity and makes it harder to reason about the behavior of applications. It should be avoided when possible.

    // WRONG
    enum Fonts {
      static var title = UIFont()
    }
    
    // RIGHT
    enum Fonts {
      static let title = UIFont()
    }
    // WRONG
    struct FeatureState {
      var count: Int
    
      static var initial = FeatureState(count: 0)
    }
    
    // RIGHT
    struct FeatureState {
      var count: Int
    
      static var initial: FeatureState {
        // Vend static properties that are cheap to compute
        FeatureState(count: 0)
      }
    }
  • (link) Handle an unexpected but recoverable condition with an assert method combined with the appropriate logging in production. If the unexpected condition is not recoverable, prefer a precondition method or fatalError(). This strikes a balance between crashing and providing insight into unexpected conditions in the wild. Only prefer fatalError over a precondition method when the failure message is dynamic, since a precondition method won't report the message in the crash report. SwiftLint: fatal_error_message

    func didSubmitText(_ text: String) {
      // It's unclear how this was called with an empty string; our custom text field shouldn't allow this.
      // This assert is useful for debugging but it's OK if we simply ignore this scenario in production.
      guard !text.isEmpty else {
        assertionFailure("Unexpected empty string")
        return
      }
      // ...
    }
    
    func transformedItem(atIndex index: Int, from items: [Item]) -> Item {
      precondition(index >= 0 && index < items.count)
      // It's impossible to continue executing if the precondition has failed.
      // ...
    }
    
    func makeImage(name: String) -> UIImage {
      guard let image = UIImage(named: name, in: nil, compatibleWith: nil) else {
        fatalError("Image named \(name) couldn't be loaded.")
        // We want the error message so we know the name of the missing image.
      }
      return image
    }
  • (link) Default type methods to static.

    Why?

    If a method needs to be overridden, the author should opt into that functionality by using the class keyword instead.

    // WRONG
    class Fruit {
      class func eatFruits(_ fruits: [Fruit]) { ... }
    }
    
    // RIGHT
    class Fruit {
      static func eatFruits(_ fruits: [Fruit]) { ... }
    }
  • (link) Default classes to final.

    Why?

    If a class needs to be overridden, the author should opt into that functionality by omitting the final keyword.

    // WRONG
    class SettingsRepository {
      // ...
    }
    
    // RIGHT
    final class SettingsRepository {
      // ...
    }
  • (link) Never use the default case when switching over an enum.

    Why?

    Enumerating every case requires developers and reviewers have to consider the correctness of every switch statement when new cases are added.

    // WRONG
    switch trafficLight {
    case .greenLight:
      // Move your vehicle
    default:
      // Stop your vehicle
    }
    
    // RIGHT
    switch trafficLight {
    case .greenLight:
      // Move your vehicle
    case .yellowLight, .redLight:
      // Stop your vehicle
    }
  • (link) Check for nil rather than using optional binding if you don't need to use the value. SwiftLint: unused_optional_binding

    Why?

    Checking for nil makes it immediately clear what the intent of the statement is. Optional binding is less explicit.

    var thing: Thing?
    
    // WRONG
    if let _ = thing {
      doThing()
    }
    
    // RIGHT
    if thing != nil {
      doThing()
    }
  • (link) Omit the return keyword when not required by the language. SwiftFormat: redundantReturn

    // WRONG
    ["1", "2", "3"].compactMap { return Int($0) }
    
    var size: CGSize {
      return CGSize(
        width: 100.0,
        height: 100.0)
    }
    
    func makeInfoAlert(message: String) -> UIAlertController {
      return UIAlertController(
        title: "ℹ️ Info",
        message: message,
        preferredStyle: .alert)
    }
    
    // RIGHT
    ["1", "2", "3"].compactMap { Int($0) }
    
    var size: CGSize {
      CGSize(
        width: 100.0,
        height: 100.0)
    }
    
    func makeInfoAlert(message: String) -> UIAlertController {
      UIAlertController(
        title: "ℹ️ Info",
        message: message,
        preferredStyle: .alert)
    }
  • (link) Use AnyObject instead of class in protocol definitions. SwiftFormat: anyObjectProtocol

    Why?

    SE-0156, which introduced support for using the AnyObject keyword as a protocol constraint, recommends preferring AnyObject over class:

    This proposal merges the concepts of class and AnyObject, which now have the same meaning: they represent an existential for classes. To get rid of the duplication, we suggest only keeping AnyObject around. To reduce source-breakage to a minimum, class could be redefined as typealias class = AnyObject and give a deprecation warning on class for the first version of Swift this proposal is implemented in. Later, class could be removed in a subsequent version of Swift.

    // WRONG
    protocol Foo: class { }
    
    // RIGHT
    protocol Foo: AnyObject { }
  • (link) Specify the access control for each declaration in an extension individually. SwiftFormat: extensionAccessControl

    Why?

    Specifying the access control on the declaration itself helps engineers more quickly determine the access control level of an individual declaration.

    // WRONG
    public extension Universe {
      // This declaration doesn't have an explicit access control level.
      // In all other scopes, this would be an internal function,
      // but because this is in a public extension, it's actually a public function.
      func generateGalaxy() { }
    }
    
    // WRONG
    private extension Spaceship {
      func enableHyperdrive() { }
    }
    
    // RIGHT
    extension Universe {
      // It is immediately obvious that this is a public function,
      // even if the start of the `extension Universe` scope is off-screen.
      public func generateGalaxy() { }
    }
    
    // RIGHT
    extension Spaceship {
      // Recall that a private extension actually has fileprivate semantics,
      // so a declaration in a private extension is fileprivate by default.
      fileprivate func enableHyperdrive() { }
    }
  • (link) Prefer dedicated logging systems like os_log or swift-log over writing directly to standard out using print(…), debugPrint(…), or dump(…).

    Why?

    All log messages should flow into intermediate logging systems that can direct messages to the correct destination(s) and potentially filter messages based on the app's environment or configuration. print(…), debugPrint(…), or dump(…) will write all messages directly to standard out in all app configurations and can potentially leak personally identifiable information (PII).

  • (link) Avoid single-expression closures that are always called immediately. Instead, prefer inlining the expression. SwiftFormat: redundantClosure

    // WRONG
    lazy var universe: Universe = { 
      Universe() 
    }()
    
    lazy var stars = {
      universe.generateStars(
        at: location,
        count: 5,
        color: starColor,
        withAverageDistance: 4)
    }()
    
    // RIGHT
    lazy var universe = Universe() 
    
    lazy var stars = universe.generateStars(
      at: location,
      count: 5,
      color: starColor,
      withAverageDistance: 4)
  • (link) Omit the get clause from a computed property declaration that doesn't also have a set, willSet, or didSet clause. SwiftFormat: redundantGet

    // WRONG
    var universe: Universe {
      get {
        Universe()
      }
    }
    
    // RIGHT
    var universe: Universe {
      Universe()
    }
    
    // RIGHT
    var universe: Universe {
      get { multiverseService.current }
      set { multiverseService.current = newValue }
    }
  • (link) Prefer using opaque generic parameters (with some) over verbose named generic parameter syntax where possible. SwiftFormat: opaqueGenericParameters

    Why?

    Opaque generic parameter syntax is significantly less verbose and thus more legible than the full named generic parameter syntax.

    // WRONG
    func spaceshipDashboard<WarpDriveView: View, CaptainsLogView: View>(
      warpDrive: WarpDriveView,
      captainsLog: CaptainsLogView)
      -> some View
    {  }
    
    func generate<Planets>(_ planets: Planets) where Planets: Collection, Planets.Element == Planet {
      
    }
    
    // RIGHT
    func spaceshipDashboard(
      warpDrive: some View,
      captainsLog: some View)
      -> some View
    {  }
    
    func generate(_ planets: some Collection<Planet>) {
      
    }
    
    // Also fine, since there isn't an equivalent opaque parameter syntax for expressing
    // that two parameters in the type signature are of the same type:
    func terraform<Body: PlanetaryBody>(_ planetaryBody: Body, into terraformedBody: Body) {
      
    }
    
    // Also fine, since the generic parameter name is referenced in the function body so can't be removed:
    func terraform<Body: PlanetaryBody>(_ planetaryBody: Body)  {
      planetaryBody.generateAtmosphere(Body.idealAtmosphere)
    }

    some Any

    Fully-unconstrained generic parameters are somewhat uncommon, but are equivalent to some Any. For example:

    func assertFailure<Value>(
      _ result: Result<Value, Error>,
      file: StaticString = #filePath,
      line: UInt = #line)
    {
      if case .failure(let error) = result {
        XCTFail(error.localizedDescription, file: file, line: line)
      }
    }
    
    // is equivalent to:
    func assertFailure(
      _ result: Result<some Any, Error>,
      file: StaticString = #filePath,
      line: UInt = #line)
    {
      if case .failure(let error) = result {
        XCTFail(error.localizedDescription, file: file, line: line)
      }
    }

    some Any is somewhat unintuitive, and the named generic parameter is useful in this situation to compensate for the weak type information. Because of this, prefer using named generic parameters instead of some Any.

back to top

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. SwiftFormat: sortedImports SwiftFormat: duplicateImports

    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. SwiftLint: vertical_whitespace SwiftFormat: consecutiveSpaces

    // 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. SwiftLint: trailing_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. SwiftFormat: blankLinesBetweenScopes

    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. SwiftFormat: blankLinesAtStartOfScope SwiftFormat: blankLinesAtEndOfScope

    // 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. SwiftFormat: markTypes

    • 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.
    // 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. SwiftFormat: organizeDeclarations

    • // MARK: Lifecycle for init and deinit methods.
    • // MARK: Open for open properties and methods.
    • // MARK: Public for public properties and methods.
    • // MARK: Internal for internal properties and methods.
    • // MARK: Fileprivate for fileprivate properties and methods.
    • // MARK: Private for private 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. SwiftFormat: organizeDeclarations

    • 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.) SwiftFormat: organizeDeclarations

    // 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.) SwiftFormat: organizeDeclarations

    // 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")
      }
    }

back to top

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() {
        // ...
      }
    }

back to top

Contributors

back to top

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.

back to top

More Repositories

1

javascript

JavaScript Style Guide
JavaScript
141,845
star
2

lottie-android

Render After Effects animations natively on Android and iOS, Web, and React Native
Java
34,600
star
3

lottie-web

Render After Effects animations natively on Web, Android and iOS, and React Native. http://airbnb.io/lottie/
JavaScript
29,564
star
4

lottie-ios

An iOS library to natively render After Effects vector animations
Swift
24,897
star
5

visx

🐯 visx | visualization components
TypeScript
18,609
star
6

react-sketchapp

render React components to Sketch ⚛️💎
TypeScript
14,951
star
7

react-dates

An easily internationalizable, mobile-friendly datepicker library for the web
JavaScript
11,630
star
8

epoxy

Epoxy is an Android library for building complex screens in a RecyclerView
Java
8,426
star
9

css

A mostly reasonable approach to CSS and Sass.
6,869
star
10

hypernova

A service for server-side rendering your JavaScript views
JavaScript
5,824
star
11

mavericks

Mavericks: Android on Autopilot
Kotlin
5,741
star
12

knowledge-repo

A next-generation curated knowledge sharing platform for data scientists and other technical professions.
Python
5,432
star
13

ts-migrate

A tool to help migrate JavaScript code quickly and conveniently to TypeScript
TypeScript
5,307
star
14

aerosolve

A machine learning package built for humans.
Scala
4,790
star
15

DeepLinkDispatch

A simple, annotation-based library for making deep link handling better on Android
Java
4,356
star
16

lottie

Lottie documentation for http://airbnb.io/lottie.
HTML
4,289
star
17

ruby

Ruby Style Guide
Ruby
3,711
star
18

polyglot.js

Give your JavaScript the ability to speak many languages.
JavaScript
3,644
star
19

MagazineLayout

A collection view layout capable of laying out views in vertically scrolling grids and lists.
Swift
3,232
star
20

native-navigation

Native navigation library for React Native applications
Java
3,127
star
21

streamalert

StreamAlert is a serverless, realtime data analysis framework which empowers you to ingest, analyze, and alert on data from any environment, using datasources and alerting logic you define.
Python
2,825
star
22

infinity

UITableViews for the web (DEPRECATED)
JavaScript
2,809
star
23

airpal

Web UI for PrestoDB.
Java
2,760
star
24

HorizonCalendar

A declarative, performant, iOS calendar UI component that supports use cases ranging from simple date pickers all the way up to fully-featured calendar apps.
Swift
2,656
star
25

synapse

A transparent service discovery framework for connecting an SOA
Ruby
2,067
star
26

Showkase

🔦 Showkase is an annotation-processor based Android library that helps you organize, discover, search and visualize Jetpack Compose UI elements
Kotlin
2,018
star
27

paris

Define and apply styles to Android views programmatically
Kotlin
1,894
star
28

AirMapView

A view abstraction to provide a map user interface with various underlying map providers
Java
1,861
star
29

react-with-styles

Use CSS-in-JavaScript with themes for React without being tightly coupled to one implementation
JavaScript
1,697
star
30

rheostat

Rheostat is a www, mobile, and accessible slider component built with React
JavaScript
1,692
star
31

binaryalert

BinaryAlert: Serverless, Real-time & Retroactive Malware Detection.
Python
1,382
star
32

epoxy-ios

Epoxy is a suite of declarative UI APIs for building UIKit applications in Swift
Swift
1,142
star
33

nerve

A service registration daemon that performs health checks; companion to airbnb/synapse
Ruby
942
star
34

okreplay

📼 Record and replay OkHttp network interaction in your tests.
Groovy
775
star
35

RxGroups

Easily group RxJava Observables together and tie them to your Android Activity lifecycle
Java
693
star
36

prop-types

Custom React PropType validators that we use at Airbnb.
JavaScript
672
star
37

react-outside-click-handler

OutsideClickHandler component for React.
JavaScript
603
star
38

ResilientDecoding

This package makes your Decodable types resilient to decoding errors and allows you to inspect those errors.
Swift
580
star
39

babel-plugin-dynamic-import-node

Babel plugin to transpile import() to a deferred require(), for node
JavaScript
575
star
40

kafkat

KafkaT-ool
Ruby
504
star
41

babel-plugin-dynamic-import-webpack

Babel plugin to transpile import() to require.ensure, for Webpack
JavaScript
500
star
42

chronon

Chronon is a data platform for serving for AI/ML applications.
Scala
479
star
43

babel-plugin-inline-react-svg

A babel plugin that optimizes and inlines SVGs for your React Components.
JavaScript
474
star
44

lunar

🌗 React toolkit and design language for Airbnb open source and internal projects.
TypeScript
461
star
45

BuckSample

An example app showing how Buck can be used to build a simple iOS app.
Objective-C
460
star
46

SpinalTap

Change Data Capture (CDC) service
Java
428
star
47

artificial-adversary

🗣️ Tool to generate adversarial text examples and test machine learning models against them
Python
390
star
48

dynein

Airbnb's Open-source Distributed Delayed Job Queueing System
Java
383
star
49

hammerspace

Off-heap large object storage
Ruby
364
star
50

trebuchet

Trebuchet launches features at people
Ruby
313
star
51

reair

ReAir is a collection of easy-to-use tools for replicating tables and partitions between Hive data warehouses.
Java
278
star
52

zonify

a command line tool for generating DNS records from EC2 instances
Ruby
270
star
53

ottr

Serverless Public Key Infrastructure Framework
Python
266
star
54

omniduct

A toolkit providing a uniform interface for connecting to and extracting data from a wide variety of (potentially remote) data stores (including HDFS, Hive, Presto, MySQL, etc).
Python
249
star
55

hypernova-react

React bindings for Hypernova.
JavaScript
248
star
56

smartstack-cookbook

The chef recipes for running and testing Airbnb's SmartStack
Ruby
244
star
57

interferon

Signaling you about infrastructure or application issues
Ruby
239
star
58

prop-types-exact

For use with React PropTypes. Will error on any prop not explicitly specified.
JavaScript
237
star
59

backpack

A pack of UI components for Backbone projects. Grab your backpack and enjoy the Views.
HTML
223
star
60

babel-preset-airbnb

A babel preset for transforming your JavaScript for Airbnb
JavaScript
222
star
61

goji-js

React ❤️ Mini Program
TypeScript
213
star
62

react-with-direction

Components to provide and consume RTL or LTR direction in React
JavaScript
192
star
63

stemcell

Airbnb's EC2 instance creation and bootstrapping tool
Ruby
185
star
64

hypernova-ruby

Ruby client for Hypernova.
Ruby
141
star
65

kafka-statsd-metrics2

Send Kafka Metrics to StatsD.
Java
135
star
66

optica

A tool for keeping track of nodes in your infrastructure
Ruby
134
star
67

sparsam

Fast Thrift Bindings for Ruby
C++
125
star
68

js-shims

JS language shims used by Airbnb.
JavaScript
123
star
69

browser-shims

Browser and JS shims used by Airbnb.
JavaScript
118
star
70

bossbat

Stupid simple distributed job scheduling in node, backed by redis.
JavaScript
118
star
71

nimbus

Centralized CLI for JavaScript and TypeScript developer tools.
TypeScript
118
star
72

lottie-spm

Swift Package Manager support for Lottie, an iOS library to natively render After Effects vector animations
Ruby
106
star
73

twitter-commons-sample

A sample REST service based on Twitter Commons
Java
103
star
74

is-touch-device

Is the current JS environment a touch device?
JavaScript
90
star
75

rudolph

A serverless sync server for Santa, built on AWS
Go
73
star
76

hypernova-node

node.js client for Hypernova
JavaScript
73
star
77

plog

Fire-and-forget UDP logging service with custom Netty pipelines & extensive monitoring
Java
72
star
78

cloud-maker

Building castles in the sky
Ruby
67
star
79

react-create-hoc

Create a React Higher-Order Component (HOC) following best practices.
JavaScript
66
star
80

vulnture

Python
65
star
81

deline

An ES6 template tag that strips unwanted newlines from strings.
JavaScript
63
star
82

react-with-styles-interface-react-native

Interface to use react-with-styles with React Native
JavaScript
63
star
83

sputnik

Scala
61
star
84

mocha-wrap

Fluent pluggable interface for easily wrapping `describe` and `it` blocks in Mocha tests.
JavaScript
54
star
85

react-with-styles-interface-aphrodite

Interface to use react-with-styles with Aphrodite
JavaScript
54
star
86

eslint-plugin-react-with-styles

ESLint plugin for react-with-styles
JavaScript
49
star
87

sssp

Software distribution by way of S3 signed URLs
Haskell
47
star
88

alerts

An example alerts repo, for use with airbnb/interferon.
Ruby
46
star
89

apple-tv-auth

Example application to demonstrate how to build Apple TV style authentication.
Ruby
44
star
90

airbnb-spark-thrift

A library for loadling Thrift data into Spark SQL
Scala
43
star
91

jest-wrap

Fluent pluggable interface for easily wrapping `describe` and `it` blocks in Jest tests.
JavaScript
39
star
92

billow

Query AWS data without API credentials. Don't wait for a response.
Java
38
star
93

gosal

A Sal client written in Go
Go
36
star
94

backbone.baseview

DEPRECATED: A simple base view class for Backbone.View
JavaScript
34
star
95

anotherlens

News Deeply X Airbnb.Design - Another Lens
HTML
33
star
96

eslint-plugin-miniprogram

TypeScript
33
star
97

react-component-variations

JavaScript
33
star
98

react-with-styles-interface-css

📃 CSS interface for react-with-styles
JavaScript
31
star
99

puppet-munki

Puppet
29
star
100

transformpy

transformpy is a Python 2/3 module for doing transforms on "streams" of data
Python
29
star