This guide is to be used with Swift 5.10.
It is based on Kodeco and Google Swift style guides. We introduced further tweaks to accomodate our needs and practices.
A large amount of the rules here are enforced by swiftlint, but not all. Some are more like guidelines rather than hard rules.
All rules are intended for production code. Test and preview code allows for somewhat different, less strict practices. Those are not described in this document.
- Naming
- Code Organization
- Spacing
- Line-wrapping
- Classes and Structures
- Closure Expressions
- Control Flow
- Access Control
- Optionals
- Misc
Use the Swift naming conventions described in the API Design Guidelines.
Some additonal notes:
- prefer clarity over brevity
- avoid overly generic names for types of specific funtionality
- prefer US English spelling to match Apple's API
(color
instead ofcolour
,favorites
instead offavourites
, etc.)
When naming a type with a specific functionality consider prefixing it with the domain in which it is used. (like CalendarEventDetailsScreen
instead of EventDetailsScreen
or DetailsScreen
)
Do not abreviate prefixes. There are few specific exceptions to this rule (API
, CD
, ...), but prefer not to introduce more.
Consider nesting helper types as a form of namespacing. At the moment this can not be done for protocols.
When namespacing constants, prefer to add them to enums as static let
type properties. For namespacing purposes use enums instead of structs.
Use prefixes like is
, can
, has
etc. in bool properties or methods.
- Preferred:
var isEnabled: Bool
var isSubtitleVisible: Bool
func updateScreen(hasContent: Bool)
- Not Preferred:
var enabled: Bool
var subtitleVisible: Bool
var visibleSubtitle: Bool
func updateScreen(content: Bool)
Try to avoid double negation.
- Preferred:
var isVisible: Bool
if isEditable { ... }
- Not Preferred:
var isNotHidden: Bool
if !isUneditable { ... }
if !isNotEditable { ... }
Avoid all-capitalized acronyms.
Exceptions are some type name prefixes, but we use only a few specific prefixes like this (API
, CD
, ...).
- Preferred:
func handleHttpError(_ error: HttpError)
struct APICalendarEvent { }
- Not Preferred:
func handleHTTPError(_ error: HTTPError)
Generic type parameters should be descriptive, upper camel case names. When a type name doesn't have a meaningful relationship or role, use a traditional single uppercase letter such as T
, U
, or V
.
- Preferred:
struct Stack<Element> { ... }
func write<Target: OutputStream>(to target: inout Target)
func swap<T>(_ a: inout T, _ b: inout T)
- Not Preferred:
struct Stack<T> { ... }
func write<target: OutputStream>(to target: inout target)
func swap<Thing>(_ a: inout Thing, _ b: inout Thing)
Most swift source files should contain the following:
- One (primary) type, which matches the file name.
- Closely related code, like its extensions, helper types, private helper extensions, nested types, etc.
If a helper is non-private and not small enough or not trivial, it's preferred to put it in its own file. No hard rule here, use your best judgement.
If a helper type is meaningful on its own and is reused elsewhere, extract it to its own file. This is usually needed when a helper component was initially private, but it's being reused elsewhere. In those cases don't just remove private
, but consider relocating the component.
Larger extensions can also be moved to their own file, named like PrimaryType+TopicOfExtension
or PrimaryType+ProtocolTheExtensionConfromsTo
.
Using extensions to organize code inside a file is optional. Using just marks to group methods is also fine. Use your best judgement.
When using extensions for organization, prefer to preceed them with a // MARK: -
comment.
Protocols intended only for DI should be defined right before their primary conforming type, in the same file.
Protocols with multiple conforming types should usually go into their own file and each type also into their own. In simple cases it's also okay to put the protocol and all the conforming types into one file. Use your best judgement here.
If a protocol is very simple or conformance is generated (eg.: Codable
), prefer adding conformance inline at the type definition.
If a protocol has multiple members or it is more complex, prefer adding a separate extension for the protocol methods.
Prefer to use // MARK: -
comments to keep things well-organized. They can be used before extensions or to group methods or some properties. Do not omit the -
.
Always put a blank line before a mark. If a mark groups multiple methods or types, put a blank line after it.
Be consistent within a file. If you use marks, make sure the groups are "closed". Add a mark after the group ends (unless it's the end of file), preferably with a mark of the next group. If there is no meaningful next group use a closing mark: // MARK: Group name -
(note the -
after the name, not before as usual).
Import only the modules a source file requires. For example, don't import UIKit
when importing Foundation
will suffice. Likewise, don't import Foundation
if you must import UIKit
.
Prefer to sort imports alphabetically.
- Preferred:
import UIKit
var view: UIView
var deviceModels: [String]
- Preferred:
import Foundation
var deviceModels: [String]
- Not Preferred:
import UIKit
import Foundation
var view: UIView
var deviceModels: [String]
- Not Preferred:
import UIKit
var deviceModels: [String]
Indent using 4 spaces rather than tabs. Be sure to set Xcode preferences accordingly.
Do not leave trailing horizontal whitespace at end of line. Not even when it is an otherwise empty line. Be sure to set Xcode preferences accordingly.
At the end of file keep exactly one blank line.
When you need to put a space between expressions always use a single space.
Colons always have no space on the left and one space on the right. Exceptions are the ternary operator ? :
, empty dictionary [:]
and #selector
syntax addTarget(_:action:)
.
- Preferred:
class TestDatabase: Database {
var data: [String: CGFloat] = ["A": 1.2, "B": 3.2]
}
- Not Preferred:
class TestDatabase : Database {
var data :[String:CGFloat] = ["A" : 1.2, "B":3.2]
}
Refer to https://google.github.io/swift/#horizontal-whitespace, except the paragraph about spaces around //
.
Do not align type names, default values, etc. horizontally.
Prefer not to create columns when defining arrays, unless it really makes them more readable. Keep in mind those alignments will need extra maintenance when code changes in the future.
Feel free to group code logically with blank lines within a block of code, but be consistent, at least within a file.
Always use only a single blank line when you want to separate groups of code.
Always put a blank line between methods, types, extensions. This is optional between methods in protocol definitions.
Do not put a blank line after a codeblock's opening delimiter (eg.: (
, {
, [
), unless it's a type. It is okay to put a blank line after a type's opening brace. Prefer to not use it for small helper types, though.
Do not put a blank line before a closing delimiter (eg.: )
, }
, ]
) which is on its own line. If you really want to separate an else
put the line break between the }
and the else
.
- Method braces and other braces (
if
/else
/switch
/while
etc.) always open on the same line as the statement but close on a new line.
- Preferred:
if user.isHappy {
// Do something
} else {
// Do something else
}
- Not Preferred:
if user.isHappy
{
// Do something
}
else {
// Do something else
}
Aim to keep a column limit of 120
characters. It's recommended to set Xcode preferences accordingly.
Exceptions:
- Lines where obeying the column limit is not possible without breaking a meaningful unit of text that should not be broken (for example, a long URL in a comment, long localizable string).
- Test method names.
- Code generated by another tool.
Keep short function declarations on one line including the opening brace:
func reticulateSplines(spline: [Double]) -> Bool {
// reticulate code goes here
}
For functions with long signatures, put each parameter on a new line and add an extra indent on subsequent lines:
- Preferred:
func reticulateSplines(
spline: [Double],
adjustmentFactor: Double,
translateConstant: Int,
comment: String
) -> Bool {
// reticulate code goes here
}
- Not Preferred:
func reticulateSplines(spline: [Double],
adjustmentFactor: Double,
translateConstant: Int,
comment: String) -> Bool {
// reticulate code goes here
}
This is also supported by Xcode: with the cursor on a parameter press CTRL-M
to wrap and indent.
Mirror the style of function declarations at call sites. Calls that fit on a single line should be written as such:
let success = reticulateSplines(splines)
If the call site must be wrapped, put each parameter on a new line, indented one additional level, and put the closing parentheses on a new line as well:
- Preferred:
let success = reticulateSplines(
spline: splines,
adjustmentFactor: 1.3,
translateConstant: 2,
comment: "normalize the display"
)
- Not Preferred:
let success = reticulateSplines(spline: splines,
adjustmentFactor: 1.3,
translateConstant: 2,
comment: "normalize the display")
For conciseness, avoid using self
since Swift does not require it to access an object's properties or invoke its methods.
Use self
only when required by the compiler (in @escaping
closures, or in initializers to disambiguate properties from parameters). In other words, if it compiles without self
then omit it.
In initializers where some properties require self
and some don't (which have no matching parameter name) it's okay to use self
for all of them, for consistency.
Prefer not to use self
in closures after guard let self
.
Always use let
instead of var
if the value of the variable will not change. A good technique is to define everything using let
and only change it to var
if the compiler complains!
For conciseness, if a computed property is read-only, omit the get
clause. The get
clause is required only when a set
clause is provided.
- Preferred:
var diameter: Double {
radius * 2
}
- Not Preferred:
var diameter: Double {
get {
radius * 2
}
}
If the initialization of a lazy property is just a few lines, prefer to use an immediately called closure { }()
.
If it is longer or more complex consider extracting that code to a private factory method.
Prefer to define constants inside the type which uses them instead of using global constants, even if they are private.
Consider namespacing constants under a nested enum if you define more of them.
struct FixedSizeButton {
private enum Size {
static let width: CGFloat = 42
static let height: CGFloat = 24
}
private enum Padding {
static let horizontal: CGFloat = 8
static let vertical: CGFloat = 4
}
...
}
Marking classes or members as final
is preferred but not required.
Give the closure parameters descriptive names. Use shorthand syntax ($0
, $1
) only when the purpose of the parameter is clear.
Do not put parentheses around closure parameters, unless they are a tuple.
Use trailing closure syntax only if there's a single closure expression parameter at the end of the argument list. Prefer to not use it if the purpose of the parameter is not clear at the call site.
Do not leave empty parentheses after the function name, when a function called with trailing closure syntax takes no other arguments.
When using multiple closures as parameters, make sure to follow the line break rules. This allows for clear identation.
Exception: SwiftUI code may follow different rules.
- Preferred:
UIView.animate(withDuration: 1.0) {
self.myView.alpha = 0
}
UIView.animate(
withDuration: 1.0,
animations: {
self.myView.alpha = 0
},
completion: { finished in
self.myView.removeFromSuperview()
}
)
let squares = [1, 2, 3].map { $0 * $0 }
- Not Preferred:
UIView.animate(
withDuration: 1.0,
animations: {
self.myView.alpha = 0
}, completion: { finished in
self.myView.removeFromSuperview()
}
)
UIView.animate(withDuration: 1.0, animations: {
self.myView.alpha = 0
}, completion: { finished in
self.myView.removeFromSuperview()
})
UIView.animate(withDuration: 1.0, animations: {
self.myView.alpha = 0
}) { finished in
self.myView.removeFromSuperview()
}
let squares = [1, 2, 3].map() { $0 * $0 }
It is okay to write simpler closures on a single line. In that case leave one space inside the braces. When not using a trailing syntax do not leave spaces between the parentheses and braces.
- Preferred:
let value = numbers.map { $0 * 2 }
if let value = numbers.map({ $0 * 2 }) { ... }
- Not Preferred:
let value = numbers.map {$0 * 2}
let value = numbers.map{ $0 * 2 }
if let value = numbers.map({$0 * 2}) { ... }
Chained methods using trailing closures should be clear and easy to read in context. Decisions on spacing, line breaks, and when to use named versus anonymous arguments is left to the discretion of the author. Examples:
let value = numbers.map { $0 * 2 }.filter { $0 % 3 == 0 }.index(of: 90)
let value = numbers
.map { $0 * 2 }
.filter { $0 > 50 }
.map { $0 + 10 }
Consider using keypaths instead of closure bodies if it helps clarity. But do not overuse it for the sake of brevity.
let names = people.map(\.name)
When multiple conditions are wrapped:
- Keep the first condition on the same line as the
if
keyword. - Left align conditions under each other. (Xcode does this by default)
- The opening brace should be on the same line as the last condition.
Prefer to put a blank line after a guard
statement.
When multiple conditions are wrapped:
- Keep the first condition on the same line as the
guard
keyword. - Left align conditions under each other. (Xcode does this by default)
- The
else {
should be kept together either on the same line as the last condition or on a new line.
If the else
clause is a simple return (like return
, return nil
, return false
, etc.) and it doesn't cause wrapping, prefer the compact form like else { return }
.
If the return value is significant, not trivial or you want to ephasize it, do not use the compact form.
When multiple conditions are wrapped, do not use the compact form on the same line as the last condition. It's okay to use it on a new line.
In general, prefer to use guard
for early exit instead of if
with a return
.
When the condition would be more natural using an if
, it's okay to use it instead. (i.e.: to avoid double negation or to simplify the condition)
Prefer to put a blank line after the if
statement in this case.
Feel free to use the ternary operator ?:
for assignments and returns.
Prefer to evaluate only a single condition and to provide simple results. Prefer using if-else
statement if any of the clauses needs to be wrapped or if it's complex.
When wrapping the operator's clauses, prefer to add an extra indent. (This is against current Xcode auto-indentation, but helps readability.)
Avoid nesting ternary operators.
It's okay to add parentheses around the condition if it helps clarity.
- Preferred:
result = value != 0 ? x : y
result = value != 0
? someVeryLongResultWhichWouldCauseReachingTheColumnLimit
: anotherVeryLongResultWhichWouldCauseReachingTheColumnLimit
result = (value == 0) ? x : y
- Not Preferred:
result = a > b ? x = c > d ? c : d : y
result = value != 0
? doSomething(
parameter1: 1,
parameter2: 2
)
: doAnoterThing()
Cases should be indented at the same level as the switch
keyword. (Xcode does this by default)
The body of a case could be either kept on the same line or put on a new line.
Avoid using fallthrough
. Put multiple cases together instead.
Multiple cases could be listed either on the same line or on new lines.
Prefer not using default
. List every case explicitly instead. This way the compiler will force us to handle newly added cases, instead of potentially causing subtle bugs.
Prefer to use the strictest access level.
When something could be private
, make it so. This helps reasoning about usages of the given property or method (or type). Although Xcode can be used (most of the time) to find usages, you still need to have the code checked out first.
Use access control as the leading property specifier, even when using static
, lazy
or override
. The only things that should come before access control are attributes and property wrappers.
If a component is designed to be reused anywhere, make it public
. In that case make all of its non-private
members public
as well.
If a type is not made public
, but it's assumed that it may need to be in the future, handle its members in one of these two ways:
- Either leave all of its non-
private
members implicitly internal. - Or make all of its non-
private
memberspublic
.
Eaither way is okay, they have their own pros and cons. Just make sure all members are consistently either public
or (implicitly) internal.
When internal is the default access level omit the internal
keyword.
Exception: Define a property or method explicitly as internal
when it should be private
, but made internal
only for testing purposes. Consider refactoring the code before using this "workaround".
Prefer private
over fileprivate
.
Avoid using public extension
. Instead keep the extension (implicitly) internal and make all of its non-private
members public
.
It is okay to use private extension
, as it is a more contained case.
Do not use forced unwrapping in production code.
Prefer not to use implicitly unwrapped optional types for properties in production code. Prefer optional binding to implicitly unwrapped optionals in most other cases.
When accessing an optional value, use optional chaining if the value is only accessed once or if there are many optionals in the chain:
textContainer?.textLabel?.setNeedsDisplay()
Use optional binding when it's more convenient to unwrap once and perform multiple operations:
if let userName = user.name { ... }
Whenever possible, use the shorthand syntax for unwrapping optionals into shadowed variables:
if let textContainer { ... }
Conditional statements that test whether an Optional is non-nil
but do not access the wrapped value, should be written as comparisons to nil
.
- Preferred:
if value != nil {
print("value was not nil")
}
- Not Preferred:
if let _ = value {
print("value was not nil")
}
Swift does not require a semicolon after each statement in your code. They are only required if you wish to combine multiple statements on a single line.
Do not write multiple statements on a single line separated with semicolons.
Prefer to omit the return
keyword for computed properties, methods or closures where the body is a oneliner.
In other simpler cases it's also preferred to omit it, but it's okay either way.
It's okay to omit it when returning results of simple if-else
or switch
statements.
Parameters with default values are not required to be in the last postions.
Prefer to set trivial defaults, which could be reasonably guessed on the call site.
Do not overuse default parameter values. Keep in mind that while default values help keeping the call site clean, they can also introduce ambiguity.
Parentheses around conditionals are not required and should be omitted.
- Preferred:
if name == "Hello" {
print("World")
}
- Not Preferred:
if (name == "Hello") {
print("World")
}
It's okay to use parentheses when it improves clarity: in complex cases or to be explicit about precedence.
- Preferred:
let playerMark = (player == current ? "X" : "O")
Do not use trailing commas in arrays and dictionaries.
In function type declarations (such as closures, or variables holding a function reference), write the return type as Void
, not as ()
.
In functions declared with the func
keyword, omit the Void
return type entirely.
To represent the lack of an input simply use ()
, not (Void)
.
- Preferred:
func doSomething() { ... }
let callback: () -> Void
typealias CompletionHandler = (Bool) -> Void
- Not Preferred:
func doSomething() -> Void { ... }
func doSomething2() -> () { ... }
let callback: () -> ()
typealias CompletionHandler = (Result) -> ()
Attributes @IBOutlet
, @IBAction
, @objc
, @NSManaged
should be in the same line as the member, before other specifiers.
Other attributes should be in their own line.
Property wrappers should be in the same line as the property, before other specifiers.
[TODO: Macros]
When they are needed, use comments to explain why a particular piece of code does something. Comments must be kept up-to-date or deleted.
Prefer extracting complex code into self-describing helper methods (or types), instead of adding comments to it.
Avoid the use of C-style comments (/* ... */
). Prefer the use of double- or triple-slash.
Leaving // TODO:
or // FIXME:
comments in the code should be rare. In those cases prefer to create and link a followup ticket in the comment.
When building a long string literal, you're encouraged to use the multi-line string literal syntax. An exception is when a string is copy-pasted from the design as-is.
Open the literal on the same line as the assignment, but do not include text on that line. Indent the text block one additional level.
- Preferred:
let message = """
You cannot charge the flux \
capacitor with a 9V battery.
You must use a super-charger \
which costs 10 credits. You currently \
have \(credits) credits available.
"""
- Not Preferred:
let message = """You cannot charge the flux \
capacitor with a 9V battery.
You must use a super-charger \
which costs 10 credits. You currently \
have \(credits) credits available.
"""
- Not Preferred:
let message = "You cannot charge the flux " +
"capacitor with a 9V battery.\n" +
"You must use a super-charger " +
"which costs 10 credits. You currently " +
"have \(credits) credits available."