Swift Conventions Highlight

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 30

swift-conventions-highlight.

md 11/27/2019

Swift Conventions Highlight.


Table of Contents
1. BundleID
2. Naming
2.1 Delegates
2.2 Use Type Inferred Context
2.3 Generics
2.4 Language
3. Code Organization
3.1 Protocol Conformance
3.2 Unused Code
3.3 Minimal Imports
4. Spacing
5. Classes and Structures
5.1 Computed Properties
6. Function Declarations
7. Closure Expressions
8. Types
8.1 Constants
8.2 Optionals
8.3 Type Inference
8.4 Syntactic Sugar
9. Functions vs Methods
10. Access Control
11. Control Flow
11.1 Ternary Operator
12. Golden Path
13. Semicolons
14. Parentheses
15. Multi-line String Literals
16. Extras
16.1 Musts
16.1.1 Native Swift Types
16.1.2 Optionals
16.1.3 Error Handling
16.1.4 Access Control
16.1.5 Protocols
16.1.6 Arrays and Dictionaries
16.1.7 Flow Control
16.1.8 Switch Statements
16.1.9 Use Implicit Getters
16.2 Shoulds
16.2.1 Declaring Variables

1 / 30
swift-conventions-highlight.md 11/27/2019

16.2.2 Optionals
16.2.3 Spacing
16.2.4 Loops
16.2.5 Closures

1. BundleID

Format: asia.vitalify.*

2. Naming
2.1 Delegates

Preferred:

func namePickerView(_ namePickerView: NamePickerView, didSelectName name:


String)
func namePickerViewShouldReload(_ namePickerView: NamePickerView) -> Bool

Not Preferred:

func didSelectName(namePicker: NamePickerViewController, name: String)


func namePickerShouldReload() -> Bool

2.2 Use Type Inferred Context

Preferred:

let selector = #selector(viewDidLoad)


view.backgroundColor = .red
let toView = context.view(forKey: .to)
let view = UIView(frame: .zero)

Not Preferred:

let selector = #selector(ViewController.viewDidLoad)


view.backgroundColor = UIColor.red
let toView = context.view(forKey: UITransitionContextViewKey.to)
let view = UIView(frame: CGRect.zero)

2.3 Generics

2 / 30
swift-conventions-highlight.md 11/27/2019

Preferred:

struct Stack<Element> { ... }


func write<Target: OutputStream>(to target: inout Target)
func swap<T>(_ a: inout T, _ b: inout T)

Not Preferred:

struct Stack<T> { ... }


func write<target: OutputStream>(to target: inout target)
func swap<Thing>(_ a: inout Thing, _ b: inout Thing)

2.4 Language

Preferred:

let color = "red"

Not Preferred:

let colour = "red"

3. Code Organization
3.1 Protocol Conformance

Preferred:

class MyViewController: UIViewController {


// class stuff here
}

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

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

3 / 30
swift-conventions-highlight.md 11/27/2019

Not Preferred:

class MyViewController: UIViewController, UITableViewDataSource,


UIScrollViewDelegate {
// all methods
}

3.2 Unused Code

Preferred:

override func tableView(_ tableView: UITableView, numberOfRowsInSection


section: Int) -> Int {
return Database.contacts.count
}

Not Preferred:

override func didReceiveMemoryWarning() {


super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}

override func numberOfSections(in tableView: UITableView) -> Int {


// #warning Incomplete implementation, return the number of sections
return 1
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection


section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return Database.contacts.count
}

3.3 Minimal Imports

Preferred:

import UIKit
var view: UIView
var deviceModels: [String]

Preferred:

4 / 30
swift-conventions-highlight.md 11/27/2019

import Foundation
var deviceModels: [String]

Not Preferred:

import UIKit
import Foundation
var view: UIView
var deviceModels: [String]

Not Preferred:

import UIKit
var deviceModels: [String]

4. Spacing
Indent using 4 spaces rather than tabs to conserve space and help prevent line wrapping

Preferred:

if user.isHappy {
// Do something
} else {
// Do something else
}

Not Preferred:

