Skip to content
This repository has been archived by the owner on Dec 5, 2022. It is now read-only.

metova/swift-style-guide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

31 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Metova Swift Style Guide

[DEPRECATED] (Dec. 2022)

Note that this repo is being archived due to lack of updates. There are many great alternatives today, including Apple's and Google's design/styling guidelines.

Metova's Swift Style Guide

This style guide is written primarily with the development of iOS and OS X applications using the Xcode IDE in mind. As such, many of the guidelines found within are based around consistency with Apple's frameworks & Xcode's default preferences. If you are developing with Swift using different frameworks, or a different IDE, you may find sections of this style guide to be less applicable.

Table of Contents

General

Whitespace

Code should be indented four spaces per indentation level. Do not insert tab characters.

Files should end with a new line.

Calls to super should be followed by an empty line.

The closing brace for guard statements should be followed by an empty line.


Braces

Opening braces should be placed on the same line as the declaration they are encapsulating.

Preferred

if someCondition {
    // execute some code
}

Not Preferred

if someCondition 
{
    // execute some code 
}

Closing braces should always be on a new line by themselves, horizontally aligned with the left edge of the opening brace it is closing.

Rationale: Apple uses same line brackets in their code and Xcode autocomplete encourages it as well.


Control Flow

Omit unnecessary parenthesis around control flow statements.

Preferred:

while someCondition {
    // execute some code
}

Not Preferred:

while (someCondition) {
    // execute some code
}

Rationale: With braces required around bodies, the conditional part is perfectly clear without parenthesis and reads better.


Prefer to return and break early.

Preferred:

guard shouldDoTheThing else {
    return
}

// Do the thing.

Not Preferred:

if shouldDoTheThing {
    // Do the thing.  
}
else {
    return
}

Rationale: This eliminates unnecessary nesting and makes the exit conditions clear up front.


When using an early return or break, prefer guard to if statements.

Preferred:

guard shouldDoTheThing else {
    return
}

Not Preferred:

if !shouldDoTheThing {
    return  
}

Rationale: The guard statement guarantees the early exit. If the scope isn't exited, it will generate compile-time errors. It is also easier for readers to identify as an early exit.


Prefer using a switch statement over else if chains when dealing with enumerations.

Preferred:

switch someValue {
case .foo:
    doFooThing()
case .bar:
    doBarThing()
}

Not Preferred:

if someValue == MyEnum.foo {
    doFooThing()
}
else if someValue == MyEnum.bar {
    doBarThing()
}

Rationale: With a switch statement, we can more clearly distinguish whether all cases are handled. It is also more compact.


Prefer switching on tuples than unnecessary levels of nesting.

Preferred:

switch (someValue, direction) {
case (.foo, .left):
    doLeftThing()
case (.foo, .right):
    doRightThing()
case (.bar, .left):
    doBarThing()
case (.bar, .right):
    break       
}

Not Preferred:

switch someValue {
case .foo:
    switch direction {
    case .left:
        doLeftThing()
    case .right:
        doRightThing()
    }
case .bar:
    if direction == .left {
        doBarThing()
    }
}

Rationale: Switching on a tuple makes all of the possible conditions more clear from the start. It also becomes more compact and removes unnecessary levels of nesting.


Prefer starting else and catch statements on a new line, under the closing brace for the previous statement.

Preferred:

do {
    try doTheThing()
}
catch let error {
    handle(error)
}

Not Preferred:

do {
    try doTheThing()
} catch let error {
    handle(error)
}

Rationale: Putting these statements at the beginning of a new line helps find all of the associated statements. It also looks significantly better when collapsing code blocks in Xcode.

Uncuddled

Cuddled


Loops

Prefer for-in loops to forEach in most circumstances.

Preferred:

for thing in things {
    thing.doTheThing()
}

Not Preferred:

things.forEach { thing in
    thing.doTheThing()
}

Rationale: The preferred style reads more naturally.


When dealing with optional collections, prefer forEach to unwrapping.

Preferred:

things?.forEach {
    thing.doTheThing()
}

Not Preferred:

if let things = things {
    for thing in things {
        thing.doTheThing()
    }
}

Rationale: While the for-in loop reads more naturally, using forEach to prevent unnecessary levels of nesting helps keep the overall code more readable.


When the loop body is nothing but passing each item in the loop into a closure, function, or method, prefer forEach.

Preferred:

things.forEach(handleTheThing)

Not Preferred:

for thing in things {
    handleTheThing(thing)
}

Rationale: This is more compact, and passing closure arguments into methods that expect closures should feel perfectly natural in Swift.


Prefer iterating over an array slice to any other sort of logic to deal with a specific section of an array.

