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 SwiftLint autocorrect or SwiftFormat).
- 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.
Table of Contents
- Xcode Formatting
- Naming
- Style
- Functions
- Closures
- Operators
- Patterns
- File Organization
- Objective-C Interoperability
- Contributors
- 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.
Why?
Due to larger screen sizes, we have opted to choose a page guide greater than 80
⬆ back to the 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()Copy the code
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 a property or method 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, session, parser, onSuccess, onFailure) } private let _executeRequest: ( URLRequest, @escaping (ModelType, Bool) -> Void, @escaping (NSError) -> Void) -> URLSessionCancellable }Copy the code
- 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 } }Copy the code
-
Acronyms in names (e.g. URL) should be all-caps except when it’s the start of a name that would otherwise be 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 isUrlReachable(_ URL: URL) -> Bool { // ... } } let URLValidator = UrlValidator().isValidUrl(/* some URL */) // RIGHT class URLValidator { func isValidURL(_ url: URL) -> Bool { // ... } func isURLReachable(_ url: URL) -> Bool { // ... } } let urlValidator = URLValidator().isValidURL(/* some URL */)Copy the code
-
// 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: CGFloatCopy the code
-
(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: UIButtonCopy the code
-
(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() { // ... }}Copy the code
-
// WRONG class AIRAccount { // ... } // RIGHT class Account { // ... }Copy the code
-
(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.
Style
-
(link) Don’t include types where they can be easily inferred.
// WRONG let host: Host = Host() // RIGHT let host = Host()Copy the code
enum Direction { case left case right } func someDirection() -> Direction { // WRONG return Direction.left // RIGHT return .left }Copy the code
-
(link) Don’t use
self
unless it’s necessary for disambiguation or required by the language.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() } }Copy the code
-
(link) Add a trailing comma on the last element of a multi-line array.
// WRONG let rowContent = [ listingUrgencyDatesRowContent(), listingUrgencyBookedRowContent(), listingUrgencyBookedShortRowContent() ] // RIGHT let rowContent = [ listingUrgencyDatesRowContent(), listingUrgencyBookedRowContent(), listingUrgencyBookedShortRowContent(), ]Copy the code
-
// 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.yCopy the code
-
(link) Use constructors instead of Make() functions for CGRect, CGPoint, NSRange and others.
// WRONG let rect = CGRectMake(10, 10, 10, 10) // RIGHT let rect = CGRect(x: 0, y: 0, width: 10, height: 10)Copy the code
-
(link) Favor modern Swift extension methods over older Objective-C global methods.
// WRONG var rect = CGRectZero var width = CGRectGetWidth(rect) // RIGHT var rect = CGRect.zero var width = rect.widthCopy the code
-
(link) Place the colon immediately after an identifier, followed by a space.
// WRONG var something : Double = 0 // RIGHT var something: Double = 0Copy the code
// WRONG class MyClass : SuperClass { // ... } // RIGHT class MyClass: SuperClass { // ... }Copy the code
// WRONG var dict = [KeyType:ValueType]() var dict = [KeyType : ValueType]() // RIGHT var dict = [KeyType: ValueType]()Copy the code
-
(link) Place a space on either side of a return arrow for readability.
// WRONG func doSomething()->String { // ... } // RIGHT func doSomething() -> String { // ... }Copy the code
// WRONG func doSomething(completion: ()->Void) { // ... } // RIGHT func doSomething(completion: () -> Void) { // ... }Copy the code
-
(link) Omit unnecessary parentheses.
// WRONG if (userCount > 0) { ... } switch (someValue) { ... } let evens = userCounts.filter { (number) in number % 2 == 0 } let squares = userCounts.map() { $0 * $0 } // RIGHT if userCount > 0 { ... } switch someValue { ... } let evens = userCounts.filter { number in number % 2 == 0 } let squares = userCounts.map { $0 * $0 }Copy the code
-
(link) Omit enum associated values from case statements when all arguments are unlabeled.
// WRONG if case .done(_) = result { ... } switch animal { case .dog(_, _, _): ... } // RIGHT if case .done = result { ... } switch animal { case .dog: ... }Copy the code
Functions
-
(link) Omit
Void
return types from function definitions.// WRONG func doSomething() -> Void { ... } // RIGHT func doSomething() { ... }Copy the code
Closures
-
// WRONG func method(completion: () -> ()) { ... } // RIGHT func method(completion: () -> Void) { ... }Copy the code
-
(link) Name unused closure parameters as underscores (
_
).Why?
Naming unused closure parameters as underscores reduces the cognitive overhead required to read closures by making it obvious which parameters are used and which are unused.
// WRONG someAsyncThing() { argument1, argument2, argument3 in print(argument3) } // RIGHT someAsyncThing() { _, _, argument3 in print(argument3) }Copy the code
-
// WRONG match(pattern: pattern) .compactMap { range in return Command(string: contents, range: range) } .compactMap { command in return command.expand() } values.forEach { value in print(value) } // RIGHT match(pattern: pattern) .compactMap { range in return Command(string: contents, range: range) } .compactMap { command in return command.expand() } values.forEach { value in print(value) }Copy the code
-
(link) Single-line closures should have a space inside each brace.
// WRONG let evenSquares = numbers.filter {$0 % 2 == 0}.map { $0 * $0 } // RIGHT let evenSquares = numbers.filter { $0 % 2 == 0 }.map { $0 * $0 }Copy the code
Operators
-
// WRONG let capacity = 1+2 let capacity = currentCapacity ?? 0 let mask = (UIAccessibilityTraitButton|UIAccessibilityTraitSelected) let capacity=newCapacity let latitude = Region. The center. The latitude - region. Span. LatitudeDelta / 2.0 / / RIGHT capacity = 1 + 2 let capacity = currentCapacity?? 0 let mask = (UIAccessibilityTraitButton | UIAccessibilityTraitSelected) let capacity = newCapacity let latitude = Region. The center. The latitude - (region) span) latitudeDelta / 2.0)Copy the code
⬆ back to the top
Patterns
-
// WRONG class MyClass: NSObject { init() { super.init() someValue = 5 } var someValue: Int! } // RIGHT class MyClass: NSObject { init() { someValue = 0 super.init() } var someValue: Int }Copy the code
-
// 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 { updateText(from: oldValue) } } private func updateText(from oldValue: String?) { guard oldValue ! = text else { return } // Do a bunch of text-related side-effects. } }Copy the code
-
//WRONG class MyClass { func request(completion: () -> Void) { API.request() { [weak self] response in if let strongSelf = self { // Processing and side effects } completion() } } } // RIGHT class MyClass { func request(completion: () -> Void) { API.request() { [weak self] response in guard let strongSelf = self else { return } strongSelf.doSomething(strongSelf.property) completion() } } func doSomething(nonOptionalParameter: SomeClass) { // Processing and side effects } }Copy the code
-
(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.
-
(link) Avoid global functions whenever possible. Prefer methods within type definitions.
// WRONG func age(of person, bornAt timeInterval) -> Int { // ... } func jump(person: Person) { // ... } // RIGHT class Person { var bornAt: TimeInterval var age: Int { // ... } func jump() { // ... }}Copy the code
-
private let privateValue = "secret" public class MyClass { public static let publicValue = "something" func doSomething() { print(privateValue) print(MyClass.publicValue) } }Copy the code
-
Why?
Caseless
enum
s work well as namespaces because they cannot be instantiated, which matches their intent.Enum Earth {static let gravity = 9.8} enum Moon {static let gravity = 1.6}}Copy the code
-
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 explicity, 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" } enum UserType: String { case owner case manager case member } 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 } enum ErrorCode: Int { case notEnoughMemory case invalidResource case timeOut } // RIGHT enum ErrorType: String { case error case warning } /// These are written to a logging service. Explicit values ensure they're consistent across binaries. // swiftlint:disable redundant_string_enum_value enum UserType: String { case owner = "owner" case manager = "manager" case member = "member" } // swiftlint:enable redundant_string_enum_value enum Planet: Int { case mercury case venus case earth case mars case jupiter case saturn case uranus case neptune } /// 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 }Copy the code
-
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) }Copy the code
// WRONG var results = [SomeType]() for element in input { if let result = transformThatReturnsAnOptional(element) { results.append(result) } } // RIGHT let results = input.compactMap { transformThatReturnsAnOptional($0) }Copy the code
-
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 }Copy the code
-
(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]) { ... }}Copy the code
-
(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 { // ... }Copy the code
-
(link) Never use the
default
case whenswitch
ing 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 anEnum { case .a: // Do something default: // Do something else. } // RIGHT switch anEnum { case .a: // Do something case .b, .c: // Do something else. }Copy the code
-
(link) Check for nil rather than using optional binding if you don’t need to use the value.
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() }Copy the code
File Organization
-
Why?
A standard organization method helps engineers more quickly determine which modules a file depends on.
Copyright © 2018 Airbnb. All rights reserved. // import DLSPrimitives import Constellation import Epoxy Import Foundation Copyright © 2018 Airbnb. All rights reserved. Import Constellation import DLSPrimitives import Epoxy import FoundationCopy the code
Exception:
@testable import
should be grouped after the regular import and separated by an empty line.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 EpoxyCopy the code
⬆ back to the top
Objective-C Interoperability
-
class PriceBreakdownViewController { private let acceptButton = UIButton() private func setUpAcceptButton() { acceptButton.addTarget( self, action: #selector(didTapAcceptButton), forControlEvents: .TouchUpInside) } @objc private func didTapAcceptButton() { // ... }}Copy the code
⬆ back to the top
Contributors
⬆ back to the 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.