STYLEGUIDE.md
February 18, 2022 · View on GitHub
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
- The official Swift API design guidelines are all unilaterally accepted for any public API and generally have good information for private or internal APIs. If you are not making a public API we do not require the same level of documentation, but we still recommend that you adhere to the naming conventions and general design guidelines.
- These rules should not fight Xcode's ^ + I indentation behavior.
- We strive to make rules lintable:
- If a rule changes the format of the code, it needs to be able to be reformatted automatically using SwiftLint.
- For rules that don't directly change the format of the code, we should have a lint rule that throws a warning.
- For rules that cannot be handled directly with SwiftLint we will strive to have our own linter (for example, file names).
How to read the guides
Each guide is broken into a few sections. Sections contain a list of guidelines. Each guideline starts with one of these words:
- DO guidelines describe practices that should always be followed. There will almost never be a valid reason to stray from them.
- DON’T guidelines are the converse: things that are almost never a good idea.
- PREFER guidelines are practices that you should follow. However, there may be circumstances where it makes sense to do otherwise. Just make sure you understand the full implications of ignoring the guideline when you do.
- AVOID guidelines are the dual to “prefer”: stuff you shouldn’t do but where there may be good reasons to on rare occasions.
- CONSIDER guidelines are practices that you might or might not want to follow, depending on circumstances, precedents, and your own preference.
Table of Contents
Xcode Formatting
-
DO Trim trailing whitespace in all lines.
-
DO Indent Case Statements in a Switch.
-
DO use 4 spaces over tabs. This is the Xcode default, and not something we will change.
-
DON'T use #imageLiteral or #colorLiteral (don't drag colors or images from xcode into code).
-
AVOID leaving compiler warnings unattended.
Every once in a while there's simply nothing reasonable you can do, because a generated file causes warnings. You also may deliberately use
#warning("")in your code to draw attention to something.
Naming
-
PREFER deprecation over removal for public APIs.
Why?
We want to give consumers of our code the opportunity to adapt to changes, outright removal means we'll constantly be breaking down-stream teams and that's a good way to frustrate developers.
We will do our best to support older methods, but we will also not hesitate to do something new and better if there's a new and better way of doing things. This is the same approach Apple tends to take.
-
DO name booleans like
isSpaceship,hasSpacesuit,areTermsAcceptedetc. This makes it clear that they are booleans and not other types. -
DO name event-handling 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() { // ... } } -
AVOID
*Controllerin 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.
-
PREFER prefixing types with modules over creating wrongly named classes.
// WRONG // in module 1 struct Appointment { var time: Date } // in module2 struct OtherAppointment { var time: Date var reason: String } // in calling code Appointment(time: Date()) OtherAppointment(time: Date(), reason: "sick") // RIGHT // in module 1 struct Appointment { var time: Date } // in module2 struct Appointment { var time: Date var reason: String } // in calling code Module1.Appointment(time: Date()) Module2.Appointment(time: Date(), reason: "sick") -
DO use US english spellings of words in code. This is to fit with the convention of english spellings that is already part of the swift standard libraries
-
PREFER short-hand type names.
// WRONG let x = Dictionary<AnyHashable, Any>() let y = Array<String>() // RIGHT let x = [AnyHashable: Any]() let y = [String]() -
PREFER delegate naming conventions similar to the Cocoa's.
Methods on delegate protocols and delegate-like protocols (such as data sources) are named using the linguistic syntax described below, which is inspired by Cocoa’s protocols.
The term “delegate’s source object” refers to the object that invokes methods on the delegate. For example, a UITableView is the source object that invokes methods on the UITableViewDelegate that is set as the view’s delegate property.
All methods take the delegate’s source object as the first argument.
For methods that take the delegate’s source object as their only argument:
If the method returns Void (such as those used to notify the delegate that an event has occurred), then the method’s base name is the delegate’s source type followed by an indicative verb phrase describing the event. The argument is unlabeled.
func scrollViewDidBeginScrolling(_ scrollView: UIScrollView)If the method returns Bool (such as those that make an assertion about the delegate’s source object itself), then the method’s name is the delegate’s source type followed by an indicative or conditional verb phrase describing the assertion. The argument is unlabeled.
func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> BoolIf the method returns some other value (such as those querying for information about a property of the delegate’s source object), then the method’s base name is a noun phrase describing the property being queried. The argument is labeled with a preposition or phrase with a trailing preposition that appropriately combines the noun phrase and the delegate’s source object.
func numberOfSections(in scrollView: UIScrollView) -> IntFor methods that take additional arguments after the delegate’s source object, the method’s base name is the delegate’s source type by itself and the first argument is unlabeled. Then:
If the method returns Void, the second argument is labeled with an indicative verb phrase describing the event that has the argument as its direct object or prepositional object, and any other arguments (if present) provide further context.
func tableView( _ tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAt indexPath: IndexPath)If the method returns Bool, the second argument is labeled with an indicative or conditional verb phrase that describes the return value in terms of the argument, and any other arguments (if present) provide further context.
func tableView( _ tableView: UITableView, shouldSpringLoadRowAt indexPath: IndexPath, with context: UISpringLoadedInteractionContext ) -> BoolIf the method returns some other value, the second argument is labeled with a noun phrase and trailing preposition that describes the return value in terms of the argument, and any other arguments (if present) provide further context.
func tableView( _ tableView: UITableView, heightForRowAt indexPath: IndexPath ) -> CGFloatApple’s documentation on delegates and data sources also contains some good general guidance about such names.
Style
-
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 } extension Container { static let `default` = Container() } // WRONG func getContainer() -> Container { return Container.default } // RIGHT func getContainer() -> Container { .default } -
PREFER type inference over explicit typing.
While similar to the rule on not including types when they can easily be inferred this is slightly different. When declaring properties prefer type inference over explicit typing.// WRONG let host: Host = Host() let arr: [String] = [] let dict: [AnyHashable: Any] = [:] // RIGHT let host = Host() let arr = [String]() let dict = [AnyHashable: Any]() -
DON'T use
selfunless 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() } } -
DO bind to
selfwhen upgrading from a weak reference.//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 = self else { return } // Do work completion() } } } -
PREFER
selfin argument capture lists.//WRONG class MyClass { func example { performSomeTask { self.thing = new self.otherThing = otherNew self.runSomeMethod() } } } // RIGHT class MyClass { func example { performSomeTask { [self] in thing = new otherThing = otherNew runSomeMethod() } } } -
DO 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]() -
DO 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) { // ... } -
DO 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 } -
DO omit
getkeyword on get-only computed properties.// WRONG var isEmpty: Bool { get { return count == 0 } } // RIGHT var isEmpty: Bool { count == 0 } -
DO have brackets on separate lines for multi-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.
// WRONG let rowContent = [listingUrgencyDatesRowContent(), listingUrgencyBookedRowContent(), listingUrgencyBookedShortRowContent()] // RIGHT let rowContent = [ listingUrgencyDatesRowContent(), listingUrgencyBookedRowContent(), listingUrgencyBookedShortRowContent(), ] -
PREFER constructors instead of Make() functions for NSRange and others.
// WRONG let range = NSMakeRange(10, 5) // RIGHT let range = NSRange(location: 10, length: 5) -
AVOID using backticks to escape reserved keywords.
Why?
Reserved keywords are well...reserved and usually very generic. If you find yourself using overly generic terms your code is likely less readable. A notable exception to this is when dot syntax clears up that ambiguity, for example:
Container.default, or even.defaultin context.// WRONG @IBOutlet var `switch`: UISwitch! // RIGHT @IBOutlet var notificationSwitch: UISwitch! extension Container { static let `default` = Container() } -
DO omit the
returnkeyword when not required by the language.// 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) } -
AVOID using semicolons after each statement in your code. It is only required if you wish to combine multiple statements on a single line.
class SomeClass { var name:String? private func fooy() { // WRONG let foo = "foo is a common term in programming"; // RIGHT let foo = "foo is a common term in programming" // RIGHT guard let x = name else { print("there is no name"); return } } } -
AVOID having more than 1 statement per line. An exception is
guardstatements when you may need a single statement before thereturn.class SomeClass { var name:String? private func fooy() { // WRONG let foo = "statement"; let bar = "bar" guard let x = name else { logFailure(); makeDebugBreadcrumbs(); completion(); return } // RIGHT let foo = "statement" let bar = "bar" guard let x = name else { logFailure() makeDebugBreadcrumbs() completion() return } } } -
DO break apart numeric literals by groups of three, just like you would use commas.
// WRONG let val = 1000 let val2 = 10000000 // RIGHT let val = 1_000 let val2 = 10_000_000 -
DON'T assign variables through a labeled tuple shuffle. let (label: varName, label2: varName2) = tuple
Assigning variables through a tuple pattern (sometimes referred to as a tuple shuffle) is only permitted if the left-hand side of the assignment is unlabeled.
// WRONG let (x: a, y: b) = (y: 4, x: 5.0) // RIGHT let (b, a) = (y: 4, x: 5.0) -
PREFER escape sequences over unicode.
For any character that has a special escape sequence (
\t,\n,\r,\",\',\\, and\0), that sequence is used rather than the equivalent Unicode (e.g.,\u{000a}) escape sequence. -
DON'T use invisible characters in code. for example zero-width space characters are illegal
-
PREFER multi-line strings to strings with multiple
\ncharacters. -
DO put
elsestatements at the end of the last line of aguardclause.// WRONG guard let a = optional, let b = otherOptional else { return } guard !isEmpty else { return } // RIGHT guard let a = optional, let b = otherOptional else { return } guard !isEmpty else { return } -
DO use the K&R brace style for braces.
// WRONG func foo() { // statements } // RIGHT func foo() { // statements } -
PREFER attributes on the same line as what they are attributing for properties, functions, and closures.
Why?
Because we naturally read lines of code as a consistent line, introducing whitespace makes it that much harder to understand the full context of the line you're trying to understand. This is the same reasoning used for preferring a return on the same line as the final parameter in a function declaration. A notable exception to this preference is the
@availableattribute which should always be on its own line.NOTE: This does not come at the cost of sanity. If you have an exceptionally large number of attributions then it might indicate that you've gotten overzealous, and it actually obscures your meaning, rather than add to it.
// WRONG @objc func thingExposedToObjectiveC() { // ... } // WRONG @ViewBuilder @discardableResult @MainActor @inlinable func betYouDoNotEvenKnowWhatThisFunctionDoesAnymore() { // ... } // WRONG @available(iOS 14.0, *) func doSomething() { // ... } // RIGHT @State var stateVar: Bool = true @discardableResult func thing() -> Bool { true } @MainActor @ViewBuilder func createViews() -> some View { // ... } // RIGHT @available(iOS 14.0, *) func doSomething() { // ... } -
PREFER attributes on a separate line from what they are attributing for classes, structs, and actors.
Why?
This is the convention set by Apple in the Swift attribute docs. A notable exception to this preference is the
@availableattribute which should always be on its own line.// WRONG @dynamicCallable class MyClass { // ... } // WRONG @MainActor struct MyStruct { // ... } // WRONG @available(iOS 14.0, *) @MainActor struct MyStruct { // ... } // RIGHT @dynamicCallable class MyClass { // ... } // RIGHT @available(iOS 14.0, *) @propertyWrapper struct MyPropertyWrapper { // ... } // RIGHT @available(iOS 13, *) @resultBuilder @requires_stored_property_inits class WorkflowBuilder { // ... }
Functions
-
PREFER omitting
Voidreturn types from function definitions.// WRONG func doSomething() -> Void { ... } // RIGHT func doSomething() { ... } -
DO separate long function declarations with line breaks before each argument label after the first, but not before the return signature. Put the open curly brace on the next line so the first executable line doesn't look like it's another parameter.
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 { 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 } // 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) throws -> String { populateUniverse() } } -
DO break each argument on long function invocations. Put the closing parenthesis on the last line of the invocation.
// WRONG universe.generateStars(at: location, count: 5, color: starColor, withAverageDistance: 4) // WRONG universe.generateStars(at: location, count: 5, color: starColor, withAverageDistance: 4 ) // RIGHT universe.generateStars(at: location, count: 5, color: starColor, withAverageDistance: 4) -
AVOID having 1-line functions unless they actually increase readability and trend towards english fluency.
Why?
Overly terse code is often difficult to reason about or modify.
// WRONG func didTapBookButton() { User.add(book: books[someIndex]) } // RIGHT var isEmpty: Bool { count == 0 }
Closures
-
PREFER
Voidreturn types over()in closure declarations. If you must specify aVoidreturn type in a function declaration, useVoidrather than()to improve readability.// WRONG func method(completion: () -> ()) { ... } // RIGHT func method(completion: () -> Void) { ... } -
DO name unused closure parameters as underscores (
_) unless none are used.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) } // RIGHT someAsyncThing() { print(\$2) } -
PREFER anonymous closure values when there are less than 2 arguments and it does not greatly increase cognitive complexity
Why?
Anonymous closure values ($0, $1, $2, etc...) can make it hard to reason able what you're dealing with barring some very specific circumstances. If you have more than 2 anonymous closure arguments you should name them to decrease ambiguity.
// WRONG someAsyncThing() { print(\$0) modify(\$1) if (\$2 == someValue.flatMap ({ \$0 })) { //wait which \$0????? } } // RIGHT someAsyncThing() { name, age, isWearingSunglasses in print(name) modify(age) if (isWearingSunglasses == someValue.flatMap ({ \$0 }).isEmpty) { } } // RIGHT [:].merging([:]) { \$1 } -
DO put a space around single-line closures.
// WRONG let evenSquares = numbers.filter {\$0 % 2 == 0}.map { \$0 * \$0 } // RIGHT let evenSquares = numbers.filter { \$0 % 2 == 0 }.map { \$0 * \$0 } -
DO use trailing closure syntax. If there are multiple trailing closure parameters, use the new syntax for multiple trailing closures available in Swift 5.x
class SomeClass { var name:String? private func fooy() { // WRONG UIView.animate(withDuration: 0.6, animations: { _ in self.view.alpha = 0}) { _ in self.view.removeFromSuperview() } // WRONG UIView.animate(withDuration: 0.6, animations: { _ in self.view.alpha = 0}, completion: { _ in self.view.removeFromSuperview() }) // RIGHT UIView.animate(withDuration: 0.3) { self.view.alpha = 0 } completion: { _ in self.view.removeFromSuperview() } } } -
AVOID multiple optional trailing closures.
Operators
-
DO put a single space around infix operators. 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) -
AVOID creating custom operators.
Why?
Custom operators can drastically decrease readability. While there are times when they can be beneficial, they should either follow other common language conventions (like how ~= is used for regex matching in many languages) or they should have a clear precedent inside the codebase (like a
%%postfix operator that has an x percent change of executing,if 10%%) -
CONSIDER overloading existing operators when your use of the operator is semantically equivalent to the existing uses in the standard library.
Overloading operators is permitted when your use of the operator is semantically equivalent to the existing uses in the standard library. Examples of permitted use cases are implementing the operator requirements for Equatable and Hashable, or defining a new Matrix type that supports arithmetic operations.
Enumerations
-
DO 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: ... } -
DO use Swift's automatic enum values unless they map to an external source. Unless the external source has a value type like String that will not cause issues when inserted in the middle. Add a comment explaining why explicit values are defined.
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, define the values explicitly and document what these values are mapping to. The exception to this is when the value type is like
Stringthat will not cause issues when inserted in the middle.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" } // These values are internal, so we should not have explicity defined the values. 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 } // These values come from a server, so we should have set them here explicitly to match those values. enum ErrorCode: Int { case notEnoughMemory case invalidResource case timeOut } // These values also come from a server, but they are of string type so we should have continued to use the automatic values. enum UserType: String { case owner = "owner" case manager = "manager" case member = "member" } // RIGHT enum ErrorType: String { case error case warning } // These values are internal, so we do not need to explicity define the values. enum Planet: Int { case mercury case venus case earth case mars case jupiter case saturn case uranus case neptune } // These values come from a server, so we set them here explicitly to match those values. enum ErrorCode: Int { case notEnoughMemory = 0 case invalidResource = 1 case timeOut = 2 } // These values also come from a server, but they are of string type so we can continue to use the automatic values. enum UserType: String { case owner case manager case member } -
DO put each
caseon its own line in anenum. The expectation to this is if none of the cases have associated values or raw values, all cases fit on a single line, and the cases do not need further documentation because their meanings are obvious from their names.// WRONG public enum Token { case comma, semicolon, identifier(String) } // RIGHT public enum Token { case comma case semicolon case identifier } -
DO declare the
enumasindirectwhen all cases must be indirect. Omit the keyword on individual cases.// WRONG public enum DependencyGraphNode { indirect case userDefined(dependencies: [DependencyGraphNode]) indirect case synthesized(dependencies: [DependencyGraphNode]) } // RIGHT public indirect enum DependencyGraphNode { case userDefined(dependencies: [DependencyGraphNode]) case synthesized(dependencies: [DependencyGraphNode]) } -
DO order
enumcases in a logical order. If there is no obvious logical ordering, use a lexicographical odering based on the cases' names.// WRONG // These are sorted lexicographically, but the meaningful groupings of related values has been lost. public enum HTTPStatus: Int { case badRequest = 400 case forbidden = 403 case internalServerError = 500 case notAuthorized = 401 case notFound = 404 case ok = 200 case paymentRequired = 402 } // RIGHT public enum HTTPStatus: Int { case ok = 200 case badRequest = 400 case notAuthorized = 401 case paymentRequired = 402 case forbidden = 403 case notFound = 404 case internalServerError = 500 }
Generics
-
DO use generic
whereclauses for constraints, and use the generic specialization for typing.Why?
We strive for code that delivers relevant information quickly. When reading the below examples from left to right, there is less cognitive load to provide the type of the specialization when the generic is introduced than to retain the generic, its uses, and then retroactively apply the type. It also creates a separation between specialization and restrictions when reading.
// WRONG class SpecializedGeneric<F, B, FB> where F: Foo, B: Bar, FB: FooBar, F.Input == FB.Input { init<T, S, O>(thing: T, stuff: S, other: O) where T: Thing, S: Stuff, O: Other, T.Output == String, O.Input == T.Output {} } // RIGHT class SpecializedGeneric<F: Foo, B: Bar, FB: FooBar> where F.Input == FB.Input { init<T: Thing, S: Stuff, O: Other>(thing: T, stuff: S, other: O) where T.Output == String, O.Input == T.Output {} } -
DO use meaningful names when specializing with generics.
Why?
Often times with generics we lazily write
Tand move on, in the right contexts a single letter generic is just fine however as you start getting into increasingly complex scenarios this drastically reduces readability.// WRONG class Bookshelf<T> { } func<T, U, V> doThing() { } // RIGHT class Bookshelf<ReadableContent> { } func<S: Sequence, P: SomeType & SomeProtocol, N: Numeric> doThing() { }
Patterns
Initializers
-
PREFER initializing properties at
inittime whenever possible, rather than using implicitly unwrapped optionals. A notable exception is UIViewController'sviewproperty.// WRONG class MyClass { var someValue: Int! init() { super.init() someValue = 5 } } // RIGHT class MyClass { var someValue: Int init() { someValue = 0 super.init() } } -
PREFER default values in property declarations over initializers setting values.
// WRONG class MyClass { var someValue: Int init() { someValue = 5 } } // RIGHT class MyClass { var someValue: Int = 5 init() { } } -
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 a factory if these things need to be done before an object is ready for use. -
AVOID
.initunless dealing with metatypes.// WRONG let x = MyType.init(arguments) let x: MyType = .init(arguments) // RIGHT let x = MyType(arguments) let type = lookupType(context) let x = type.init(arguments) let x = makeValue(factory: MyType.init) -
DON'T call
ExpressibleBy*Literalcompiler protocols directly.// WRONG struct Kilometers: ExpressibleByIntegerLiteral { init(integerLiteral value: Int) { // ... } } let k = Kilometers(integerLiteral: 10) // RIGHT struct Kilometers: ExpressibleByIntegerLiteral { init(integerLiteral value: Int) { // ... } } let k1: Kilometers = 10 // GOOD. let k2 = 10 as Kilometers // ALSO GOOD.
Method Complexity
-
DO extract complex property observers into methods. This reduces nestedness, separates side-effects from property declarations, and makes the usage of implicitly-passed parameters like
oldValueexplicit.// 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. } } -
CONSIDER Combine functional chains over completion handlers.
-
DO 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
guardto unwrap self for the duration of the callback.// WRONG class MyClass { func request(completion: () -> Void) { API.request() { [weak self] response in if let self = self { // Processing and side effects } completion() } } } // RIGHT class MyClass { func request(completion: () -> Void) { API.request() { [weak self] response in guard let self = self else { return } self.doSomething(with: self.property, response: response) completion() } } func doSomething(with nonOptionalParameter: SomeClass, response: SomeResponseClass) { // Processing and side effects } }
Control Flow
-
PREFER using
guardoveriffor preconditions.// WRONG class MyClass { var thingWeNeed: String? func doThings() { if let thing = thingWeNeed { // lets do something with the thing } } } // RIGHT class MyClass { var thingWeNeed: String? func doThings() { guard let thing = thingWeNeed else { return } // lets do something with the thing } } // WRONG class MyClass { var arr = [String]() func doThings() { if !arr.isEmpty { // lets do something with the thing } } } // RIGHT class MyClass { var arr = [String]() func doThings() { guard !arr.isEmpty else { return } // lets do something with the thing } } -
PREFER using
guardat the beginning of a scope.Why?
It's easier to reason about a block of code when all
guardstatements are grouped together at the top rather than intermixed with business logic. -
DO use the "Golden Path" rule. GOLDEN PATH RULE: When coding with conditionals, the left-hand margin of the code should be the "golden" or "happy" path. That is, don't nest if statements. Multiple return statements are OK. The guard statement is built for this.
// WRONG func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies { if let context = context { if let inputData = inputData { // use context and input to compute the frequencies // notice the "return" line is far to the right, this violates the 'left margin' idea. return frequencies } else { throw FFTError.noInputData } } else { throw FFTError.noContext } } // RIGHT func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies { guard let context = context else { throw FFTError.noContext } guard let inputData = inputData else { throw FFTError.noInputData } // use context and input to compute the frequencies // notice the return statement is as far left as it can be, this satisfies the golden path rule. return frequencies } -
DO write if/else statements starting with the happy path.
Why?
Your code should read as a declaration of intent. Starting with the happy path case makes your intent more immediately apparent.
NOTE: This does not conflict with the golden path rule for guards and early exits.
// WRONG if case .failure(let err) = result { // handle error } else { // thing the code should really do } switch result { case .failure(let err): throw err case .success: //thing the code should do } if thingThatProbablyWillNotHappen || thingThatLikelyWillHappen { } // RIGHT guard case .success = result else { throw err } // thing the code should do switch result { case .success: // thing the code should do case .failure(let err): throw err } if thingThatLikelyWillHappen || thingThatProbablyWillNotHappen { } -
DON'T use the
defaultcase whenswitching 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. } -
DON'T use nested ternaries.
Why?
Ternaries can be great, they actually serve a functional purpose over an
ifstatement because they are expressions, so you can assign them to a constant or return them as an expression. That being said if you find yourself nesting them you have gone too far.// WRONG result = a > b ? x = c > d ? c : d : y // RIGHT result = value != 0 ? x : y -
DON'T use multi-line ternaries.
A ternary is meant to be used for a very short conditional. If you find it cannot be reasonably expressed on one line then it should not be a ternary.
-
PREFER for where loops when the entirety of a for loop’s body would be a single if block testing a condition of the element.
// WRONG for item in collection { if item.hasProperty { // ... } } // RIGHT for item in collection where item.hasProperty { // ... } -
DO put a
varorletbefore each element in a pattern that is being matched.The let and var keywords are placed individually in front of each element in a pattern that is being matched. The shorthand version of let/var that precedes and distributes across the entire pattern is forbidden because it can introduce unexpected behavior if a value being matched in a pattern is itself a variable.
enum DataPoint { case unlabeled(Int) case labeled(String, Int) } let label = "goodbye" // WRONG // `label` is treated as a value here because it is not preceded by `let`, so // the pattern below matches only data points that have the label "goodbye". switch DataPoint.labeled("hello", 100) { case .labeled(label, let value): // ... } // RIGHT // Writing `let` before each individual binding clarifies that the intent is to // introduce a new binding (shadowing the local variable within the case) rather // than to match against the value of the local variable. Thus, this pattern // matches data points with any string label. switch DataPoint.labeled("hello", 100) { case .labeled(let label, let value): // ... }In the example below, if the author’s intention was to match using the value of the label variable above, that has been lost because let distributes across the entire pattern and thus shadows the variable with a binding that applies to any string value:
// WRONG switch DataPoint.labeled("hello", 100) { case let .labeled(label, value): // ... }Labels of tuple arguments and enum associated values are omitted when binding a value to a variable with the same name as the label.
// RIGHT enum BinaryTree<Element> { indirect case subtree(left: BinaryTree<Element>, right: BinaryTree<Element>) case leaf(element: Element) } switch treeNode { case .subtree(let left, let right): // ... case .leaf(let element): // ... }Including the labels adds noise that is redundant and lacking useful information:
// WRONG switch treeNode { case .subtree(left: let left, right: let right): // ... case .leaf(element: let element): // ... }
Access Control
-
PREFER the strictest possible access control. Prefer
publictoopenandprivatetofileprivateunless you need that behavior. -
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() { // ... } } -
DO use caseless
enums for organizingpublicorinternalconstants and functions into namespaces.- Avoid creating non-namespaced global constants and functions.
- Feel free to nest namespaces where it adds clarity.
privateglobals are permitted, since they are scoped to a single file and do not pollute the global namespace. Consider placing private globals in anenumnamespace to match the guidelines for other declaration types.
Why?
Caseless
enums work well as namespaces because they cannot be instantiated, which matches their intent.enum Environment { enum Earth { static let gravity = 9.8 } enum Moon { static let gravity = 1.6 } } -
DO default type methods to
static.Why?
If a method needs to be overridden, the author should opt into that functionality by using the
classkeyword instead.// WRONG class Fruit { class func eatFruits(_ fruits: [Fruit]) { ... } } // RIGHT class Fruit { static func eatFruits(_ fruits: [Fruit]) { ... } } -
DO default classes to
final.Why?
If a class needs to be overridden, the author should opt into that functionality by omitting the
finalkeyword.// WRONG class SettingsRepository { // ... } // RIGHT final class SettingsRepository { // ... } -
DO specify the access control for each declaration in an extension individually.
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() { } }
Optionals
-
PREFER throwing or optional intializers over optional properties that should have a value.
// WRONG struct Person { var firstName: String? // firstName should have a value, but won't necessarily if we just create a new person var lastName: String? // lastName may, or may not have a value based on culture } // RIGHT struct Person: Decodable { var firstName: String // firstName should have a value, if a REST API does not return it Decodable has a throwing intializer that will well...throw var lastName: String? // lastName may, or may not have a value based on culture, so it should remain optional } // STILL RIGHT struct Person: Decodable { var firstName: String // firstName should have a value var lastName: String? // lastName may, or may not have a value based on culture, so it should remain optional init?(_ dictionary: [String: Any]) { guard let firstName = dictionary["firstName"] as? String else { return nil } self.firstName = firstName lastName = dictionary["lastName"] as? String } } -
PREFER implicitly unwrapped optionals when a value can be safely assumed.
Why?
Implicitly unwrapped optionals aren't bad, contrary to some opinions. While Swift does give us a lot of safety implicitly unwrapped optionals merely mean "this likely has a value when you need it". You can still treat them like optionals, unwrap them, use optional chaining syntax. Or you can treat them as if they have an expected value. Use them where appropriate and write adequate tests.
// WRONG class ViewController { @IBOutlet var textField: UITextField? } // RIGHT class ViewController { @IBOutlet var textField: UITextField! } // STILL RIGHT class ViewModel { // where we have a test proving that SomeAPI is registered in the container in the app lifecycle @DependencyInjected var someAPI: SomeAPI! } -
DO 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() } -
AVOID force unwrapping optionals.
Why?
Remember, implicitly unwrapped optionals are a thing if you can safely assume a value. A core component to Swift is its safety, don't ruin that safety just for conveniences sake.
// WRONG let index = [].index(of: "hello")! // This is NOT thread safe tableView?.dataSource?.tableView?(tableView!, commit: .delete, forRowAt: expectedIndexPath) // RIGHT if let index = [].index(of: "hello") { // use index } if let tableView = tableView { // thread safe tableView.dataSource?.tableView?(tableView, commit: .delete, forRowAt: expectedIndexPath) }
Immutability
-
PREFER immutable values whenever possible. Use
mapandcompactMapinstead of appending to a new collection. Usefilterinstead 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) } -
PREFER structs over classes.
Why?
This follows the previous rule of preferring immutability. Structs explicitly mark mutating members as mutating, they favor composition over inheritance, they have synthesized initializers, and they are copy-on-write meaning that unintended side-effects from modifying a reference are less prevalent.
// WRONG class User: Codable { var username: String var email: String var dateOfBirth: Date init(username: String, email: String, dateOfBirth: Date) { self.username = username self.email = email self.dateOfBirth = dateOfBirth } } // RIGHT struct User: Codable { var username: String var email: String var dateOfBirth: Date }
Protocols
-
DO use
AnyObjectinstead ofclassin protocol definitions.Why?
SE-0156, which introduced support for using the
AnyObjectkeyword as a protocol constraint, recommends preferringAnyObjectoverclass:This proposal merges the concepts of
classandAnyObject, which now have the same meaning: they represent an existential for classes. To get rid of the duplication, we suggest only keepingAnyObjectaround. To reduce source-breakage to a minimum,classcould be redefined astypealias class = AnyObjectand give a deprecation warning on class for the first version of Swift this proposal is implemented in. Later,classcould be removed in a subsequent version of Swift.// WRONG protocol Foo: class {} // RIGHT protocol Foo: AnyObject {} -
PREFER one conformance per extension or type declaration.
Why?
Choosing to have multiple conformances in a type means it is more difficult to extract code later, it also makes your type harder to reason about.
// WRONG class MyViewController: UIViewController, UITableViewDataSource, UIScrollViewDelegate { // all methods } // RIGHT class MyViewController: UIViewController { // class stuff here } extension MyViewController: UITableViewDataSource { // table view data source methods } extension MyViewController: UIScrollViewDelegate { // scroll view delegate methods }
Type Erasure
-
PREFER a wrapped
Anytype over a subclass for type erasure.Why?
Subclassing based type erasure inherantly exposes implemention details to the end user that should not be exposed. Wrapping our types in the same way that Combine and SwiftUI do type erasure, ensures that the final exposed API is clean and correct for the end user to consume.
// WRONG public protocol AnyStuff { // cannot make internal func erasedFoo(_ x: Any) } // End user should not see erasedFoo but can public protocol Stuff: AnyStuff { associatedtype T func foo(_ x: T) } extension Stuff { func foo(_ x: T) { erasedFoo(x) } } // RIGHT class AnyStuffBase { func foo(_ x: Any) { fatalError() } } class AnyStuffStorage<S: Stuff>: AnyStuffBase { let holder: S init(_ Stuff: S) { holder = Stuff } override func foo(_ x: Any) { holder.foo(x as! S.T) } } public class AnyStuff { private let base: AnyStuffBase public init<S: Stuff>(_ stuff: S) { base = AnyStuffStorage(stuff) } public func foo(_ x: Any) { base.foo(x) } } public protocol Stuff { associatedtype T func foo(_ x: T) }
File Organization
-
DO limit empty vertical whitespace to one line. Favor the following formatting guidelines over whitespace of varying heights to divide files into logical groupings.
-
DO end files with a newline.
-
DO place content in the correct order within a file based on impact to the codebase, grouped by similarity. This allows a new reader of your code to more easily find what they are looking for.
Why?
Things that can potentially affect more of the codebase go first. Similarly, related things (like the same property wrappers) take sorting precedence over the below list. e.g.
@EnvironmentObjectcomes before@State;publiccomes beforeprivate;@MainActorcomes before@ViewBuilder;typealias's are potentially more affecting and thus go at the start.Still extract as necessary into extensions, but ensure those extensions also conform to the rule.
The order:
- Typealiases
- Class properties
- Static properties
- Instance properties
- Class methods
- Static methods
- Initializers
- Instance methods
- Nested types placed into extensions
public struct ContentView: View { // typealias goes first as it impacts the rest of this struct and all other objects. // Also these are candidates for extraction into an extension public typealias CustomTypeAlias1 = String typealias CustomTypeAlias2 = String private typealias CustomTypeAlias3 = String // EnvironmentObject impacts this view and can impact all views beyond this one. @EnvironmentObject private var appModel: AppModel // ObservedObject impacts more than State and thus goes earlier even though hikeResult is internal. @ObservedObject private var viewModel = ViewModel() @State var hikesResult: Result<[Hike], API.HikesService.Error>? // inspection impacts everything in body so it goes before body let inspection = Inspection<Self>() var body: some View { Empty() } // Candidates for extraction into an extension public class func classMethods1() {} class func classMethods2() {} private class func classMethods3() {} // Candidates for extraction into an extension public static func staticMethods1() {} static func staticMethods2() {} private static func staticMethods3() {} public init() init() init?() private init() public func instanceMethod1() {} func instanceMethod2() {} private func instanceMethod3() {} } -
DO name files after the primary type in the file.
-
DO use utf8 string encoding for all your files.
-
DO name extension files in the style
*Extensions. -
PREFER one non-nested type per file.
-
DO use extensions to break apart code into logical groups/files.
-
PREFER grouping by logical types. If there are extensive related models, extensions, classes, other files, then group them together in a group named after their logical meaning.
-
DO sort files by type using the builtin sorting in Xcode File Explorer.
-
DO group imports according to the correct order with a line break between groups. Alphabetizing after grouping is not required.
The correct order:
- Apple imports
- 3rd party imports
- 1st party imports
@testableand/or import under test
import Foundation import Combine import Cuckoo import Swinject import ViewInspector import SwiftCurrentCore @testable import SwiftCurrentSwiftUI -
DO put computed properties and properties with property observers at the end of the set of declarations of the same kind. (e.g. instance properties.)
// 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") } } -
DON'T keep dead code around.
On the surface this may seem obvious but dead code takes many forms. File templates can really hurt you here because when you say create a new UIViewController it has methods that do nothing but call
superthat counts as dead code and clutters up the codebase needlessly.// WRONG override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } override func numberOfSections(in tableView: UITableView) -> Int { // #warning Incomplete implementation, return the number of sections return 1 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // #warning Incomplete implementation, return the number of rows return Database.contacts.count } // RIGHT override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return Database.contacts.count } -
AVOID excessive comments.
We are big believers in self documenting code. Public API deserve documentation comments in all their glory and you should follow our guide on that. When dealing with internal code comments should be reserved for times when meaning is genuinely unclear or non-intuitive. This tends to only be true when you cannot extract to a private method and increase readability.
// WRONG // calculates sum of all the ages of all the users func allUserAges() { users.reduce(0) { \$0.ageInYears + \$1.ageInYears } } // RIGHT /// max: Returns the maximum value in the comparable LinkedList /// - Returns: The maximum concrete value in the LinkedList or nil if there is none public func max() -> Value? { guard var m = first?.value else { return nil } forEach { m = Swift.max(m, \$0.value) } return m } // STILL RIGHT // Implementation of Luhn's Algorithm in Swift // From the rightmost digit of your card number, double every other digit. // If the doubled digit is larger than 9 (ex. 8 * 2 = 16), subtract 9 from the product (16 – 9 = 7). // Sum the digits. // If there is no remainder after dividing by 10 (sum % 10 == 0), the card is valid. var isValidCreditCardNumber: Bool { let digits = reversed().compactMap { Int(String(\$0)) } guard digits.count == count, digits.count > 0 else { return false } let sum = digits.enumerated().reduce(0) { return \$0 + (((\$1.offset % 2) == 0) ? \$1.element : (2 * \$1.element - 1) % 9 + 1) } return sum % 10 == 0 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // #warning Incomplete implementation, return the number of rows return Database.contacts.count } // RIGHT override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return Database.contacts.count }
Objective-C Interoperability
-
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
@objcon 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() { // ... } }
Attribution:
- This styleguide was forked from the AirBnB styleguide. Thanks AirBnB!
- Inspiration on format also came from the effective dart docs, Thanks Google!
- Parts of the styleguide also inspired by the Google Swift styleguide, Thanks Google!
- Parts of the styleguide also inspired by The Official raywenderlich.com Swift Style Guide, Thanks Ray Wenderlich!