swift-style-guide
swift-style-guide copied to clipboard
Coding conventions and best practices for Swift projects
Note: This repository is no longer being maintained.
Swift Style Guide
This style guide is a product of our iOS team's more than a year writing, reviewing, and testing Swift code. It reflects the coding rules we have observed as "efficient" in our production apps.
The guidelines here try to accomplish/encourage practices that accomplish the following goals:
- to make it hard to write programmer errors, or at least make them hard to miss
- to increase readability and clarity of intent, with assumption that the code will be reviewed/consumed by a different person
- to minimize unnecessary code bloat
- to observe aesthetics that the majority of the team voted for
We understand that a quite a few of the guidelines below are controversial and may even be opposite what the Swift community generally observes, but we have tried and tested these practices in a team environment and they work for us.
That said, this is a live document. As our app grows, our team improves, and Swift evolves, our practices will adapt as well and this list will be updated if needed.
Table of Contents
-
Styles and Conventions
-
Formatting
-
Semicolons (
;
) - Whitespaces
-
Commas (
,
) -
Colons (
:
) -
Braces (
{}
) - Properties
- Control Flow Statements
-
Semicolons (
-
Naming
- Capitalization
- Semantics
-
Dependencies
- Import Statements
- Declaration Order
-
Formatting
-
Best Practices
- Comments
- Protection from Dynamism
- Access Modifiers
- Type Inference
- Collections / SequenceTypes
- Protection from Retain Cycles
Styles and Conventions
Formatting
Semicolons
Trailing semicolons (;
) are not allowed.
OK | NG |
---|---|
self.backgroundColor = UIColor.whiteColor()
self.completion = {
// ...
}
|
self.backgroundColor = UIColor.whiteColor();
self.completion = {
// ...
};
|
Rationale: There is no practical advantage of using trailing semicolons. It is, however, a very good way to catch someone copy-pasting Objective-C code ;)
Whitespaces
Use 4 spaces for tabs.
Set Xcode's Text Editing settings as shown:

