Awesome
Our Swift Style Guide
Swift language style guide & coding conventions followed by xmartlabs.com team.
The goals of this guide are:
- Be syntactically consistent on how we write swift code no matter who does it.
- Write readable and maintainable code.
- Focus on real software problems rather than on how we style the code.
Naming
Use descriptive names with camel case for classes, structs, enums, methods, properties, variables, constants, etc. Only Classes, Enums, Structs names should be capitalized. Don't care about name length.
prefered
let maxSpeed = 150.0
var currentSpeed = 46.5
var numberOfWheels = 3
func accelerateVehicleTo(speed: Double) {
currentSpeed = speed
}
not prefered
let NUMBER_OF_WHEELS = 3
let MAX_SPEED = 150.0
var mCurrentSpeed = 46.5
func accelerate_vehicleTo(speed: Double) {
mCurrentSpeed = speed
}
Line-wrapping
Where to break
The prime directive of line-wrapping is: prefer to break at a higher syntactic level. Also:
- When a line is broken at a non-assignment operator the break comes before the symbol.
- This also applies to the following "operator-like" symbols: the dot separator (.).
- When a line is broken at an assignment operator the break typically comes after the symbol, but either way is acceptable.
- A method or constructor name stays attached to the open parenthesis (() that follows it.
- A comma (,) stays attached to the token that precedes it.
Indent continuation lines at least +4 spaces
When line-wrapping, each line after the first (each continuation line) is indented at least +4 from the original line.
preferred
func ownersOfCarsWithoutFuel(cars: [Car]) -> [Owner] {
return cars.filter { $0.fuelLevel == 0 }
.map { $0.owner }
}
not preferred
func ownersOfCarsWithoutFuel(cars: [Car]) -> [Owner] {
return cars.filter { $0.fuelLevel == 0 }
.map { $0.owner }
}
When there are multiple continuation lines, indentation may be varied beyond +4 as desired. In general, two continuation lines use the same indentation level if and only if they begin with syntactically parallel elements.
Whitespace
- Tabs, not spaces.
- Always end a file with a newline.
- Never leave trailing whitespace.
Vertical Whitespace
A single blank line appears:
- Between consecutive members (or initializers) of a class: fields, constructors, methods, nested classes, static initializers, instance initializers.
- Exception: A blank line between two consecutive fields (having no other code between them) is optional. Such blank lines are used as needed to create logical groupings of fields.
- Within method bodies, as needed to create logical groupings of statements.
- Before the first member and after the last member of the type/extension declaration.
- As required by other sections of this document.
Multiple consecutive blank lines are not permitted.
Next reserved words won't start in a new line: else, catch
Horizontal whitespace
Beyond where required by the language or other style rules, and apart from literals, comments and doc, a single ASCII space also appears in the following places only.
- Separating any reserved word, such as if, from an open parenthesis (() that follows it on that line
- Separating any reserved word, such as else or catch, from a closing curly brace (}) that precedes it on that line
- Before any open curly brace ({):
- On both sides of any binary or ternary operator. This also applies to the following "operator-like" symbols:
- the colon (:) in an inline if
- After (,:) or the closing parenthesis ()) of a cast
- On both sides of the double slash (//) that begins an end-of-line comment. Here, multiple spaces are allowed, but not required.
A whitespace before colon (:) in a property declaration, class inheritance declaration and arguments labels declaration must not be added
preferred
class Car: Vehicle {
let numberOfDoors: Int
let maxSpeed: Float
let brand: String?
let model: String?
var fuelLevel = 0
var temperature = 0.0
init(numberOfDoors: Int, maxSpeed: Float, brand: String?, model: String?) {
super.init()
self.numberOfDoors = numberOfDoors
self.maxSpeed = maxSpeed
self.brand = brand
self.model = model
}
func fuelStatusDescription() -> String {
if fuelLevel < 0.1 {
return "\(Warning), not enough fuel!"
} else {
return "Enough fuel to travel."
}
}
func temperatureStatusDescription() -> String {
return temperature > 28 ? "To much hot, turn on air conditioner" : "Great temperature condition"
}
}
not preferred
class Car : Vehicle {
let numberOfDoors : Int
let maxSpeed : Float
let brand : String?
let model : String?
var fuelLevel = 0
var temperature = 0.0
init(numberOfDoors: Int, maxSpeed: Float, brand: String?, model: String?) {
super.init()
self.numberOfDoors = numberOfDoors
self.maxSpeed = maxSpeed
self.brand = brand
self.model = model
}
func fuelStatusDescription() -> String {
if fuelLevel < 0.1{
return "\(Warning), not enough fuel!"
}
else{
return "Enough fuel to travel."
}
}
func temperatureStatusDescription() -> String {
return temperature > 28 ? "To much hot, turn on air conditioner":"Great temperature condition"
}
}
Semicolon
- Do not use semicolon at the end of a line.
End line semicolon is optional in swift.
prefered
let numberOfWheels = 4
not prefered
let numberOfWheels = 4;
Imports
Refer to [Import Declarations from Apple Doc].
Imports should be declared at top of each Swift file and alphabetically (ignoraing case) sorted. There could be any number of comments before the first import sentence.
Keep imports that declare a kind
first ordered by their kind
after imports that don't do it. Optionally, group imports by kind declaration into logical blocks.
Imports marked with @testable
should be put at the end of the list of imports. Optionally, add an empty line in order to group normal imports and @testable
imports in two different logical blocks.
Avoid duplicated imports.
prefered
// Comments are allowed before imports
import Foundation
import UIKit
import test
import class MyApp.MyClassA
import class MyApp.MyClassB
import enum MyApp.MyEnum
import struct MyApp.Struct
@testable import MyApp
not prefered
// Comments are allowed before imports
import UIKit
@testable import MyApp
import Foundation
Methods
If a method returns Void
we omit the return value.
prefered
func accelerateVehicleTo(speed: Double) {
..
.
not prefered
func accelerateVehicleTo(speed: Double) -> Void {
..
.
let vs var
Always use let
if a variable value won't change.
It's clear for a developer that a let
reference can not be changed which makes easy to read and understand a code. Whenever a var
appears a developer expects that its value changes somewhere.
Normally swift compiler complains if we use
var
and the variable does not change.
self
Only use self
when strictly necessary. For instance within closures or to disambiguate a symbol.
Swift does not require
self
to access an instance property or to invoke its methods.
class Person {
let name: String
var skills = []
init(name: String) {
self.name = name // self needed to assign the parameter value to the property that has the same name.
}
func resetSkills() {
skills = [] // Note that we don't use self here
}
}
Optionals
Avoid Force Unwrapping
Optional types should not be forced unwrapped.
Trying to use ! to access a non-existent optional value triggers a runtime error.
Whenever possible prefer to use Optional Binding or Optional Chaining.
var vehicle = Vehicle?
prefered
vehicle?.accelerateVehicleTo(65.3)
not prefered
vehicle!.accelerateVehicleTo(65.3)
prefered
if let vehicle = vehicle {
parkVehicle(vehicle)
}
not prefered
if vehicle != nil {
parkVehicle(vehicle!)
}
Multiple optional Binding & where usage
var vehicleOne: Vehicle? = ...
var vehicleTwo: Vehicle? = ...
prefered
if let vehicleOne = vehicleOne, vehicleTwo = vehicleTwo, vehicleOne.currentSpeed > vehicleTwo.currentSpeed {
print("\(vehicleOne) is faster than \(vehicleTwo)")
}
not prefered
if let vehicleOne = vehicleOne {
if let vehicleTwo = vehicleTwo {
if vehicleOne.currentSpeed > vehicleTwo.currentSpeed {
print("\(vehicleOne) is faster than \(vehicleTwo)")
}
}
}
Nil Coalescing Operator
The nil coalescing operator (a ?? b) unwraps an optional a if it contains a value, or returns a default value b if a is nil. It's shorthand for the code below:
a != nil ? a! : b
The nil coalescing operator provides a more elegant way to encapsulate this conditional checking and unwrapping in a concise and readable form.
prefered
public func textFieldShouldReturn(textField: UITextField) -> Bool {
return formViewController()?.textInputShouldReturn(textField, cell: self) ?? true
}
not prefered
public func textFieldShouldReturn(textField: UITextField) -> Bool {
guard let result = formViewController()?.textInputShouldReturn(textField, cell: self) else {
return true
}
return result
}
prefered
func didSelect() {
row.value = !(row.value ?? false)
}
not prefered
func didSelect() {
row.value = row.value != nil ? !(row.value!) : true
}
prefered
switchControl?.on = row.value ?? false
not prefered
if let value = row.value {
switchControl?.on = value
} else {
switchControl?.on = false
}
Avoid Implicit unwrapping Optionals
Prefer to use ?
over !
if the property/variable value can be nil.
class BaseRow {
var section: Section? // can be nil at any time, we should use ?
}
Only use !
when strictly necessary. For instance if a property value can not be set up at initialization time but we can ensure it will be set up before we start using it.
Explicit optionals are safer whereas Implicit unwrapping optionals will crash at runtime if the value is nil.
class BaseCell: UITableViewCell
// Every BaseCell has a baseRow value that is set up by a external library right after BaseCell init is invoked.
var baseRow: BaseRow!
}
Access Control
By default an entity access control level is internal which means it can be used from within the same module. Typically this access control modifier works and in this cases we should not explicit define a internal modifier.
In Swift, no entity can be defined in terms of another entity that has a lower (more restrictive) access level.
Control Flow
Whenever possible prefer to use for-in
statements over traditional for c-style:
prefered
for index in 1..<5 {
...
}
not prefered
for var index = 0; index < 5; ++index {
...
}
prefered
var items = ["Item 1", "Item 2", "Item 3"]
for item in items().enumerate() {
segmentedControl.insertSegmentWithTitle(item.element, atIndex: item.index, animated: false)
}
// or
for (index, item) in items().enumerate() {
segmentedControl.insertSegmentWithTitle(item, atIndex: index, animated: false)
}
not prefered
for var index = 0; index < items.count; i++ {
let item = items[index]
segmentedControl.insertSegmentWithTitle(item, atIndex: index, animated: false)
}
Early Exit
Use guard
if some condition must be true to continue the execution of a method. Check the condition as early as possible.
Swift compiler checks that the code inside the guard statement transfers the control to exit the code block that the guard appears in.
prefered
func getOutPassenger(person: Person) {
guard passenger.contains(person) && vehicle.driver != person else {
return
}
....
}
not prefered
func getOutPassenger(person: Person) {
if !passenger.contains(person) || vehicle.driver == person else {
return
}
....
}
Protocols
Whenever possible conform to a protocol through an extension. Use one extension per protocol conformance.
prefered
class XLViewController: UIViewController {
...
}
extension XLViewController: UITableViewDataSource {
...
}
extension XLViewController: UITableViewDelegate {
...
}
not prefered
class XLViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
...
}
One limitation of conforming protocol through extension is that we can not override implementations neither invoke super implementation.
Properties
Whenever possible instantiate the property inline.
prefered
var currentSpeed = 46.5
not preferred
var currentSpeed: Double
init() {
self.currentSpeed = 46.5
}
Don't specify property type if it can be inferred.
prefered
var dismissViewControllerWhenDone = false
not prefered
var dismissViewControllerWhenDone: Bool = false
Array property declaration:
prefered
var vehicles = [Vehicle]()
//or
var vehicles: [Vehicle]
not prefered
var vehicles = Array<Vehicle>()
//or
var vehicles: Array<Vehicle>
// or
var vehicles: Array<Vehicle> = []
Read only computed properties and subscripts
We use implicit getters if a computed property or subscript is read-only.
prefered
var hasEngine: Bool {
return true
}
subscript(index: Int) -> Passenger {
return passenger[index]
}
not prefered
var hasEngine: Bool {
get {
return true
}
}
subscript(index: Int) -> Passenger {
get {
return passenger[index]
}
}
Enumerations
Enumeration values should be begin with a lowercase letter. One option per line.
prefered
public enum HeaderFooterType {
case header
case footer
}
not prefered
public enum HeaderFooterType {
case Header, Footer
}
// or
public enum HeaderFooterType {
case Header
case Footer
}
Shorter dot syntax
Usually enum type can be inferred and in these cases we prefer the short dot syntax over long typed syntax.
prefered
var directionToHead = CompassPoint.West // same as var directionToHead: CompassPoint = .West
directionToHead = .east
..
headToDirection(.west)
not prefered
var directionToHead = CompassPoint.west
directionToHead = CompassPoint.east // here the type of directionToHead is already know so the CompassPoint type is unnecessary.
headtoDirection(CompassPoint.west)
Multiple cases in the same line
Whenever possible use multiple cases in the same line.
prefered
switch service {
case .ChangeEmail, .ChangeUserName:
return .POST
}
not prefered
switch service {
case .ChangeEmail:
return .POST
case .ChangeUserName:
return .POST
}
Sometimes more than one option share the same switch case logic
Strings
String interpolation is prefered over string concatenation using +
prefered
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
not prefered
let message = String(multiplier) + " times 2.5 is " + String(Double(multiplier) * 2.5)
Error Handling
try? - Converting Errors to Optional Values
Use try? to write concise error handling code when you want to handle all errors in the same way.
You use try? to handle an error by converting it to an optional value. If an error is thrown while evaluating the try? expression, the value of the expression is nil.
prefered
let x = try? someThrowingFunction() ?? []
not prefered
var x : [AnyObject]
do {
x = try someThrowingFunction()
}
catch {
x = []
}
SwiftLint
Some of the style and syntaxis conventions we follow are checked using SwiftLint realm project.
Default configuration
- Just explicitly disable next rules:
- valid_docs
- variable_name
- variable_name_min_length
- Enable next opt-in rules
- sorted_imports
- closure_end_indentation
- force_unwrapping
This guide may suffer changes due to swift language evolution.
We would love to hear your ideas! Feel free to contribute by creating a pull request!