Airbnb Swift Style Guide

repository·master·Indexed 25 days ago

https://github.com/airbnb/swift

A set of rules and automated tools to improve code readability, maintainability, and consistency in Swift projects. It includes a Swift Package Manager command plugin for automated linting and formatting, as well as guidelines for naming conventions, type inference, and code formatting.

Tokens
20.1K
Snippets
116
Records
135
Agent score
83%

What's inside Airbnb Swift Style Guide

  1. Prefer throwing tests over try! in unit tests

    master

    Avoid using try! in tests, as it will crash the entire test suite. Instead, mark your test methods as throws and use try to allow the testing framework to handle errors gracefully.

    import Testing
    
    struct SomeTests {
      // WRONG
      @Test
      func something() {
        try! Something().doSomething()
      }
    
      // RIGHT
      @Test
      func something() throws {
        try Something().doSomething()
      }
    }
  2. Use CIFilterBuiltins for type-safe CoreImage filtering

    master

    Prefer using the typed factory methods provided by CoreImage.CIFilterBuiltins over the string-based CIFilter(name:) initializer and KVO setValue(_:forKey:). This provides compile-time type safety and avoids runtime crashes caused by misspelled keys or incorrect types.

    Note: You must import CoreImage.CIFilterBuiltins to access these methods.

    // WRONG
    import CoreImage
    
    guard
      let kMeansFilter = CIFilter(name: "CIKMeans")
    else { return nil }
    
    kMeansFilter.setValue(ciImage, forKey: kCIInputImageKey)
    kMeansFilter.setValue(CIVector(cgRect: ciImage.extent), forKey: "inputExtent")
    kMeansFilter.setValue(1, forKey: "inputCount")
    kMeansFilter.setValue(5, forKey: "inputPasses")
    
    // RIGHT
    import CoreImage.CIFilterBuiltins
    
    let kMeansFilter = CIFilter.kMeans()
    kMeansFilter.inputImage = ciImage
    kMeansFilter.extent = CIVector(cgRect: ciImage.extent)
    kMeansFilter.count = 1
    kMeansFilter.passes = 5
  3. Avoid using `unowned` captures

    master

    Avoid unowned captures because they cause crashes if the referenced object is deallocated. Instead, use weak captures (and handle the nil case) or capture the specific variables needed directly.

    // RIGHT: Use weak self and handle nil
    spaceship.travel(to: planet, onArrival: { [weak self] in
      guard let self else { return }
      planet.colonize()
    })
    
    // RIGHT: Capture the variable directly to avoid self
    spaceship.travel(to: planet, onArrival: { [planet] in
      planet.colonize()
    })
  4. Avoid redundant expectation messages in Swift Testing

    master

    When using the #expect macro in Swift Testing, avoid providing a message string that simply restates the condition. The macro automatically generates detailed failure messages. If you need to add context, use a more descriptive string or a code comment.

    // RIGHT: Omits the message string, or adds valuable context
    @Test
    func `engage warp drive`() {
      spaceship.engageWarpDrive()
      #expect(spaceship.isWarpDriveActive)
      #expect(spaceship.speed > lightSpeed, "Spaceship must reach light speed before the warp bubble can form")
    }
  5. Install the Airbnb Swift Style Guide via Swift Package Manager

    master

    To use the Airbnb Swift Style Guide's automated formatting and linting, add this repository as a dependency in your Package.swift file.

    dependencies: [
      .package(url: "https://github.com/airbnb/swift", from: "1.0.0"),
    ]
  6. Formatting: Return arrows and Parentheses

    master

    Follow these spacing and syntax rules:

    • Return Arrows: Place a space on both sides of a return arrow (->) for readability.
    • Unnecessary Parentheses: Omit parentheses in if and switch statements, and in closure parameter lists where they aren't required.
    // RIGHT: Spacing around return arrows
    func doSomething() -> String { ... }
    func doSomething(completion: () -> Void) { ... }
    
    // RIGHT: Omitting unnecessary parentheses
    if userCount > 0 { ... }
    switch someValue { ... }
    let evens = userCounts.filter { number in number.isMultiple(of: 2) }
    let squares = userCounts.map { $0 * $0 }
  7. Format long function declarations

    master

    For long function declarations, use line breaks before each argument label and before the closing parenthesis ). This prevents visual blending with the function body and ensures compatibility with Xcode's indentation.

    // RIGHT
    func generateStars(
      at location: Point,
      count: Int,
      color: StarColor,
      withAverageDistance averageDistance: Float
    ) -> String {
      populateUniverse()
    }
    
    // ALSO RIGHT (with async/throws)
    func generateStars(
      at location: Point,
      count: Int,
      color: StarColor,
      withAverageDistance averageDistance: Float
    ) async throws -> String {
      populateUniverse()
    }
  8. Optimize collection counting with count(where:)

    master

    When working with Swift 6.0+, prefer using the count(where:) method instead of filtering a collection and then calling .count. This is more direct and efficient.

    // WRONG
    let planetsWithMoons = planets.filter { !$0.moons.isEmpty }.count
    
    // RIGHT
    let planetsWithMoons = planets.count(where: { !$0.moons.isEmpty })
  9. Omit redundant @ViewBuilder attributes

    master

    Do not explicitly use @ViewBuilder when it is not required. It is implicitly applied to View.body properties and ViewModifier.body(content:) functions. It is also unnecessary for single-expression properties or functions. Use @ViewBuilder only when necessary, such as for properties containing conditional logic (e.g., if statements).

    // RIGHT: @ViewBuilder is implicit on body
    struct PlanetView: View {
      var body: some View {
        Text("Hello, World!")
      }
    
      // ALSO RIGHT: @ViewBuilder is necessary for conditionals
      @ViewBuilder
      var title: some View {
        if showDetails {
          Text("Details")
        } else {
          Text("Summary")
        }
      }
    }
  10. Omit redundant typed throws annotations

    master

    Do not use redundant typed throws annotations. throws(Never) is equivalent to a non-throwing function, and throws(Error) is equivalent to a standard non-typed throws.

    // RIGHT
    func doSomething() -> Int {
      return 0
    }
    
    func doSomethingElse() throws -> Int {
      throw MyError.failed
    }
  11. Wrap function and property bodies onto multiple lines

    master

    Wrap function and property bodies onto multiple lines rather than using single-line declarations. Note that this rule does not apply to protocol definitions.

    // RIGHT
    var galaxy: String {
      "Milky Way"
    }
    
    func launchRocket() {
        print("Launching")
    }
    
    init() {
        self.value = 0
    }
    
    subscript(index: Int) -> Int {
        array[index]
    }
    
    // Protocol definitions remain single-line
    protocol SpaceshipEngine {
      func engage() -> Bool
      var fuelLevel: Double { get }
    }
  12. Omit redundant SwiftUI Group wrappers

    master

    Avoid using Group wrappers that wrap the entire content of a @ViewBuilder context (like View.body) without applying any modifiers. Inside a @ViewBuilder property, using @ViewBuilder is more idiomatic and reduces nesting compared to Group.

    Note: Group is still useful when you need to apply a modifier to a collection of views at once.

    // RIGHT: Using @ViewBuilder instead of Group to reduce nesting
    struct SpacecraftView: View {
      var body: some View {
        Text("Voyager")
        instruments
      }
    
      @ViewBuilder
      var instruments: some View {
        Text("Altimeter")
        Text("Gyroscope")
      }
    }
    
    // ALSO RIGHT: Group is not redundant when a modifier is applied to it
    struct SpacecraftView: View {
      var body: some View {
        Group {
          Text("Voyager")
          instruments
        }
        .padding()
      }
    }