Preferred:

for thing in things[first...last] {
    thing.doTheThing()
    handleTheThing(thing)
}

Not Preferred:

for index in first.stride(through: last, by: 1)
    let thing = things[index]
    thing.doTheThing()
    handleTheThing(thing)
}

Rationale: In almost all cases, iterating over the array slice will be both more compact source code and more efficient.


Prefer not to pull items out of an array index within a loop. If the index is needed, use the enumerated() method.

Preferred:

for (index, thing) in things.enumerated() {
    print("Found \(thing) at index \(index)")
}

Not Preferred:

for index in 0..<things.count {
    print("Found \(things[index]) at index \(index)")
}

Classes and Structures

When deciding between using a class or a struct, it is important to keep in mind that structs are value types and classes are reference types. We need to understand the implications of using a value type versus a reference type when deciding what type to use.

As a general rule, using structs will be safer when passing the data to multiple sources or when dealing with a multithreaded environment. Because structs are a value type, their value is copied, so they cannot be mutated outside of the scope they are used in. With structs, there is far less need to worry about memory leaks or race conditions.

However, because structs are value types, there is a performance hit when passing large structs around. The larger the struct, the more data needs to be copied when we pass it. Because classes are reference types, we are only passing a pointer to that class, so the performance is the same when passing classes, no matter the size of the class.

However, we should be careful not to default to choosing classes for this reason alone. The memory issues and potential for race conditions that we have to deal with and protect against with classes is arguably harder to debug and resolve. And we shouldn't optimize for performance until performance has been measured and proven to be a problem.

It is often best to default to structs, only falling back to classes when you specifically require the functionality they provide (i.e. multiple references to the same object). This is due to struct's overall greater safety and efficiency. However, it is also important to understand the use cases for both. While structs and classes are overall very similar, structs are more-so designed to encapsulate a small amount of values while classes are well-suited to represent custom data constructs and when you wish to represent or link to an external resource (such as files or services.) And keep in mind with protocols & protocol extensions, we can deal with a lot of inheritance semantics even with structs.


Avoid explicit use of self except where required.

Preferred:

func setUpUI() {
    view.backgroundColor = UIColor.blue()
}

Not Preferred:

func setUpUI() {
    self.view.backgroundColor = UIColor.blue()
}

Rationale: Omitting self allows for more concise code. Only use it when a local variable has hidden an instance variable or in escaping closures.


Closures

When calling a method with a single closure as the last argument, leave it outside the parenthesis.

Preferred:

UIView.animate(withDuration: animationDuration) {
    self.myView.alpha = 0
}

Not Preferred:

UIView.animate(withDuration: animationDuration, animations: {
    self.myView.alpha = 0
})

Rationale: By leaving the closure out of the parenthesis, it makes the code cleaner. This style also allows the creation of functions that look & feel like extensions of the Swift language itself.


When calling a method with multiple closure arguments, include all of the closures within the parenthesis.

Preferred:

UIView.animate(withDuration: animationDuration, animations: {
        self.myView.alpha = 0
    },
    completion: { _ in
        self.myView.removeFromSuperview()
    }
)

Not Preferred:

UIView.animate(withDuration: animationDuration, animations: {
        self.myView.alpha = 0
    }) { _ in
        self.myView.removeFromSuperview()
}