Rationale: Maintains visually similar indentation across different text editors.
All source files should end with a single trailing newline (only).
OK | NG |
---|---|
class Button {
// ...
}
// |
class Button {
// ...
} // |
class Button {
// ...
}
// |
Rationale: Prevents no-trailing-newline errors and reduces noise in commit diffs.
All functions should be at least one empty line apart each other.
OK |
---|
class BaseViewController: UIViewController {
// ...
|
Rationale: Gives breathing room between code blocks.
Use single spaces around operator definitions and operator calls.
OK | NG |
---|---|
func Int {
// ...
}
|
func Int {
// ...
}
|
Rationale: Readability.
Use single spaces around return arrows (->
) both in functions and in closures.
OK | NG |
---|---|
func doSomething(value: Int) -> Int {
// ...
}
|
func doSomething(value: Int)->Int {
// ...
}
|
Rationale: Readability.
Commas
Commas (,
) should have no whitespace before it, and should have either one space or one newline after.
OK | NG |
---|---|
let array = [1, 2, 3]
|
let array = [1,2,3]
let array = [1 ,2 ,3]
let array = [1 , 2 , 3]
|
self.presentViewController(
controller,
animated: true,
completion: nil
)
|
self.presentViewController(
controller ,
animated: true,completion: nil
)
|
Rationale: Keeps comma-separated items visually separate.
Colons
Colons (:
) used to indicate type should have one space after it and should have no whitespace before it.
OK | NG |
---|---|
func createItem(item: Item)
|
func createItem(item:Item)
func createItem(item :Item)
func createItem(item : Item)
|
var item: Item? = nil
|
var item:Item? = nil
var item :Item? = nil
var item : Item? = nil
|
Rationale: The colon describes the object to its left, not the right. (Just how we write colons in english)
Colons (:
) for case
statements should have no whitespace before it, and should have either one space or one newline after it.
OK | NG |
---|---|
switch result {
|
switch result {
|
Rationale: Same as he previous rule, the colon describes the object to its left, not the right.
Braces
Open braces ({
) should be one space following the previous non-whitespace character.
OK | NG |
---|---|
class Icon {
// ...
}
|
class Icon{
// ...
}
|
let block = { () -> Void in
// ...
}
|
let block ={ () -> Void in
// ...
}
|
Rationale: Separates the brace from the declaration.
Open braces ({
) for type declarations, functions, and closures should be followed by one empty line. Single-statement closures can be written in one line.
OK | NG |
---|---|
class Icon {
|
class Icon {
let image: UIImage
|
Rationale: Gives breathing room when scanning for code.
Empty declarations should be written in empty braces ({}
), otherwise a comment should indicate the reason for the empty implementation.
OK | NG |
---|---|
extension Icon: Equatable {}
|
extension Icon: Equatable {
}
|
var didTap: () -> Void = {}
|
var didTap: () -> Void = { }
|
Rationale: Makes it clear that the declaration was meant to be empty and not just a missing TODO
.
Close braces (}
) should not have empty lines before it. For single line expressions enclosed in braces, there should be one space between the last statement and the closing brace.
OK | NG |
---|---|
class Button {
|
class Button {
|
Rationale: Provides breathing room between declarations while keeping code compact.
Close braces (}
) unless on the same line as its corresponding open brace ({
), should be left-aligned with the statement that declared the open brace.
OK | NG |
---|---|
lazy var largeImage: UIImage = { () -> UIImage in
|
lazy var largeImage: UIImage = { () -> UIImage in
|
Rationale: Close braces left-aligned with their opening statements visually express their scopes pretty well. This rule is the basis for the succeeding formatting guidelines below.
Properties
The get
and set
statement and their close braces (}
) should all be left-aligned. If the statement in the braces can be expressed in a single line, the get
and set
declaration can be inlined.
The rules on braces apply.
OK | NG |
---|---|
struct Rectangle {
|
struct Rectangle {
|
struct Rectangle {
|
struct Rectangle {
|
Rationale: Combined with the rules on braces, this formatting provides very good consistency and scannability.
Read-only computed properties should ommit the get
clause.
OK | NG |
---|---|
struct Rectangle {
|
struct Rectangle {
|
Rationale: The return
statement provides enough clarity that lets us use the more compact form.
Control Flow Statements
if
, else
, switch
, do
, catch
, repeat
, guard
, for
, while
, and defer
statements should be left-aligned with their respective close braces (}
).
The rules on braces apply.
OK | NG |
---|---|
if array.isEmpty {
// ...
}
else {
// ...
}
|
if array.isEmpty {
// ...
} else {
// ...
}
|
if array.isEmpty
{
// ...
}
else
{
// ...
}
|
Rationale: Combined with the rules on braces, this formatting provides very good consistency and scannability. Close braces left-aligned with their respective control flow statements visually express their scopes pretty well.
case
statements should be left-aligned with the switch
statement. Single-line case
statements can be inlined and written compact. Multi-line case
statements should be indented below case:
and separated with one empty line.
The rules on braces apply.
OK | NG |
---|---|
switch result {
|
switch result {
|
switch result {
|
switch result {
|
Rationale: Reliance on Xcode's auto-indentation. For multi-line statements, separating case
s with empty lines enhance visual separation.
Conditions for if
, switch
, for
, and while
statements should not be enclosed in parentheses (()
).
OK | NG |
---|---|
if array.isEmpty {
// ...
}
|
if (array.isEmpty) {
// ...
}
|
Rationale: This isn't Objective-C.
Try to avoid nesting statements by return
ing early when possible.
OK | NG |
---|---|
guard let strongSelf = self else {
|
if let strongSelf = self {
|
Rationale: The more nested scopes to keep track of, the heavier the burden of scanning code.
Naming
Naming rules are mostly based on Apple's naming conventions, since we'll end up consuming their API anyway.
Capitalization
Type names (class
, struct
, enum
, protocol
) should be in UpperCamelCase.
OK | NG |
---|---|
class ImageButton {
|
class image_button {
|
Rationale: Adopt Apple's naming rules for uniformity.
enum
values and OptionSetType
values should be in UpperCamelCase.
OK | NG |
---|---|
enum ErrorCode {
|
enum ErrorCode {
|
Rationale: Adopt Apple's naming rules for uniformity.
Variables and functions should be in lowerCamelCase, including statics and constants. An exception is acronyms, which should be UPPERCASE.
OK | NG |
---|---|
var webView: UIWebView?
var URLString: String?
|
var web_view: UIWebView?
var urlString: String?
|
Rationale: Adopt Apple's naming rules for uniformity. As for acronyms, the readability makes keeping them upper-case worth it.
Semantics
Avoid single-character names for types, variables, and functions. The only place they are allowed is as indexes in iterators.
OK | NG |
---|---|
for (i, value) in array.enumerate() {
// ... "i" is well known
}
|
for (i, v) in array.enumerate() {
// ... what's "v"?
}
|
Rationale: There is always a better name than single-character names. Even with i
, it is still more readable to use index
instead.
Avoid abbreviations as much as possible. (although some are allowed such as min
/max
)
OK | NG |
---|---|
let errorCode = error.code
|
let err = error.code
|
Rationale: Clarity is prioritized over slight brevity.
Choose a name that communicates as much information about what it is and what it's for.
OK | NG |
---|---|
class Article {
|
class Article {
|
Better | |
class NewsArticle {
|
Rationale: Clarity is prioritized over slight brevity. Also, the more specific the name, the less likely they are to collide with other symbols.
When pertaining to URLs, distinguish strings from actual NSURL
s by appending the suffix ~String
.
OK | NG |
---|---|
var requestURL: NSURL
var sourceURLString: String
|
var requestURL: NSURL
var sourceURL: String
|
Rationale: Saves a few seconds checking header declarations for the correct type.
Do not pertain to constructs (class
, struct
, enum
, protocol
, etc.) in their names.
OK | NG |
---|---|
class User {
// ...
}
|
class UserClass {
// ...
}
|
Rationale: The extra suffix is redundant. It should be noted though that Objective-C protocols with the same name as an existing Objective-C class are bridged to Swift with a ~Protocol
suffix (e.g. NSObject
and NSObjectProtocol
). But they are irrelevant to this guideline as they are automatically generated by the Swift compiler.
Dependencies
Import Statements
import
statements for OS frameworks and external frameworks should be separated and alphabetized.
OK | NG |
---|---|
import Foundation
import UIKit
|
import Foundation
import Alamofire
import SwiftyJSON
import UIKit
import Cartography
|
Rationale: Reduce merge conflicts when dependencies change between branches.
Declaration Order
All type declarations such as class
, struct
, enum
, extension
, and protocol
s, should be marked with // MARK: - <name of declaration>
(with hyphen)
OK | NG |
---|---|
// MARK: - Icon
|
// Icon
|
Rationale: Makes it easy to jump to specific types when using Xcode's Source Navigator.
All properties and methods should be grouped into the superclass/protocol they implement and should be tagged with // MARK: <superclass/protocol name>
. The rest should be marked as either // MARK: Public
, // MARK: Internal
, or // MARK: Private
.
OK |
---|
// MARK: - BaseViewController
|
Rationale: Makes it easy to locate where in the source code certain properties and functions are declared.
All // MARK:
tags should have two empty lines above and one empty line below.
OK | NG |
---|---|
import UIKit
|
import UIKit
// MARK: - BaseViewController
class BaseViewController: UIViewController {
|
Rationale: Aesthetic. Gives breathing room between type declarations and function groups.
The groupings for // MARK:
tags should be ordered as follows:
-
// MARK: Public
-
// MARK: Internal
- Class Inheritance (parent-most to child-most)
-
// MARK: NSObject
-
// MARK: UIResponder
-
// MARK: UIViewController
-
- Protocol Inheritance (parent-most to child-most)
-
// MARK: UITableViewDataSource
-
// MARK: UIScrollViewDelegate
-
// MARK: UITableViewDelegate
-
-
// MARK: Private
Rationale: Makes it easy to locate where in the source code certain implementations are declared. public
and internal
declarations are more likely to be referred to by API consumers, so are declared at the top.
Under each grouping above, declarations should be ordered as follows:
-
@
properties (@NSManaged
,@IBOutlet
,@IBInspectable
,@objc
,@nonobjc
, etc.) -
lazy var
properties - computed
var
properties - other
var
properties -
let
properties -
@
functions (@NSManaged
,@IBAction
,@objc
,@nonobjc
, etc.) - other functions
Rationale: @
properties and functions are more likely to be referred to (such as when checking KVC keys or Selector
strings, or when cross-referencing with Interface Builder) so are declared higher.
Best Practices
In general, all Xcode warnings should not be ignored. These include things like using let
instead of var
when possible, using _
in place of unused variables, etc.
Comments
Comments should be answering some form of "why?" question. Anything else should be explainable by the code itself, or not written at all.
OK | NG |
---|---|
let leftMargin: CGFloat = 20
view.frame.x = leftMargin
|
view.frame.x = 20 // left margin
|
@objc dynamic func tableView(tableView: UITableView,
heightForHeaderInSection section: Int) -> CGFloat {
|
@objc dynamic func tableView(tableView: UITableView,
heightForHeaderInSection section: Int) -> CGFloat {
|
Rationale: The best comment is the ones you don't need. If you have to write one be sure to explain the rationale behind the code, not just to simply state the obvious.
All temporary, unlocalized strings should be marked with // TODO: localize
OK | NG |
---|---|
self.titleLabel.text = "Date Today:" // TODO: localize
|
self.titleLabel.text = "Date Today:"
|
Rationale: Features are usually debugged and tested in the native language and translated strings are usually tested separately. This guarantees that all unlocalized texts are accounted for and easy to find later on.
Protection from Dynamism
All Objective-C protocol
implementations, whether properties or methods, should be prefixed with @objc dynamic
OK | NG |
---|---|
@objc dynamic func scrollViewDidScroll(scrollView: UIScrollView) {
// ...
}
|
func scrollViewDidScroll(scrollView: UIScrollView) {
// ...
}
|
Rationale: Prevents horrible compiler optimization bugs. Trust us.
All IBAction
s and IBOutlet
s should be declared dynamic
OK |
---|
@IBOutlet private dynamic weak var closeButton: UIButton?
|
Rationale: The Swift compiler sometimes mangle these. Writing dynamic
guarantees safety, even when we declare them as private
.
All properties used for KVC/KVO and all functions used as Selector
s should be marked dynamic
OK |
---|
override func viewDidLoad() {
|
Rationale: Same reason as the preceding rule, the Swift compiler sometimes mangle these. Writing dynamic
guarantees safety, even when we declare them as private
.
All @IBOutlet
s should be declared weak
. They should also be wrapped as Optional
, not ImplicitlyUnwrappedOptional
.
OK | NG |
---|---|
@IBOutlet dynamic weak var profileIcon: UIImageView?
|
@IBOutlet var profileIcon: UIImageView!
|
Rationale: This guarantees safety even if subclasses opt to not create the view for the @IBOutlet
. This also protects against crashes caused by properties being accessed before viewDidLoad(_:)
.
Access Modifiers
Design declarations as private
by default and only expose as internal
or public
as the needs arise.
Rationale: This helps prevent pollution of XCode's auto-completion. In theory this should also help the compiler make better optimizations and build faster.
For library modules: all declarations should explicitly specify either public
, internal
, or private
.
OK | NG |
---|---|
private let defaultTimeout: NSTimeInterval = 30
|
let defaultTimeout: NSTimeInterval = 30
|
Rationale: Makes the intent clear for API consumers.
For application modules: public
access is prohibited unless required by a protocol. The internal
keyword may or may not be written, but the private
keyword is required.
OK | NG |
---|---|
private let someGlobal = "someValue"
|
public let someGlobal = "someValue"
|
Rationale: A public
declaration in an app bundle does not make sense. In effect, declarations are assumed to be either internal
or private
, in which case it is sufficient to just require private
explicitly.
Access modifiers should be written before all other non-@
modifiers.
OK | NG |
---|---|
@objc internal class User: NSManagedObject {
// ...
@NSManaged internal dynamic var identifier: Int
// ...
@NSManaged private dynamic var internalCache: NSData?
}
|
internal @objc class User: NSManagedObject {
// ...
@NSManaged dynamic internal var identifier: Int
// ...
private @NSManaged dynamic var internalCache: NSData?
}
|
Rationale: Combined with the rules on declaration order, this improves readability when scanning code vertically.
Type Inference
Unless required, a variable/property declaration's type should be inferred from either the left or right side of the statement, but not both.
OK | NG |
---|---|
var backgroundColor = UIColor.whiteColor()
var iconView = UIImageView(image)
|
var backgroundColor: UIColor = UIColor.whiteColor()
var iconView: UIImageView = UIImageView(image)
|
var lineBreakMode = NSLineBreakMode.ByWordWrapping
// or
var lineBreakMode: NSLineBreakMode = .ByWordWrapping
|
var lineBreakMode: NSLineBreakMode = NSLineBreakMode.ByWordWrapping
|
Rationale: Prevent redundancy. This also reduces ambiguity when binding to generic types.
When literal types are involved (StringLiteralConvertible
, NilLiteralConvertible
, etc), it is encouraged to specify the type explicitly and is preferrable over casting with as
directly.
OK | NG |
---|---|
var radius: CGFloat = 0
var length = CGFloat(0)
|
var radius: CGFloat = CGFloat(0)
var length = 0 as CGFloat // prefer initializer to casts
|
Rationale: Prevent redundancy. This also reduces ambiguity when binding to generic types.
Collections / SequenceTypes
.count
should only be used when the count value itself is needed
OK |
---|
let badgeNumber = unreadItems.count
|
Checking if empty or not:
OK | NG |
---|---|
if sequence.isEmpty {
// ...
|
if sequence.count |
Getting the first or last item:
OK | NG |
---|---|
let first = sequence.first
let last = sequence.last
|
let first = sequence[0]
let last = sequence[sequence.count - 1]
|
Removing the first or last item:
OK | NG |
---|---|
sequence.removeFirst()
sequence.removeLast()
|
sequence.removeAtIndex(0)
sequence.removeAtIndex(sequence.count - 1)
|
Iterating all indexes:
OK | NG |
---|---|
for i in sequence.indices {
// ...
}
|
for i in 0 .. |
Getting the first or last index:
OK | NG |
---|---|
let first = sequence.indices.first
let last = sequence.indices.last
|
let first = 0
let last = sequence.count - 1
|
Iterating all indexes except the last n
indexes:
OK | NG |
---|---|
for i in sequence.indices.dropLast(n) {
// ...
}
|
for i in 0 .. |
Iterating all indexes except the first n
indexes:
OK | NG |
---|---|
for i in sequence.indices.dropFirst(n) {
// ...
}
|
for i in n .. |
In general, if you have to add or subtract to count
, there is probably a better, Swift-y way to do it.
Rationale: Clarity of intent, which in turn reduces programming mistakes (esp. off-by-one calculation errors).
Protection from Retain Cycles
In particular, this will cover the ever-debatable usage/non-usage of self
.
All instance properties and functions should be fully-qualified with self
, including within closures.
(See next rule for implications)
OK | NG |
---|---|
self.animatableViews.forEach { view in
|
animatableViews.forEach { view in
|
Rationale: We found that we made less mistakes when we just required self
all the time than if we have to decide wether to write it or not. That said, we are aware of the implications of this to retain cycles. See rule below.
For all non-@noescape
and non-animation closures, accessing self
within the closure requires a [weak self]
declaration.
OK | NG |
---|---|
self.request.downloadImage(
url,
completion: { [weak self] image in
|
self.request.downloadImage(
url,
completion: { image in
|
Rationale: Combined with the self
-requirement rule above, retain cycle candidates are very easy to spot. Just look for closures that access self
and check for the missing [weak self]
.
Never use unowned
to capture references in closures.
OK | NG |
---|---|
self.request.downloadImage(
url,
completion: { [weak self] image in
|
self.request.downloadImage(
url,
completion: { [unowned self] image in
|
Rationale: While unowned
is more convenient (you don't need to work with an Optional
) than weak
, it is also more prone to crashes. Nobody likes zombies.
If the validity of the weak self
in the closure is needed, bind using the variable `self`
to shadow the original.
OK | NG |
---|---|
self.request.downloadImage(
url,
completion: { [weak self] image in
|
self.request.downloadImage(
url,
completion: { [weak self] image in
|
Rationale: Keep the syntax highlighting ;)
Back to top