Styling your code in Swift
What’s a Style?
Coding style simply is how your code looks, plain and simple or complex like a puzzle word game. Coding styles may differ from developer to developer according to their own preferences and easiness. You can discover the coding styles and conventions by simply observing the code already present in your workspace by your senior or fellow developers or you can discover your own personal style according to your preference.
Why do we need style?
The first and simple answer to this question is to make your code more readable. Who does not want their code to be seen as beautiful and readable. Style is also important if you want to maintain your code for years and among a large number of developers in the team.
Here are some things I consider during coding. This is my personal opinion and you may or may not agree to my opinion. Please inform me through comments why you do not agree to my opinion so that I could also know more about the alternatives to my opinion.
What do you have to consider during coding?
Source Files
Source files should be named considering their role or entity. Source files related to the same module should be grouped in order to find them easily when in need. Following this practice helps developers to quickly find the files related to modules they have to change or work on.
Naming convention
Descriptive and consistent naming makes software easier to read and understand.
- using camelCase not snake_case
- using UpperCamelCase for classes, types, enums and protocols, lowerCamelCase for everything else
- including all needed words while omitting needless words
- using names based on roles, not types
- using terms that don’t surprise experts or confuse beginners
- choosing good parameter names that serve as documentation
- taking advantage of default parameters
Constants
Constants are defined using the let keyword and variables with the var keyword. Always use let instead of var if the value of the variable will not change.
Tip: A good technique is to define everything using let and only change it to var if the compiler complains!
Optionals
Declare variables as optional with ? where a nil value is acceptable whenever the variable is not assigned during its declaration.
Use implicitly unwrapping with ! only for instance variables that you know will be surely initialized later before use. Prefer optional binding to implicitly unwrapped optionals in most other cases.
When accessing an optional value, use optional chaining if a non-optional type of it is required during execution.
Types
Always use Swift’s native types and expressions when available. Swift offers bridging to Objective-C so you can still use the full set of methods if needed.
- Use String over NSString
- Use Dictionary over NSDictionary
- Use Array over NSArray
Semicolons
Swift does not require a semicolon after each statement in your code. They are only required if you wish to combine multiple statements on a single line.
Tip: Do not write multiple statements on a single line separated with semicolons.
Spacing
Method braces and other braces (if/else/switch/while etc.) should always open on the same line as the statement but close on a new line.
Tip: You can re-indent by selecting some code (or Command+A to select all) and then Control+I (or Editor ▸ Structure ▸ Re-Indent in the menu)
Preferred:
if number % 2 == .zero {
print("Even number")
} else {
print("Odd number")
}
Not Preferred:
if number % 2 == .zero
{
print("Even number")
}
else
{
print("Odd number")
}
- There should be one blank line between methods and up to one blank line between different declaration types to aid in visual clarity and organization.
- There should be no blank lines after an opening brace or before a closing brace except in class declaration / protocol conformance statement.
- Closing parentheses should not appear on a line by themselves.
Preferred:
let user = getUser(of: userID,
on: connection)
Not Preferred:
let user = getUser(
of: userID,
on: connection
)
- Colons always have no space on the left and one space on the right. Exceptions are the ternary operator ? :, empty dictionary [:] and #selector syntax addTarget(_:action:).
Preferred:
let dictionary: [String: Any] = ["id": 1, "name": "Mukesh Shakya"]
Not Preferred:
let dictionary: [String : Any] = ["id" : 1, "name":"Mukesh Shakya"]
Unused Code
Unused (dead) code, including Xcode template code and placeholder comments should be removed.
Preferred:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModels.count
}
Not Preferred:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return viewModels.count
}
Minimal Imports
import only the modules a source file requires. Never import modules to use in future. For example, avoiding import of UIKit when importing Foundation will be sufficient. Likewise, don’t import Foundation if you must import UIKit.
Preferred:
import Foundation
let name: String?
Not Preferred:
import UIKit
let name: String?
Use of Self
Avoid using self since Swift does not require it to access an object’s properties or invoke its methods. Use self only when required by the compiler (in @escaping closures, or in initializers to differentiate properties of class or struct from arguments). In other words, if it compiles without self then omit it.
Function Declarations
Keep function declarations short on one line including the opening brace. For functions with long signatures, put each parameter on a new line.
Preferred:
func signup(email: String,
password: String,
confirmPassword: String,
additionalParams: [String: Any]?,
completion: @escaping (Result<String, Error>) -> Void)
Not Preferred:
func signup(email: String, password: String, confirmPassword: String, additionalParams: [String: Any]?, completion: @escaping (Result<String, Error>) -> Void)
Avoid declaring a function in a single line without any intentional line break.
Function Calls
Similar approach while declaring a function, follow same approach while calling the functions too. For functions with long signatures, put each parameter & their values on a new line together.
Preferred:
service.signup(email: email ?? "",
password: password ?? "",
confirmPassword: confirmPassword ?? "") { result in
switch result{
case success (let authModel):
print(authModel)
case .failure(let error):
print(error.localizedDescription)
}
}
Type Inference
Prefer compact code and let the compiler infer the type for constants or variables of single instances while declaring as well as assigning the value to it in the same line.
Preferred:
let message = "Namaste!"
Not Preferred:
let message: String = "Namaste!"
Note: For empty arrays and dictionaries, use type annotation since compiler cannot infer it without the data.
Preferred:
var numbers: [Int] = []
Not Preferred:
var numbers = [Int]()
Ternary Operator
The Ternary operator ?: , should only be used when it increases clarity or code neatness. A single condition is usually all that should be evaluated. Evaluating multiple conditions, usually more than one is usually more understandable as an if else statement. In general, the best use of the ternary operator is during assignment of a variable and deciding which value to use.
Preferred:
let alertControllerStyle: UIAlertController.Style = UIDevice.current.userInterfaceIdiom == .pad ? .alert : .actionSheet
Not Preferred:
let result = a > b ? (c > d ? c : d) : y
Multiple Optional unbinding
When coding with conditionals, the left-hand margin of the code should be the “golden” or “happy” path. That is, don’t nest if statements. Multiple return statements are OK. The guard statement is built for this.
Preferred:
guard let firstNumber = firstNumber else {
fatalError()
}
//some code using firstNumber
guard let secondNumber = secondNumber else {
fatalError()
}
// other code using firstNumber and secondNumber
Not Preferred:
if let firstNumber = firstNumber {
if let secondNumber = secondNumber {
let sum = firstNumber + secondNumber
print(sum)
} else {
fatalError()
}
} else {
fatalError()
}
When multiple optionals are unwrapped either with guard or if let, minimize nesting by using the compound version when possible.
if let firstNumber = firstNumber,
let secondNumber = secondNumber {
let sum = firstNumber + secondNumber
print(sum)
} else {
fatalError()
}
guard let firstNumber = firstNumber,
let secondNumber = secondNumber else {
fatalError()
return
}
let sum = firstNumber + secondNumber
print(sum)
Not Preferred:
if let firstNumber = firstNumber {
if let secondNumber = secondNumber {
let sum = firstNumber + secondNumber
print(sum)
} else {
fatalError()
}
} else {
fatalError()
}
Parentheses
Parentheses around conditionals are not required in swift and should be omitted
Preferred:
if number % 2 == .zero {
print("Even number")
}
Not Preferred:
if (number % 2 == .zero) {
print("Even number")
}
In larger expressions, optional parentheses can be used sometimes to make code read more clear.
Protocol Conformance
When adding protocol conformance to a class, prefer adding a separate extension for the protocol methods. This keeps the related methods grouped together with the protocol and can simplify instructions to add a protocol to a class with its associated methods. Also make a habit of marking the conformance.
Preferred:
import UIKit
class MyViewController: UIViewController {
private lazy var tableView: UITableView = {
let tableView = UITableView()
tableView.dataSource = self
return tableView
}()
}
// MARK: UITableViewDataSource
extension MyViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 22
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return UITableViewCell()
}
}
Not Preferred:
import UIKit
class MyViewController: UIViewController, UITableViewDataSource {
private lazy var tableView: UITableView = {
let tableView = UITableView()
tableView.dataSource = self
return tableView
}()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 22
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return UITableViewCell()
}
}
When the compiler does not allow you to re-declare protocol conformance in a derived class, it is not always required to replicate the extension groups of the base class.
Preferred:
import UIKit
class MyTableViewController: UITableViewController {
var viewModels: [String] = []
}
// MARK: UITableViewDataSource
extension MyTableViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModels.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return UITableViewCell()
}
}
Not Preferred:
import UIKit
class MyTableViewController: UITableViewController {
var viewModels: [String] = []
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModels.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return UITableViewCell()
}
}
Thank you for reading the article till now. I hope these practices help you maintain your code style in your project.