Swift
Airbnb's Swift Style Guide
Install / Use
/learn @airbnb/SwiftREADME
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 <kbd>^</kbd> + <kbd>I</kbd> 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.
- Format rules should be non-destructive.
Swift Package Manager command plugin
We offer 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
<details>
<summary>Usage guide</summary>
# Prompts for permission to write to the package directory.
$ swift package format
# When using a noninteractive shell, you can 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 6.2
The package plugin returns a non-zero exit code if there is a lint failure that requires attention.
- In
--lintmode, 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
Xcode Formatting
You can enable the following settings in Xcode by running this script, e.g. as part of a "Run Script" build phase.
-
<a id='column-width'></a>(<a href='#column-width'>link</a>) Each line should have a maximum column width of 100 characters.
<details>Why?
Due to larger screen sizes, we have opted to choose a page guide greater than 80.
We currently only "strictly enforce" (lint / auto-format) a maximum column width of 130 characters to limit the cases where manual clean up is required for reformatted lines that fall slightly above the threshold.
</details> -
<a id='spaces-over-tabs'></a>(<a href='#spaces-over-tabs'>link</a>) Use 2 spaces to indent lines.
<details> </details> -
<a id='trailing-whitespace'></a>(<a href='#trailing-whitespace'>link</a>) Trim trailing whitespace in all lines.
<details> </details>
Naming
-
<a id='use-camel-case'></a>(<a href='#use-camel-case'>link</a>) Use UpperCamelCase for type and protocol names, and lowerCamelCase for everything else.
<details>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
</details>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 } } -
<a id='bool-names'></a>(<a href='#bool-names'>link</a>) Name booleans like
isSpaceship,hasSpacesuit, etc. This makes it clear that they are booleans and not other types. -
<a id='capitalize-acronyms'></a>(<a href='#capitalize-acronyms'>link</a>) Acronyms in names (
<details>ID,URL, etc) 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.
</details>// 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) -
<a id='past-tense-events'></a>(<a href='#past-tense-events'>link</a>) Event-handling functions should be named like past-tense sentences (e.g.
<details>didTap, nothandleTap). The subject can be omitted if it's not needed for clarity.
</details>// WRONG class ExperiencesViewController { private func handleBookButtonTap() { ... } private func modelChanged() { ... } } // RIGHT class ExperiencesViewController { private func didTapBookButton() { ... } private func modelDidChange() { ... } } -
<a id='avoid-class-prefixes'></a>(<a href='#avoid-class-prefixes'>link</a>) Avoid Objective-C-style acronym prefixes. This is not needed to avoid naming conflicts in Swift.
<details>
</details>// WRONG class AIRAccount { ... } // RIGHT class Account { ... }
Style
-
<a id='use-implicit-types'></a>(<a href='#use-implicit-types'>link</a>) Don't include types where they can be easily inferred.
<details> <!-- ai-skill-include: not fully autocorrectable -->// 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 le
Related Skills
node-connect
354.3kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
112.3kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
354.3kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
354.3kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
