Skip to content

Latest commit

 

History

History
1363 lines (1064 loc) · 29.1 KB

README.md

File metadata and controls

1363 lines (1064 loc) · 29.1 KB

Swift-Coding-Style-Guide

The aim of this guide is to help developers in writing clean, concise, beautiful and easy to read Swift codes. The main goals:

  • to make it hard to write programmer errors, or at least make them hard to miss
  • to increase readability and clarity of intent
  • to minimize unnecessary code bloat
  • to have a nice look of code

Table of Contents

Styles and Conventions

Formatting

Semicolons

Trailing semicolons (;) are not nessesary any more.

Cause: This is Swift

  • Preferred
self.backgroundColor = UIColor.whiteColor()
self.completion = { 
    // ...
}
  • Not Preferred
self.backgroundColor = UIColor.whiteColor();
self.completion = { 
    // ...
};

Whitespaces

Use 4 spaces for tabs.

It's preferred to use tab to indent the code instead of using spaces. Tab setting could be set at Xcode's Text Editing settings. As following: tab setting

All source files should end with a single trailing newline (only).

Cause: This prevents no-trailing-newline errors and reduces noise in commit diffs.

  • Preferred
class Button {
  // ...
}
// <-- One line here
  • Not Preferred
class Button {
  // ...
} // <-- No new line after
class Button {
  // ...
}
// <-- One line here
// <-- Another line here

All functions should be at least one empty line apart each other.

Cause: Gives breathing room between code blocks.

  • Preferred
class BaseViewController: UIViewController {
    // ...
    
    override viewDidLoad() {
        // ...
    }
    
    override viewWillAppear(animated: Bool) {
        // ...
    }
}

Use single spaces around operator definitions and operator calls.

Cause: Readability

  • Preferred
func <| (lhs: Int, rhs: Int) -> Int {
    // ...
}

let value = 1 <| 2
  • Not Preferred
func <|(lhs: Int, rhs: Int) -> Int {
    // ...
}

let value = 1<|2

Use single spaces around return arrows (->) both in functions and in closures.

Cause: Readability

  • Preferred
func doSomething(value: Int) -> Int {
    // ...
}
  • Not Preferred
func doSomething(value: Int)->Int {
    // ...
}

Commas

Commas (,) should have no whitespace before it, and should have either one space or one newline after.

Cause: Keeps comma-separated items visually separate.

  • Preferred
let array = [1, 2, 3]
self.presentViewController(
    controller,
    animated: true,
    completion: nil
)
  • Not Preferred
let array = [1,2,3]
let array = [1 ,2 ,3]
let array = [1 , 2 , 3]
self.presentViewController(
    controller ,
    animated: true,completion: nil
)

Colons

Colons (:) used to indicate type should have one space after it and should have no whitespace before it.

Cause: The colon describes the object to its left, not the right.

  • Preferred
func createItem(item: Item)
var item: Item? = nil
  • Not Preferred
func createItem(item:Item)
func createItem(item :Item)
func createItem(item : Item)
var item:Item? = nil
var item :Item? = nil
var item : Item? = nil

Colons (:) for case statements should have no whitespace before it, and should have either one space or one newline after it.

Cause: Same as he previous rule, the colon describes the object to its left, not the right.

  • Preferred
switch result {

case .Success:
    self.completion()
    
case .Failure:
    self.failure()
}
  • Not Preferred
switch result {

case .Success :
    self.completion()
    
case .Failure:self.reportError()
}

Braces