if user.isHappy
{
// Do something
}
else {
// Do something else
}

Preferred:

class TestDatabase: Database {


var data: [String: CGFloat] = ["A": 1.2, "B": 3.2]
}
5 / 30
swift-conventions-highlight.md 11/27/2019

Not Preferred:

class TestDatabase : Database {


var data :[String:CGFloat] = ["A" : 1.2, "B":3.2]
}

5. Classes and Structures


5.1 Computed Properties

Preferred:

var diameter: Double {


return radius * 2
}

Not Preferred:

var diameter: Double {


get {
return radius * 2
}
}

6. Function Declarations
Preferred:

func updateConstraints() -> Void {


// magic happens here
}

typealias CompletionHandler = (result) -> Void

Not Preferred:

func updateConstraints() -> () {


// magic happens here
}

typealias CompletionHandler = (result) -> ()

6 / 30
swift-conventions-highlight.md 11/27/2019

7. Closure Expressions
Preferred:

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

UIView.animate(withDuration: 1.0, animations: {


self.myView.alpha = 0
}, completion: { finished in
self.myView.removeFromSuperview()
})

Not Preferred:

UIView.animate(withDuration: 1.0, animations: {


self.myView.alpha = 0
})

UIView.animate(withDuration: 1.0, animations: {


self.myView.alpha = 0
}) { f in
self.myView.removeFromSuperview()
}

8. Types
Preferred:

let width = 120.0 // Double


let widthString = "\(width)" // String

Less Preferred:

let width = 120.0 // Double


let widthString = (width as NSNumber).stringValue // String

Not Preferred:

let width: NSNumber = 120.0 // NSNumber


let widthString: NSString = width.stringValue // NSString

7 / 30
swift-conventions-highlight.md 11/27/2019

8.1 Constants

Preferred:

enum Math {
static let e = 2.718281828459045235360287
static let root2 = 1.41421356237309504880168872
}

let hypotenuse = side * Math.root2

Not Preferred:

let e = 2.718281828459045235360287 // pollutes global namespace


let root2 = 1.41421356237309504880168872

let hypotenuse = side * root2 // what is root2?

8.2 Optionals

Preferred:

var subview: UIView?


var volume: Double?

// later on...
if let subview = subview, let volume = volume {
// do something with unwrapped subview and volume
}

// another example
UIView.animate(withDuration: 2.0) { [weak self] in
guard let self = self else { return }
self.alpha = 1.0
}

Not Preferred:

var optionalSubview: UIView?


var volume: Double?

if let unwrappedSubview = optionalSubview {


if let realVolume = volume {
// do something with unwrappedSubview and realVolume
8 / 30
swift-conventions-highlight.md 11/27/2019

}
}

// another example
UIView.animate(withDuration: 2.0) { [weak self] in
guard let strongSelf = self else { return }
strongSelf.alpha = 1.0
}

8.3 Type Inference

Preferred:

let message = "Click the button"


let currentBounds = computeViewBounds()
var names = ["Mic", "Sam", "Christine"]
let maximumWidth: CGFloat = 106.5

Not Preferred:

let message: String = "Click the button"


let currentBounds: CGRect = computeViewBounds()
var names = [String]()

Type Annotation for Empty Arrays and Dictionaries

Preferred:

var names: [String] = []


var lookup: [String: Int] = [:]

Not Preferred:

var names = [String]()


var lookup = [String: Int]()

8.4 Syntactic Sugar

Preferred:

var deviceModels: [String]


var employees: [Int: String]

9 / 30
swift-conventions-highlight.md 11/27/2019

var faxNumber: Int?

Not Preferred:

var deviceModels: Array<String>


var employees: Dictionary<Int, String>
var faxNumber: Optional<Int>

9. Functions vs Methods
Preferred

let sorted = items.mergeSorted() // easily discoverable


rocket.launch() // acts on the model

Not Preferred

let sorted = mergeSort(items) // hard to discover


launch(&rocket)

Free Function Exceptions

let tuples = zip(a, b) // feels natural as a free function (symmetry)


let value = max(x, y, z) // another free function that feels natural

