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.
- 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.
- We list during all pull requests Danger Swift)
- Exceptions to these rules should be rare and heavily justified.
- Xcode Formatting
- Naming
- Style
- Patterns
- File Organization
- Objective-C Interoperability
- Documentation Comments
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.
-
(link) Use 2 spaces to indent lines.
-
(link) Trim trailing whitespace in all lines.
-
(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
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 }
- 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 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 */)
-
(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.
-
(link) Don't include types where they can be easily inferred.
// 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.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) 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(), ]
-
(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) 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)
-
(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.width
-
(link) Place the colon immediately after an identifier, followed by a space.
// 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.
// WRONG func doSomething()->String { // ... } // RIGHT func doSomething() -> String { // ... }
// WRONG func doSomething(completion: ()->Void) { // ... } // RIGHT func doSomething(completion: () -> Void) { // ... }
-
(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 }
-
(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: ... }
-
(link) Omit
Void
return types from function definitions.// WRONG func doSomething() -> Void { ... } // RIGHT func doSomething() { ... }
-
(link) Favor
Void
return types over()
in closure declarations. If you must specify aVoid
return type in a function declaration, useVoid
rather than()
to improve readability.// WRONG func method(completion: () -> ()) { ... } // RIGHT func method(completion: () -> Void) { ... }
-
(link) Name unused closure parameters as underscores (
_
).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) 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 }
-
(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
).// 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) Prefer initializing properties at
init
time whenever possible, rather than using implicitly unwrapped optionals. A notable exception is UIViewController'sview
property.// WRONG class MyClass: NSObject { init() { super.init() someValue = 5 } var someValue: Int! } // RIGHT class MyClass: NSObject { 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 astart()
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 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 } }
-
(link) Prefer using
guard
at the beginning of a scope. -
(link) Access control should be at the strictest level possible. Prefer
public
toopen
andprivate
tofileprivate
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() { // ... } }
-
(link) Prefer putting constants in the top level of a file if they are
private
. If they arepublic
orinternal
, define them as static properties, for namespacing purposes.private let privateValue = "secret" public class MyClass { public static let publicValue = "something" func doSomething() { print(privateValue) print(MyClass.publicValue) } }
-
(link) Use caseless
enum
s for organizingpublic
orinternal
constants and functions into namespaces. Avoid creating non-namespaced global constants and functions. Feel free to nest namespaces where it adds clarity. -
(link) Use Swift's automatic enum values unless they map to an external source. Add a comment explaining why explicit values are defined.
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 }
-
(link) Use optionals only when they have semantic meaning.
-
(link) Prefer immutable values whenever possible. Use
map
andcompactMap
instead of appending to a new collection. Usefilter
instead of removing elements from a mutable collection.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) 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 aprecondition
method orfatalError()
. This strikes a balance between crashing and providing insight into unexpected conditions in the wild. Only preferfatalError
over aprecondition
method when the failure message is dynamic, since aprecondition
method won't report the message in the crash report.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
. -
(link) Default classes to
final
. -
(link) Never use the
default
case whenswitch
ing over an enum.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. }
-
(link) Check for nil rather than using optional binding if you don't need to use the value.
-
(link) Alphabetize module imports at the top of the file a single line below the last line of the header comments. Do not add additional line breaks between import statements.
A standard organization method helps engineers more quickly determine which modules a file depends on.
// WRONG // Copyright © 2022 Shortcut Scandinavia Apps AB. All rights reserved. // import DLSPrimitives import Constellation import Epoxy import Foundation //RIGHT // Copyright © 2022 Shortcut Scandinavia Apps AB. 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 © 2022 Shortcut Scandinavia Apps AB. All rights reserved. // import DLSPrimitives @testable import Epoxy import Foundation import Nimble import Quick //RIGHT // Copyright © 2022 Shortcut Scandinavia Apps AB. All rights reserved. // import DLSPrimitives import Foundation import Nimble import Quick @testable import Epoxy
-
(link) Limit empty vertical whitespace to one line. Favor the following formatting guidelines over whitespace of varying heights to divide files into logical groupings.
-
(link) Files should end in a newline.
-
(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 func didTapAcceptButton() { // ... } }
General Format Documentation comments are written using the format where each line is preceded by a triple slash (///). Javadoc-style block comments (/** ... */) are not permitted.
/// Returns the numeric value of the given digit represented as a Unicode scalar.
///
/// - Parameters:
/// - digit: The Unicode scalar whose numeric value should be returned.
/// - radix: The radix, between 2 and 36, used to compute the numeric value.
/// - Returns: The numeric value of the scalar.
func numericValue(of digit: UnicodeScalar, radix: Int = 10) -> Int {
// ...
}
Documentation comments begin with a brief single-sentence summary that describes the declaration. (This sentence may span multiple lines, but if it spans too many lines, the author should consider whether the summary can be simplified and details moved to a new paragraph.)
If more detail is needed than can be stated in the summary, additional paragraphs (each separated by a blank line) are added after it.
The single-sentence summary is not necessarily a complete sentence; for example, method summaries are generally written as verb phrases without “this method […]” because it is already implied as the subject and writing it out would be redundant. Likewise, properties are often written as noun phrases without “this property is […]”. In any case, however, they are still terminated with a period.
/// The background color of the view.
var backgroundColor: UIColor
/// Returns the sum of the numbers in the given array.
///
/// - Parameter numbers: The numbers to sum.
/// - Returns: The sum of the numbers.
func sum(_ numbers: [Int]) -> Int {
// ...
}
Clearly document the parameters, return value, and thrown errors of functions using the Parameter(s), Returns, and Throws tags, in that order. None ever appears with an empty description. When a description does not fit on a single line, continuation lines are indented 2 spaces in from the position of the hyphen starting the tag.
The recommended way to write documentation comments in Xcode is to place the text cursor on the declaration and press Command + Option + /. This will automatically generate the correct format with placeholders to be filled in.
Parameter(s) and Returns tags may be omitted only if the single-sentence brief summary fully describes the meaning of those items and including the tags would only repeat what has already been said.
The content following the Parameter(s), Returns, and Throws tags should be terminated with a period, even when they are phrases instead of complete sentences.
When a method takes a single argument, the singular inline form of the Parameter tag is used. When a method takes multiple arguments, the grouped plural form Parameters is used and each argument is written as an item in a nested list with only its name as the tag.
/// Returns the output generated by executing a command.
///
/// - Parameter command: The command to execute in the shell environment.
/// - Returns: A string containing the contents of the invoked process's
/// standard output.
func execute(command: String) -> String {
// ...
}
/// Returns the output generated by executing a command with the given string
/// used as standard input.
///
/// - Parameters:
/// - command: The command to execute in the shell environment.
/// - stdin: The string to use as standard input.
/// - Returns: A string containing the contents of the invoked process's
/// standard output.
func execute(command: String, stdin: String) -> String {
// ...
}
Use of Apple’s markup format is strongly encouraged to add rich formatting to documentation. Such markup helps to differentiate symbolic references (like parameter names) from descriptive text in comments and is rendered by Xcode and other documentation generation tools. Some examples of frequently used directives are listed below.
Paragraphs are separated using a single line that starts with /// and is otherwise blank.
Single asterisks and single underscores surround text that should be rendered in italic/oblique type.
Double asterisks and double underscores surround text that should be rendered in boldface.
Names of symbols or inline code are surrounded in backticks
.
Multi-line code (such as example usage) is denoted by placing three backticks ( `` `) on the lines before and after the code block.
At a minimum, documentation comments are present for every open or public declaration, and every open or public member of such a declaration, with specific exceptions noted below:
-
Individual cases of an enum often are not documented if their meaning is self-explanatory from their name. Cases with associated values, however, should document what those values mean if it is not obvious.
-
A documentation comment is not always present on a declaration that overrides a supertype declaration or implements a protocol requirement, or on a declaration that provides the default implementation of a protocol requirement in an extension.
It is acceptable to document an overridden declaration to describe new behavior from the declaration that it overrides. In no case should the documentation for the override be a mere copy of the base declaration’s documentation.
-
A documentation comment is not always present on test classes and test methods. However, they can be useful for functional test classes and for helper classes/methods shared by multiple tests.
-
A documentation comment is not always present on an extension declaration (that is, the extension itself). You may choose to add one if it help clarify the purpose of the extension, but avoid meaningless or misleading comments.
In the following example, the comment is just repetition of what is already obvious from the source code:
/// WRONG
/// Add `Equatable` conformance.
extension MyType: Equatable {
// ...
}
The next example is more subtle, but it is an example of documentation that is not scalable because the extension or the conformance could be updated in the future. Consider that the type may be made Comparable at the time of that writing in order to sort the values, but that is not the only possible use of that conformance and client code could use it for other purposes in the future.
/// WRONG
/// Make `Candidate` comparable so that they can be sorted.
extension Candidate: Comparable {
// ...
}
In general, if you find yourself writing documentation that simply repeats information that is obvious from the source and sugaring it with words like “a representation of,” then leave the comment out entirely.
However, it is not appropriate to cite this exception to justify omitting relevant information that a typical reader might need to know. For example, for a property named canonicalName, don’t omit its documentation (with the rationale that it would only say /// The canonical name.) if a typical reader may have no idea what the term “canonical name” means in that context. Use the documentation as an opportunity to define the term.
Utilize Jazzy to generate our documentation. To install:
[sudo] gem install jazzy
After getting it installed:
Run jazzy
from your command line. Run jazzy -h
for a list of additional options.
If your Swift module is the first thing to build, and it builds fine when running xcodebuild
without any arguments from the root of your project, then just running jazzy
(without any arguments) from the root of your project should succeed too!
You can set options for your project’s documentation in a configuration file, .jazzy.yaml
by default. For a detailed explanation and an exhaustive list of all available options, run jazzy --help config
.