Rationale: The }) { syntax can be very confusing, and this makes the code slightly less readable compared to passing both closures in with their named arguments. In general, we should simply prefer to avoid functions that take multiple closures, where possible.


For single expression arguments, use implicit returns if the context is clear.

Preferred:

let filteredRestaurants = restaurants.filter { $0.id == searchID }

Not Preferred:

let filteredRestaurants = restaurants.filter { restaurant in
    return restaurant.id == searchID
}

Rationale: When the context is clear, the implicit return allows more compact code without sacrificing any readability.

Types

Let Swift implicitly decide which type to use except when you require a type different from Swift's default inference.

Preferred:

let count = 100
let width: CGFloat = 80.0
let name = person.name

Not Preferred:

let count: Int = 100
let width: CGFloat = 80.0
let name: String = person.name

Rationale: Swift type inference system is well documented. Adding types where unnecessary adds clutter to the source code.


Prefer Swift native types to Objective-C's types.

Preferred:

var people = [Person]()

Not Preferred:

var people = NSMutableArray()

Rationale: In the case of collections, preferring Swift-native types allows for more type-safety, one of the biggest advantages Swift has over Objective-C. In the case of other types, Swift is optimized for its native types. Almost (if not all) of the behavior is bridged across both types, and whenever you absolutely must have the Objective-C type, you can freely cast into it.


Constants

Prefer constants to variables. Use let as your default declaration keyword, and only change to var when you know the value of a variable will change.

Preferred:

let minimumPasswordLength: Int

Not Preferred:

var minimumPasswordLength: Int

Rationale: Using let for variables which should not change can prevent bugs and keep the code's intent more clear.


Optionals

Avoid implicitly unwrapped optionals.

Preferred:

var profilePicture: UIImage?

Not Preferred:

var profilePicture: UIImage!

Rationale: Implicitly unwrapped optionals can lead to all sorts of problems and crashes. A little bit of extra unwrapping code is worth knowing that you won't see crashes from unwrapping nil.


Do not force unwrap optionals. If crashing is the correct behavior when the optional is nil, prefer a fatalError with a message.

Preferred:

guard let unwrapped = someOptional else {
    fatalError("Some helpful debugging message describing the circumstance.")
}

Not Preferred:

let unwrapped = someOptional!

Rationale: An extra line of code here can save plenty of headaches when debugging.


If a value should never be nil, prefer a non-optional.

Preferred:

class Person {
    let name: String
}

Not Preferred:

class Person {
    // name should never be nil
    var name: String?
}

Rationale: Using optionals for values that really never should be nil makes code unclear. It can lead to bugs or crashes, and it makes it harder for other devs to understand your code. Also, using optionals has an added unwrapping overhead. Using optionals for values that should never be nil is usually a sign of problems elsewhere in the code.


When you need to unwrap an optional into a non-constant, prefer if var and guard var.

Preferred:

if var volume = volume {
    // use mutable volume
}

Not Preferred:

if let volume = volume {
    var vol = volume
    // use mutable vol
}

Rationale: Using if var and guard var prevents muddying the current scope with unnecessary variable declarations.


When you need to unwrap a variable from a higher scope, prefer simply shadowing the variable name.

Preferred:

var imageURL: URL?

// later...
guard let imageURL = imageURL else { /*...*/ }

Not Preferred:

var imageURL: URL?

// later...
guard let unwrappedImageURL = imageURL else { /*...*/ }

Rationale: This prevents cluttering the current scope with more any more variables than we actually need, and makes the code slightly easier to read.


When unwrapping multiple variables, prefer the same if let or guard let clause.

Preferred:

if let person = people.first, let profileImageURL = person.profileImageURL {
    // do something with profileImageURL
}

Not Preferred:

if let person = people.first {
    if let profileImageURL = person.profileImageURL {
        // do something with profileImageURL
    }
}

Rationale: This prevents unnecessary levels of nesting and helps avoid "arrow code".


Type Casting

Do not force cast. If crashing is the correct behavior when a type cast fails, prefer a fatalError with a message.

Preferred:

guard let firstName = json["first_name"] as? String else {
    fatalError("Some helpful debugging message describing the circumstance.")
}

Not Preferred:

let firstName = json["first_name"] as! String

Rationale: An extra line of code here can save plenty of headaches when debugging.


Struct Initializers

Prefer native Swift initializers over the legacy C "make" functions.

Preferred:

let bounds = CGRect(x: 0, y: 0, width: 100, height: 80)
let center = CGPoint(x: 50, y: 50)

Not Preferred:

let bounds = CGRectMake(0, 0, 100, 80)
let center = CGPointMake(50, 50)

Rationale: The named parameters of the Swift initializers make it clear which argument is which, making the code more readable. This will also be consistent with how we initialize all of our other custom structs.


Shorthand

Prefer more concise type declarations.

Preferred:

var people = [Person]()

Not Preferred:

var people: Array<Person> = Array<Person>()

Rationale: The more compact version is easier to read, and no information is lost.


Typealiasing

Use typealiases to make your code more clear. This is especially true when dealing with units of measure.

Preferred:

typealias Miles = Double

func travel(distance: Miles) {}

Not Preferred:

func travel(distance: Double) {}

Rationale: Using typealiases makes our code self-documenting. In the above example, without the typealias, it could be hard to figure out whether we're passing in the correct unit of measure, or whether we need to do a conversion before calling the method. With the typealias, it is made clear that we must pass distance in as a measure of Miles.

Language

Code should be written in US English.

Preferred:

var color: UIColor

Not Preferred:

var colour: UIColor

Rationale: For consistency with the existing frameworks we use in iOS development, we should use the American spellings.

Swiftlint

Metova uses swiftlint to enforce as much of this style guide as possible across all Swift projects. This repository also contains the swiftlint configuration file Metova uses for its projects.

Credits

This style guide is a collaborative effort from members of the Metova iOS team.

You can find the Metova Team on Stack Overflow.

Releases

No releases published

Packages

No packages published