Extending object lifetime

Preferred

resource.request().onComplete { [weak self] response in


guard let self = self else {
return
}
let model = self.updateModel(response)
self.updateUI(model)
}

Not Preferred

10 / 30
swift-conventions-highlight.md 11/27/2019

// might crash if self is released before response returns


resource.request().onComplete { [unowned self] response in
let model = self.updateModel(response)
self.updateUI(model)
}

Not Preferred

// deallocate could happen between updating the model and updating UI


resource.request().onComplete { [weak self] response in
let model = self?.updateModel(response)
self?.updateUI(model)
}

10. Access Control


Preferred:

private let message = "Great Scott!"

class TimeMachine {
private dynamic lazy var fluxCapacitor = FluxCapacitor()
}

Not Preferred:

fileprivate let message = "Great Scott!"

class TimeMachine {
lazy dynamic private var fluxCapacitor = FluxCapacitor()
}

11. Control Flow


Preferred:

for _ in 0..<3 {
print("Hello three times")
}

for (index, person) in attendeeList.enumerated() {


print("\(person) is at position #\(index)")
}

11 / 30
swift-conventions-highlight.md 11/27/2019

for index in stride(from: 0, to: items.count, by: 2) {


print(index)
}

for index in (0...3).reversed() {


print(index)
}

Not Preferred:

var i = 0
while i < 3 {
print("Hello three times")
i += 1
}

var i = 0
while i < attendeeList.count {
let person = attendeeList[i]
print("\(person) is at position #\(i)")
i += 1
}

11.1 Ternary Operator

Preferred:

let value = 5
result = value != 0 ? x : y

let isHorizontal = true


result = isHorizontal ? x : y

Not Preferred:

result = a > b ? x = c > d ? c : d : y

12. Golden Path


Preferred:

func computeFFT(context: Context?, inputData: InputData?) throws ->


Frequencies {

12 / 30
swift-conventions-highlight.md 11/27/2019

guard let context = context else {


throw FFTError.noContext
}
guard let inputData = inputData else {
throw FFTError.noInputData
}

// use context and input to compute the frequencies


return frequencies
}

Not Preferred:

func computeFFT(context: Context?, inputData: InputData?) throws ->


Frequencies {

if let context = context {


if let inputData = inputData {
// use context and input to compute the frequencies

return frequencies
} else {
throw FFTError.noInputData
}
} else {
throw FFTError.noContext
}
}

Preferred:

guard
let number1 = number1,
let number2 = number2,
let number3 = number3
else {
fatalError("impossible")
}
}
// do something with numbers

Not Preferred:

if let number1 = number1 {


if let number2 = number2 {
if let number3 = number3 {
// do something with numbers

13 / 30
swift-conventions-highlight.md 11/27/2019

} else {
fatalError("impossible")
}
} else {
fatalError("impossible")
}
} else {
fatalError("impossible")
}

13. Semicolons
Preferred:

let swift = "not a scripting language"

Not Preferred:

let swift = "not a scripting language";

14. Parentheses
Preferred:

if name == "Hello" {
print("World")
}

Not Preferred:

if (name == "Hello") {
print("World")
}

Preferred:

let playerMark = (player == current ? "X" : "O")

15. Multi-line String Literals


Preferred:

14 / 30
swift-conventions-highlight.md 11/27/2019

let message = """


You cannot charge the flux \
capacitor with a 9V battery.
You must use a super-charger \
which costs 10 credits. You currently \
have \(credits) credits available.
"""

Not Preferred:

let message = """You cannot charge the flux \


capacitor with a 9V battery.
You must use a super-charger \
which costs 10 credits. You currently \
have \(credits) credits available.
"""

Not Preferred:

let message = "You cannot charge the flux " +


"capacitor with a 9V battery.\n" +
"You must use a super-charger " +
"which costs 10 credits. You currently " +
"have \(credits) credits available."

16. Extras
16.1 Musts

16.1.1 Native Swift Types

Preferred

let pageLabelText = "\(currentPage)/\(pageCount)"


