Skip to content

Swift language style guide & coding conventions followed by Xmartlabs.

License

Notifications You must be signed in to change notification settings

xmartlabs/Swift-Style-Guide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 

Repository files navigation

Our Swift Style Guide

Swift language style guide & coding conventions followed by xmartlabs.com team.

The goals of this guide are:

  1. Be syntactically consistent on how we write swift code no matter who does it.
  2. Write readable and maintainable code.
  3. 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:

  1. 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 (.).
  2. When a line is broken at an assignment operator the break typically comes after the symbol, but either way is acceptable.
  3. A method or constructor name stays attached to the open parenthesis (() that follows it.
  4. 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:

  1. 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.
  2. Within method bodies, as needed to create logical groupings of statements.
  3. Before the first member and after the last member of the type/extension declaration.
  4. 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.

  1. Separating any reserved word, such as if, from an open parenthesis (() that follows it on that line
  2. Separating any reserved word, such as else or catch, from a closing curly brace (}) that precedes it on that line
  3. Before any open curly brace ({):
  4. On both sides of any binary or ternary operator. This also applies to the following "operator-like" symbols:
    • the colon (:) in an inline if
  5. After (,:) or the closing parenthesis ()) of a cast
  6. 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

  1. Just explicitly disable next rules:
  • valid_docs
  • variable_name
  • variable_name_min_length
  1. 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!

About

Swift language style guide & coding conventions followed by Xmartlabs.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published