Open braces ({) should be one space following the previous non-whitespace character.

Cause: Separates the brace from the declaration.

  • Preferred
class Icon {
    // ...
}
let block = { () -> Void in
    // ...
}
  • Not Preferred
class Icon{
    // ...
}
let block ={ () -> Void in
    // ...
}

Open braces ({) for type declarations, functions, and closures should be followed by one empty line. Single-statement closures can be written in one line.

Cause: Gives breathing room when scanning for code.

  • Preferred
class Icon {

    let image: UIImage
    var completion: (() -> Void)

    init(image: UIImage) {
    
        self.image = image
        self.completion = { [weak self] in self?.didComplete() }
    }
    
    func doSomething() {
    
        self.doSomethingElse()
    }
}
  • Not Preferred
class Icon {
    let image: UIImage

    init(image: UIImage) {
        self.image = image
        self.completion = { [weak self] in print("done"); self?.didComplete() }
    }
    
    func doSomething() { self.doSomethingElse() }
}

Empty declarations should be written in empty braces ({}), otherwise a comment should indicate the reason for the empty implementation.

Cause: Makes it clear that the declaration was meant to be empty and not just a missing TODO.

  • Preferred
extension Icon: Equatable {}
var didTap: () -> Void = {}

override func drawRect(rect: CGRect) {}

@objc dynamic func controllerDidChangeContent(controller: NSFetchedResultsController) {

    // do nothing; delegate method required to enable tracking mode
}
  • Not Preferred
extension Icon: Equatable {
}
var didTap: () -> Void = { }

override func drawRect(rect: CGRect) {
}

@objc dynamic func controllerDidChangeContent(controller: NSFetchedResultsController) {
    
}

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.

Cause: Provides breathing room between declarations while keeping code compact.

  • Preferred
class Button {

    var didTap: (sender: Button) -> Void = { _ in }

    func tap() {
    
        self.didTap()
    }
}
  • Not Preferred
class Button {

    var didTap: (sender: Button) -> Void = {_ in}

    func tap() {
    
        self.didTap()
        
    }
    
}

Close braces (}) unless on the same line as its corresponding open brace ({), should be left-aligned with the statement that declared the open brace.

Cause: 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.

  • Preferred
lazy var largeImage: UIImage = { () -> UIImage in

    let image = // ...
    return image
}()
  • Not Preferred
lazy var largeImage: UIImage = { () -> UIImage in

    let image = // ...
    return image
    }()

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.

Cause: Combined with the rules on braces, this formatting provides very good consistency and scannability.

  • Preferred
struct Rectangle {

    // ...
    var right: Float {
    
        get {
        
            return self.x + self.width
        }
        set {
        
            self.x = newValue - self.width
        }
    }
}
struct Rectangle {

    // ...
    var right: Float {
    
        get { return self.x + self.width }
        set { self.x = newValue - self.width }
    }
}
  • Not Preferred
struct Rectangle {

    // ...
    var right: Float {
    
        get
        {
            return self.x + self.width
        }
        set
        {
            self.x = newValue - self.width
        }
    }
}
struct Rectangle {

    // ...
    var right: Float {
    
        get { return self.x + self.width }
        set { self.x = newValue - self.width
            print(self) 
        }
    }
}

Read-only computed properties should ommit the get clause.

Cause: The return statement provides enough clarity that lets us use the more compact form.

  • Preferred
struct Rectangle {

    // ...
    var right: Float {
    
        return self.x + self.width
    }
}
  • Not Preferred
struct Rectangle {

    // ...
    var right: Float {
    
        get {
        
            return self.x + self.width
        }
    }
}

Control Flow Statements

if, else, switch, do, catch, repeat, guard, for, while, and defer statements should be left-aligned with their respective close braces (}).

Cause: 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.

  • Preferred
if array.isEmpty {
    // ...
}
else {
    // ...
}

or

if array.isEmpty {
    // ...
} else {
    // ...
}

Personally, I like the second way

  • Not Preferred
if array.isEmpty
{
    // ...
}
else
{
    // ...
}

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.

Cause: Reliance on Xcode's auto-indentation. For multi-line statements, separating cases with empty lines enhance visual separation.

  • Preferred
switch result {

case .Success:
    self.doSomething()
    self.doSomethingElse()
    
case .Failure:
    self.doSomething()
    self.doSomethingElse()
}
switch result {

case .Success: self.doSomething()
case .Failure: self.doSomethingElse()
}
  • Not Preferred
switch result {

case .Success: self.doSomething()
               self.doSomethingElse()
case .Failure: self.doSomething()
               self.doSomethingElse()
}
switch result {

    case .Success: self.doSomething()
    case .Failure: self.doSomethingElse()
}

Conditions for if, switch, for, and while statements should not be enclosed in parentheses (()).

Cause: Do coding Swift way

  • Preferred
if array.isEmpty {
    // ...
}
  • Not Preferred
if (array.isEmpty) {
    // ...
}

Try to avoid nesting statements by returning early when possible.

Cause: The more nested scopes to keep track of, the heavier the burden of scanning code.

  • Preferred
guard let strongSelf = self else {

    return
}
// do many things with strongSelf
  • Not Preferred
if let strongSelf = self {

    // do many things with strongSelf
}

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.

Cause: Adopt Apple's naming rules for uniformity.

  • Preferred
class ImageButton {

    enum ButtonState {
        // ...
    }
}
  • Not Preferred
class image_button {

    enum buttonState {
        // ...
    }
}

enum values and OptionSetType values should be in UpperCamelCase.

Cause: Adopt Apple's naming rules for uniformity.

  • Preferred
enum ErrorCode {
    
    case Unknown
      case NetworkNotFound
      case InvalidParameters
}

struct CacheOptions : OptionSetType {

    static let None = CacheOptions(rawValue: 0)
    static let MemoryOnly = CacheOptions(rawValue: 1)
    static let DiskOnly = CacheOptions(rawValue: 2)
    static let All: CacheOptions = [.MemoryOnly, .DiskOnly]
    // ...
}
  • Not Preferred
enum ErrorCode {
    
    case unknown
      case network_not_found
      case invalidParameters
}

struct CacheOptions : OptionSetType {

    static let none = CacheOptions(rawValue: 0)
    static let memory_only = CacheOptions(rawValue: 1)
    static let diskOnly = CacheOptions(rawValue: 2)
    static let all: CacheOptions = [.memory_only, .diskOnly]
    // ...
}

Variables and functions should be in lowerCamelCase, including statics and constants. An exception is acronyms, which should be UPPERCASE.

Cause: Adopt Apple's naming rules for uniformity. As for acronyms, the readability makes keeping them upper-case worth it.

  • Preferred
var webView: UIWebView?
var URLString: String?

func didTapReloadButton() {
    // ..
}
  • Not Preferred
var web_view: UIWebView?
var urlString: String?

func DidTapReloadButton() {
    // ..
}

Semantics

Avoid single-character names for types, variables, and functions. The only place they are allowed is as indexes in iterators.

Cause: There is always a better name than single-character names. Even with i, it is still more readable to use index instead.

  • Preferred
for (i, value) in array.enumerate() {
    // ... "i" is well known
}
  • Not Preferred
for (i, v) in array.enumerate() {
    // ... what's "v"?
}

Avoid abbreviations as much as possible. (although Acceptable Abbreviations and Acronyms are allowed such as min/max).

Cause: Clarity is prioritized over slight brevity.

  • Preferred
let errorCode = error.code
  • Not Preferred
let err = error.code

Choose a name that communicates as much information about what it is and what it's for.

Cause: Clarity is prioritized over slight brevity. Also, the more specific the name, the less likely they are to collide with other symbols.

  • Preferred
class Article {

    var title: String
}
class NewsArticle {

    var headlineTitle: String
}
  • Not Preferred
class Article {

    var text: String
    // is this the title or the content text?
}

When pertaining to URLs, distinguish strings from actual NSURLs by appending the suffix ~String.

Cause: Saves a few seconds checking header declarations for the correct type.

  • Preferred
var requestURL: NSURL
var sourceURLString: String

func loadURL(URL: NSURL) {
    // ...
}

func loadURLString(URLString: String) {
    // ...
}
  • Not Preferred
var requestURL: NSURL
var sourceURL: String

func loadURL(URL: NSURL) {
    // ...
}

func loadURL(URL: String) {
    // ...
}

Do not pertain to constructs (class, struct, enum, protocol, etc.) in their names.

Cause: 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.

  • Preferred
class User {
    // ...
}

enum Result {
    // ...
}

protocol Queryable {
    // ...
}
  • Not Preferred
class UserClass {
    // ...
}

enum ResultEnum {
    // ...
}

protocol QueryableProtocol {
    // ...
}

Dependencies

Import Statements

import statements for OS frameworks and external frameworks should be separated and alphabetized.

Cause: Reduce merge conflicts when dependencies change between branches.

  • Preferred
import Foundation
import UIKit

import Alamofire
import Cartography
import SwiftyJSON
  • Not Preferred
import Foundation
import Alamofire
import SwiftyJSON
import UIKit
import Cartography

Declaration

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.

Cause: Makes it easy to locate where in the source code certain properties and functions are declared.

  • Preferred
// MARK: - BaseViewController

class BaseViewController: UIViewController, UIScrollViewDelegate {


    // MARK: Internal
    
    weak var scrollView: UIScrollView?
    
    
    // MARK: UIViewController
    
    override func viewDidLoad() {
        // ...
    }
    
    override func viewWillAppear(animated: Bool) {
        // ...
    }
    
    
    // MARK: UIScrollViewDelegate
    
    @objc dynamic func scrollViewDidScroll(scrollView: UIScrollView) {
        // ...
    }
    
    
    // MARK: Private
    
    private var lastOffset = CGPoint.zero
}

All // MARK: tags should have two empty lines above and one empty line below.

Cause: Aesthetic. Gives breathing room between type declarations and function groups.

  • Preferred
import UIKit


// MARK: - BaseViewController

class BaseViewController: UIViewController {


    // MARK: Internal
    
    weak var scrollView: UIScrollView?
    
    
    // MARK: UIViewController
    
    override func viewDidLoad() {
        // ...
    }
    
    override func viewWillAppear(animated: Bool) {
        // ...
    }
    
    
    // MARK: Private
    
    private var lastOffset = CGPoint.zero
}
  • Not Preferred
import UIKit
// MARK: - BaseViewController
class BaseViewController: UIViewController {

    // MARK: Internal
    weak var scrollView: UIScrollView?
    
    // MARK: UIViewController
    override func viewDidLoad() {
        // ...
    }
    
    override func viewWillAppear(animated: Bool) {
        // ...
    }
    
    // MARK: Private
    private var lastOffset = CGPoint.zero
}

The groupings for // MARK: tags should be ordered as follows:

Cause: 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.

  • // 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

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

@ 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.

Cause: 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.

  • Preferred
let leftMargin: CGFloat = 20
view.frame.x = leftMargin
@objc dynamic func tableView(tableView: UITableView,
 heightForHeaderInSection section: Int) -> CGFloat {

    return 0.01 // tableView ignores 0
}
  • Not Preferred
view.frame.x = 20 // left margin
@objc dynamic func tableView(tableView: UITableView,
 heightForHeaderInSection section: Int) -> CGFloat {

    return 0.01 // return small number
}

All temporary, unlocalized strings should be marked with // TODO: localize

Cause: 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.

  • Preferred
self.titleLabel.text = "Date Today:" // TODO: localize
  • Not Preferred
self.titleLabel.text = "Date Today:"

Protection from Dynamism

All Objective-C protocol implementations, whether properties or methods, should be prefixed with @objc dynamic

Cause: Prevents horrible compiler optimization bugs.

  • Preferred
@objc dynamic func scrollViewDidScroll(scrollView: UIScrollView) {
    // ...
}
  • Not Preferred
func scrollViewDidScroll(scrollView: UIScrollView) {
    // ...
}

All IBActions and IBOutlets should be declared dynamic

Cause: Xcode automatically generates this for us when we drag&drop from storyboard to viewcontroller

  • Preferred
@IBOutlet private dynamic weak var closeButton: UIButton?

@IBAction private dynamic func closeButtonTouchUpInside(sender: UIButton) {
    // ...
}

All @IBOutlets should be declared weak. They should also be wrapped as Optional, not ImplicitlyUnwrappedOptional.

Cause: 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(_:).

  • Preferred
@IBOutlet dynamic weak var profileIcon: UIImageView?
  • Not Preferred
@IBOutlet var profileIcon: UIImageView!

Access Modifiers

Design declarations as private by default and only expose as internal or public as the needs arise.

Cause: 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.

Cause: Makes the intent clear for API consumers.

  • Preferred
private let defaultTimeout: NSTimeInterval = 30

internal class NetworkRequest {
    // ...
}
  • Not Preferred
let defaultTimeout: NSTimeInterval = 30

class NetworkRequest {
    // ...
}

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.

Cause: 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.

  • Preferred
private let someGlobal = "someValue"

class AppDelegate {
    // ...
    private var isForeground = false
}
  • Not Preferred
public let someGlobal = "someValue"

public class AppDelegate {
    // ...
    var isForeground = false
}

Access modifiers should be written before all other non-@ modifiers.

Combined with the rules on declaration order, this improves readability when scanning code vertically*

  • Preferred
@objc internal class User: NSManagedObject {
    // ...
    @NSManaged internal dynamic var identifier: Int
    // ...
    @NSManaged private dynamic var internalCache: NSData?
}
  • Not Preferred
internal @objc class User: NSManagedObject {
    // ...
    @NSManaged dynamic internal var identifier: Int
    // ...
    private @NSManaged dynamic var internalCache: NSData?
}

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.

Cause: Prevent redundancy. This also reduces ambiguity when binding to generic types.

  • Preferred
var backgroundColor = UIColor.whiteColor()
var iconView = UIImageView(image)
var lineBreakMode = NSLineBreakMode.ByWordWrapping
// or
var lineBreakMode: NSLineBreakMode = .ByWordWrapping
  • Not Preferred
var backgroundColor: UIColor = UIColor.whiteColor()
var iconView: UIImageView = UIImageView(image)
var lineBreakMode: NSLineBreakMode = NSLineBreakMode.ByWordWrapping

When literal types are involved (StringLiteralConvertible, NilLiteralConvertible, etc), it is encouraged to specify the type explicitly and is preferrable over casting with as directly.

Cause: Prevent redundancy. This also reduces ambiguity when binding to generic types.

  • Preferred
var radius: CGFloat = 0
var length = CGFloat(0)
  • Not Preferred
var radius: CGFloat = CGFloat(0)
var length = 0 as CGFloat // prefer initializer to casts

Collections / SequenceTypes

.count should only be used when the count value itself is needed

  • Preferred
let badgeNumber = unreadItems.count

Checking if empty or not:

if sequence.isEmpty {
// ...
  • Not Preferred
if sequence.count <= 0 {
// ...

Getting the first or last item:

  • Preferred
let first = sequence.first
let last = sequence.last
  • Not Preferred
let first = sequence[0]
let last = sequence[sequence.count - 1]

Removing the first or last item:

  • Preferred
sequence.removeFirst()
sequence.removeLast()
  • Not Preferred
sequence.removeAtIndex(0)
sequence.removeAtIndex(sequence.count - 1)

Iterating all indexes:

  • Preferred
for i in sequence.indices {
    // ...
}
  • Not Preferred
for i in 0 ..< sequence.count {
    // ...
}

Getting the first or last index:

  • Preferred
let first = sequence.indices.first
let last = sequence.indices.last
  • Not Preferred
let first = 0
let last = sequence.count - 1

Iterating all indexes except the last n indexes:

  • Preferred
for i in sequence.indices.dropLast(n) {
    // ...
}
  • Not Preferred
for i in 0 ..< (sequence.count - n) {
    // ...
}

Iterating all indexes except the first n indexes:

  • Preferred
for i in sequence.indices.dropFirst(n) {
    // ...
}
  • Not Preferred
for i in n ..< sequence.count {
    // ...
}

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.

For all non-@noescape and non-animation closures, accessing self within the closure requires a [weak self] declaration.

*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]. *

  • Preferred
self.request.downloadImage(
    url,
    completion: { [weak self] image in

        self?.didDownloadImage(image)
    }
)
  • Not Preferred
self.request.downloadImage(
    url,
    completion: { image in

        self.didDownloadImage(image) // hello retain cycle
    }
)

Never use unowned to capture references in closures.

Cause: 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.

  • Preferred
self.request.downloadImage(
    url,
    completion: { [weak self] image in

        self?.didDownloadImage(image)
    }
)
  • Not Preferred
self.request.downloadImage(
    url,
    completion: { [unowned self] image in

        self.didDownloadImage(image)
    }
)

If the validity of the weak self in the closure is needed, bind using the variable `self` to shadow the original.

Cause: Write the nice looking code

  • Preferred
self.request.downloadImage(
    url,
    completion: { [weak self] image in

        guard let `self` = self else { 
        
            return
        }
        self.didDownloadImage(image)
        self.reloadData()
        self.doSomethingElse()
    }
)
  • Not Preferred
self.request.downloadImage(
    url,
    completion: { [weak self] image in

        guard let strongSelf = self else { 
        
            return
        }
        strongSelf.didDownloadImage(image)
        strongSelf.reloadData()
        strongSelf.doSomethingElse()
    }
)

Back to top

License

See the LICENSE file for more info.