let alsoPageLabelText = currentPage + "/" + pageCount

Not Preferred

let pageLabelText = NSString(format: "%@/%@", currentPage, pageCount)

Swift Collection Types

15 / 30
swift-conventions-highlight.md 11/27/2019

Preferred

var arrayOfJSONObjects = [[String: AnyObject]]()


...
let names: AnyObject? = (arrayOfJSONObjects as NSArray).value(forKeyPath:
"name")

Swiftier Preferred

var arrayOfJSONObjects = [[String: AnyObject]]()


...
let names: [String] = arrayOfJSONObjects.compactMap { object in
return object["name"] as? String
}

Not Preferred

var arrayOfJSONObjects: NSArray = NSArray()


...
let names: AnyObject? = arrayOfJSONObjects.value(forKeyPath: "name")

16.1.2 Optionals

Force Unwrapping

Preferred unwrap

guard let url = URL(string: "http://www.example.com/") else {


return
}

UIApplication.shared.open(url)

Not Preferred unwrap

// URL init(string:) is a failable initializer and will crash at runtime


with a force unwrap if initialization fails!
let url = URL(string: "http://www.example.com/")!

UIApplication.shared.open(url)

Preferred downcast

16 / 30
swift-conventions-highlight.md 11/27/2019

guard let detailViewController = segue.destination as?


DetailViewController else {
return
}

detailViewController.person = person

Not Preferred downcast

// segue.destination is declared to be of type UIViewController, so


forcing a downcast to type
// DetailViewController here will crash if the type is not
DetailViewController at runtime!
let detailViewController = segue.destination as! DetailViewController
detailViewController.person = person

Preferred optional chaining

delegate?.didSelectItem(item)

Not Preferred optional chaining

// delegate is an optional so force unwrapping here will crash if delegate


is actually nil at runtime!
delegate!.didSelectItem(item)

if let Pyramid of Doom

Preferred

if
let id = jsonObject[Constants.Id] as? Int,
let firstName = jsonObject[Constants.firstName] as? String,
let lastName = jsonObject[Constants.lastName] as? String,
let initials = jsonObject[Constants.initials] as? String {
// Flat
let user = User(id: id, name: name, initials: initials)
// ...
}

Not Preferred

17 / 30
swift-conventions-highlight.md 11/27/2019

if let id = jsonObject[Constants.id] as? Int {


if let firstName = jsonObject[Constants.firstName] as? String {
if let lastName = jsonObject[Constants.lastName] as? String {
if let initials = jsonObject[Constants.initials] as? String {
// Deep nesting
let user = User(id: id, firstName: name, lastName:
lastName, initials: initials)
// ...
}
}
}
}

Unwrapping Multiple Optionals

Preferred

guard
let constantOne = valueOne,
let constantTwo = valueTwo,
let constantThree = valueThree else {
return
}

if
let constantOne = valueOne,
let constantTwo = valueTwo,
let constantThree = valueThree {
// Code
}

Not Preferred

guard let constantOne = valueOne,


let constantTwo = valueTwo,
let constantThree = valueThree
else {
return
}

if let constantOne = valueOne,


let constantTwo = valueTwo,
let constantThree = valueThree
{
// Code
}

guard let constantOne = valueOne, constantTwo = valueTwo, constantThree =

18 / 30
swift-conventions-highlight.md 11/27/2019

valueThree else {
return
}

if let constantOne = valueOne, let constantTwo = valueTwo, let


constantThree = valueThree {
// Code
}

Preferred

guard
let constantOne = valueOne,
let constantTwo = valueTwo,
let constantThree = valueThree,
var variableOne = valueFour,
var variableTwo = valueFive,
var variableThree = valueSix else {
return
}

if
let constantOne = valueOne,
let constantTwo = valueTwo,
let constantThree = valueThree,
var variableOne = valueFour,
var variableTwo = valueFive,
var variableThree = valueSix {
// Code
}

Not Preferred

guard let
constantOne = valueOne,
var variableOne = valueTwo,
let constantTwo = valueThree else {
return
}

if let constantOne = valueOne,


