Skip to content

adessoTurkey/swift-coding-style-guide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 

Repository files navigation

Swift Coding Style Guide

The coding standards adopted by adesso Turkey for the iOS platform.

Table of Contents

DRY (Don't Repeat Yourself)

The simple meaning of DRY is don’t write the same code repeatedly.

  • (link) Instead of preventing code repetition and calling the same function more than once, we should prefer the following method.

Preferred

let message = isPositionCorrect ? "Position Correct" : "Position InCorrect"
updateUI(message, isPositionCorrect)

Not Preffered

let isPositionCorrect = false
if isPositionCorrect {
    updateUI("Position Correct", isPositionCorrect)
} else {
    updateUI("Position InCorrect", isPositionCorrect)
}
  • (link) By creating an extension on ShowAlert protocol, all conforming types automatically gain showAlert() method implementation without any additional modification.

Preferred

protocol ShowingAlert {
    func showAlert()
}

extension ShowingAlert where Self: UIViewController {
    func showAlert() {
        // ...
    }
}

class LoginViewController: ShowingAlert { }
class HomeViewController: ShowingAlert { }

Not Preffered

class LoginViewController {
    func showAlert() {
        // ...
    }
}

class HomeViewController: ShowingAlert { 
    func showAlert() {
        // ...
    }
}
  • (link) Extract code snippets with the same job into a single function.

Preferred

func sum(a: Int, b: Int) -> Int { return a + b }

func calculateTwoProperties() {
    let result = sum(a: firstValue, b: secondValue)
}

Not Preffered

let firstValue: Int = 5
let secondValue: Int = 12

func calculateTwoProperties() {
    let result = firstValue + secondValue
}

Use Early Exit

  • (link) In functions that take parameters with early exit instead of wrapping the source code in an if loop statement, the condition that the loop is not executed should be added first, and if this is the case, it should return.

Preferred

func function(items: [Int]) {
    guard !items.isEmpty else { return }
    for item in items {
        // do something
    }
}

Not Preffered

func function(items: [Int]) {
    if items.count > 0 {
        for item in items {
            // do something
        }
    }
}

Write Shy(Law Of Demeter) Code

  • (link) Write shy code that makes objects loosely coupled. Write everything with the smallest scope possible and only increase the scope if it really needs to.

Preferred

protocol PrefferedVMProtocol {
    func changeUserName(name: String)
}

struct User {
    var name: String?
}

class PrefferedVM: PrefferedVMProtocol {
    private var user: User
    
    init(user: User) {
        self.user = user
    }
    
    func changeUserName(name: String) {
        user.name = name
    }
}

let viewModel: PrefferedVMProtocol = PrefferedVM(user: User(name: "Test"))
viewModel.changeUserName(name: "Preffered")

Not Preffered

class NotPrefferedVM {
    var user: User
    
    init(user: User) {
        self.user = user
    }
}

let viewModel = NotPrefferedVM(user: User(name: "Test"))
viewModel.user.name = "Not Preffered"

Minimal Imports

  • (link) Import only the modules a source file requires.

Preferred

import UIKit
var textField: UITextField
var numbers: [Int]

Not Preffered

import UIKit
import Foundation
var textField: UITextField
var numbers: [Int]

Preffered

import Foundation
var numbers: [Int]

Not Preffered

import UIKit
var numbers: [Int]

Protocol Declarations

Protocol Conformance

When adding protocol conformance to a model, separate each into an extension and use // MARK: - comment.

Preferred

class MyViewController: UIViewController {
  // class stuff
}

// MARK: - UITableViewDataSource
extension MyViewController: UITableViewDataSource {
  // table view data source
}

// MARK: - UIScrollViewDelegate
extension MyViewController: UIScrollViewDelegate {
  // scroll view delegate
}

Not Preferred

class MyViewController: UIViewController, UITableViewDataSource, UIScrollViewDelegate {
  // all
}

Function Declarations

  • (link) Keep function declarations short, if long, then use a line break after each parameter.

Preferred

func calculateCost(quantity: Int,
                   realPrice: Double,
                   discountRate: Double,
                   taxRate: Double,
                   serviceCharge: Double) -> Double {
    ...
}

Not Preferred

func calculateCost(quantity: Int, realPrice: Double, discountRate: Double, taxRate: Double, serviceCharge: Double) -> Double {
    ...
}
  • (link) Invoke functions with a long declaration by using a line break for each parameter.

Preferred

let result = calculateCost(quantity: 5,
                           realPrice: 100,
                           discountRate: 25,
                           taxRate: 10,
                           serviceCharge: 5)

Not Preferred

let result = calculateCost(quantity: 5, realPrice: 100, discountRate: 25, taxRate: 10, serviceCharge: 5)

Not Preferred

let result = calculateCost(
    quantity: 5,
    realPrice: 100,
    discountRate: 25,
    taxRate: 10,
    serviceCharge: 5)
  • (link) Use prepositions or assistive words in the parameter naming instead of function naming.

Preferred

func displayPopup(with message: String) {
    ...
}

Not Preferred

func displayPopupWith(message: String) {
    ...
}
  • (link) Avoid from Void return type.

Preferred

func someMethod() {
    ...
}

Not Preferred

func someMethod() -> Void {
    ...
}

Closure Expressions

  • (link) Use an underscore (_) for the name of the unused closure parameter.

Preferred

// Only `data` parameter is used
someCompletion { data, _, _ in
    handle(data)
}

Not Preferred

// `error` and `succeeded` parameters are unused
someCompletion { data, error, succeeded in
    handle(data)
}
  • (link) Use trailing closure syntax when a function has only one closure parameter that is at the end of the argument list.

Preferred

someMethod(options: [.option1]) { result in
    print(result)
}
        
otherMethod(options: [.option1],
            action: { index in
            print(index)
},
            completion: { result in
    print(result)
})

Not Preferred

someMethod(options: [.option1], completion: { result in
    print(result)
})
        
otherMethod(options: [.option1], action: { index in
    print(index)
}) { result in
    print(result)
}
  • (link) Use space or line break inside and outside of the closure (if necessary) to increase readability.

Preferred

let activeIndices = items.filter { $0.isActive }.map { $0.index }

let activeIndices = items.filter({ $0.isActive }).map({ $0.index })

let activeIndices = items
    .filter { $0.isActive }
    .map { $0.index }

let activeIndices = items
    .filter {
        $0.isActive
    }
    .map {
        $0.index
    }

Not Preferred

let activeIndices = items.filter{$0.isActive}.map{$0.index}

let activeIndices = items.filter( { $0.isActive } ).map( { $0.index } )

let activeIndices = items
    .filter{$0.isActive}
    .map{$0.index}

let activeIndices = items
    .filter{
        $0.isActive
    }
    .map{
        $0.index
    }
  • (link) Don't use empty parentheses () when single parameter of a function is closure.

Preferred

func someClosure { result in
    ...
}

Not Preferred

func someClosure() { result in
    ...
}
  • (link) Avoid from Void return type.

Preferred

func someClosure { result in
    ...
}

Not Preferred

func someClosure { result -> Void in
    ...
}

Memory Management

A memory leak must not be created in the source code. Retain cycles should be prevented by using either weak and unowned references. In addition, value types (e.g., struct, enum) can be used instead of reference types (e.g., class).

  • (link) Always use [weak self] or [unowned self] with guard let self = self else { return }.

Preferred

someMethod { [weak self] someResult in
  guard let self else { return } // Check out 'If Let Shorthand'
  let result = self.updateResult(someResult)
  self.updateUI(with: result)
}

Not Preferred

// Deallocation of self might occur between `let result = self?.updateResult(someResult)` and `self?.updateUI(with: result)`
someMethod { [weak self] someResult in
  let result = self?.updateResult(someResult)
  self?.updateUI(with: result)
}
  • (link) Use [unowned self] where the object can not be nil and 100% sure that object's lifetime is less than or equal to self. However, [weak self] should be preferred to [unowned self].

Preferred

someMethod { [weak self] someResult in
  guard let self else { return } // Check out 'If Let Shorthand'
  let result = self.updateResult(someResult)
  self.updateUI(with: result)
}

Not Preferred

// self may be deallocated inside closure
someMethod { [unowned self] someResult in
  guard let self else { return } // Check out 'If Let Shorthand'
  let result = self.updateResult(someResult)
  self.updateUI(with: result)
}

If Let Shorthand

Since (Swift 5.7), we can simplfy the if-let & guard let blocks.

Preferred

if let value {
  print(value)
}

Not Preferred

if let value = value {
  print(value)
}

Preferred

someMethod { [weak self] someResult in
  guard let self else { return }
  let result = self.updateResult(someResult)
  self.updateUI(with: result)
}

Not Preferred

someMethod { [weak self] someResult in
  guard let self = self else { return }
  let result = self.updateResult(someResult)
  self.updateUI(with: result)
}

License

Copyright 2020 adesso Turkey

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Releases

No releases published

Packages