var variableOne = valueTwo,
var variableTwo = valueThree,
var variableThree = valueFour,
let constantTwo = valueFive {
// Code
}

19 / 30
swift-conventions-highlight.md 11/27/2019

16.1.3 Error Handling

Forced-try Expression

Preferred

do {
let json = try JSONSerialization.jsonObject(with: data, options:
.allowFragments)
print(json)
} catch {
print(error)
}

Not Preferred

// This will crash at runtime if there is an error parsing the JSON data!
let json = try! JSONSerialization.jsonObject(with: data, options:
.allowFragments)
print(json)

16.1.4 Access Control

Preferred

import Foundation

// Top level declaration


fileprivate let foo = "bar"

struct Baz {
...

Not Preferred

import Foundation

// Top level declaration


private let foo = "bar"

struct Baz {
...

Preferred

20 / 30
swift-conventions-highlight.md 11/27/2019

class SomeClass: SomeSuperClass {


private let someString: String

func someFunction(someParam: Int) {


let dictionaryLiteral: [String: AnyObject] = ["foo": "bar"]

let ternary = (someParam > 10) ? "foo" : "bar"

if someParam > 10 {
...
} else {
...
}
}
}

Not Preferred

class SomeClass : SomeSuperClass


{

private let someString:String

func someFunction(someParam :Int)


{

let dictionaryLiteral : [String : AnyObject] = ["foo" : "bar"]

let ternary = (someParam > 10) ? "foo": "bar"

if someParam > 10 { ... }

else {
...
} } }

16.1.5 Protocols

Protocol Conformance

Preferred

class MyViewcontroller: UIViewController {


...
}

// MARK: - UITableViewDataSource

21 / 30
swift-conventions-highlight.md 11/27/2019

extension MyViewcontroller: UITableViewDataSource {


// Table view data source methods
}

// MARK: - UIScrollViewDelegate
extension MyViewcontroller: UIScrollViewDelegate {
// Scroll view delegate methods
}

Not Preferred

class MyViewcontroller: UIViewController, UITableViewDataSource,


UIScrollViewDelegate {
// All methods
}

16.1.6 Arrays and Dictionaries

Type Shorthand Syntax

Preferred

let users: [String]


let usersByName: [String: User]

Not Preferred

let users: Array<String>


let usersByName: Dictionary<String, User>

Trailing Comma

Not Preferred

let anArray = [
object1,
object2,
object3 //no trailing comma
]

let aDictionary = ["key1": value1, "key2": value2] //how can you even read
that?!

22 / 30
swift-conventions-highlight.md 11/27/2019

Preferred

let anArray = [
object1,
object2,
object3,
]

let aDictionary = [
"key1": value1,
"key2": value2,
]

16.1.7 Flow Control

Preferred

if user.isCurrent?.boolValue == true {
// isCurrent is true
} else {
// isCurrent is nil or false
}

Not Preferred

if user.isCurrent?.boolValue ?? false {
// isCurrent is true
} else {
// isCurrent is nil or false
}

16.1.8 Switch Statements

Preferred

enum AnEnum {
case foo
case bar(String)
case baz
}

let anEnumInstanceWithAssociatedValue = AnEnum.Bar("hello")

switch anEnumInstanceWithAssociatedValue {
case .foo: print("Foo")

23 / 30
swift-conventions-highlight.md 11/27/2019

// Correct
case .bar(let barValue): print(barValue) // "hello"
case .baz: print("Baz")
}

Not Preferred

enum AnEnum {
case foo
case bar(String)
case baz
}

let anEnumInstanceWithAssociatedValue = AnEnum.Bar("hello")

switch anEnumInstanceWithAssociatedValue {
case .foo: print("Foo")
// Not Preferred
case let .bar(barValue): print(barValue) // "hello"
case .baz: print("Baz")
}

16.1.9 Use Implicit Getters

Preferred

var someProperty: Int {


return 4 * someOtherProperty
}

subscript(index: Int) -> T {


return object[index]
}

Not Preferred

var someProperty: Int {


get {
return 4 * someOtherProperty
}
}

subscript(index: Int) -> T {


get {
return object[index]
}
}
24 / 30
swift-conventions-highlight.md 11/27/2019

16.2 Shoulds

16.2.1 Declaring Variables

Preferred

var someArray = [String]()


var someArray: [String] = []
var someDictionary = [String: Int]()
var someDictionary: [String : Int] = [:]
var countOfCats: UInt32 = 12
var isMadeOfCheese = false
var somePoint = CGPoint(x:100, y: 200)

Not Preferred

var someDictionary: Dictionary = [String: String]() //Dictionary is


redundant
var somePoint: CGPoint = CGPoint(x:100, y: 200) //CGPoint is repeated
var b = Bool(false) //b is not a descriptive name

16.2.2 Optionals

guard let vs. if let

Preferred

func openURL(string: String) {


guard let url = URL(string: string) else {
return
}
// Flat
UIApplication.shared.open(url)
}

Not Preferred

func openURL(string: String) {


if let url = URL(string: string) {
// Nested
UIApplication.shared.open(url)
}
}

25 / 30
swift-conventions-highlight.md 11/27/2019

16.2.3 Spacing

Preferred

func foo() -> Int {


let nums: [Int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

return nums
.map { $0 * 2 }
.filter { $0 % 2 == 0 }
.reduce(0, +)
}

Not Preferred

func foo() -> Int {


let nums: [Int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

return nums.map { $0 * 2 }.filter { $0 % 2 == 0 }.reduce(0, +)


}

Preferred (lots of choices here)

func openURL(string: String) {


guard let url = URL(string: string) else {
return
}
// Flat
UIApplication.shared.open(url)
}

func openURL(string: String) {


guard let url = URL(string: string) else { return }
// Flat
UIApplication.shared.open(url)
}

func openURL(string: String) {


guard
let url = URL(string: string)
else {
return

26 / 30
swift-conventions-highlight.md 11/27/2019

}
// Flat
UIApplication.shared.open(url)
}

func openURL(string: String) {


guard
let url = URL(string: string)
else { return }
// Flat
UIApplication.shared.open(url)
}

guard
let constantOne = valueOne,
let constantTwo = valueTwo,
let constantThree = valueThree else { return }

guard
let constantOne = valueOne,
let constantTwo = valueTwo,
let constantThree = valueThree
else { return }

Not Preferred

func openURL(string: String) {


guard let url = URL(string: string)
else {
return
}
// Flat
UIApplication.shared.open(url)
}

guard let constantOne = valueOne,


let constantTwo = valueTwo,
let constantThree = valueThree
else { return }

Preferred

27 / 30
swift-conventions-highlight.md 11/27/2019

switch something {
case .oneLongCase,
.anotherLongCase,
.thereAreMoreCases,
.thisIsInASanerPlace:
return false
case .sanity:
return true
}

Not Preferred

switch something {
case .oneLongCase, .anotherLongCase, .thereAreMoreCases,
.thisIsWayTooFarToTheRight:
return true
case .sanity:
return false
}

16.2.4 Loops

Recommended

for name in arraysOfNames.joined() {


print("\(name) is an old-timey comedian")
}

Discouraged

for names in arraysOfNames {


for name in names {
print("\(name) is an old-timey comedian")
}
}

16.2.5 Closures

Preferred

functionWithAClosure { result in
...
}

28 / 30
swift-conventions-highlight.md 11/27/2019

functionWithAClosure { result -> Int in


...
}

functionWithAClosure { (result: String) in


...
}

Not Preferred

functionWithAClosure { (result) in
...
}

functionWithAClosure { (result) -> Int in


...
}

Trailing Closure Syntax

Preferred

UserAPI.registerUser(user) { result in
if result.success {
...
}
}

Not Preferred

UserAPI.registerUser(user, completion: { result in


if result.success {
...
}
})

Preferred

29 / 30
swift-conventions-highlight.md 11/27/2019

let doubled = [2, 3, 4].map { $0 * 2 }

Not Preferred

let doubled = [2, 3, 4].map() { $0 * 2 }

30 / 30

You might also like