Swift 🏎
Reference Resources
- Swift’s Official Guidelines
- Ray Wenderlich’s Guidelines
Code formatting
General
- On average, row width should not exceed 120 characters. However, the main criterion is readability.
- Do not align code as ASCII-art.
-
When using Objective-C, class names must be unique across an entire application. The convention is to use prefixes on all classes. For Swift, class prefixes are optional. It will depend on the project’s type. If we are working with an Objective-C codebase, we will use prefixes. Otherwise, we will not use prefixes classes except for Error classes.
// Good for Objective-C RPViewController, RPError // Good for Swift ViewController, Manager
-
Use proper data types. For non-integer types, use decimal point explicitly.
// Bad let width = 320 // Good let width: CGFloat = 320.0
- Do not keep outdated code in comments as it could confuse other team members and could cause less productivity. Source control is a way to go in this case 😌.
Braces and Indents
-
Curly braces open on the same line with the method signature and close on a new line.
// Bad func viewDidLoad() { super.viewDidLoad() } // Good func viewDidLoad() { super.viewDidLoad() }
- Separate logical code blocks with 1 line wrap. A random number of spaces and line breaks are not allowed.
-
Add a blank line after the opening brace ({), within
class
,protocol
,struct
,extension
orenum
.// Bad class User() { let name: String let age: Int } // Good class User() { let name: String let age: Int } // Good enum Direction { case north case south }
-
Do not add a blank line after the opening brace, within
func
.// Bad func doSomething() { print("done") } // Good func doSomething() { print("done") }
-
Use the same indent for switch and case sentence, within
switch
.// Bad switch value { case .caseA: print("a") print("aa") case .caseB: print("b") print("bb") } // Good switch value { case .caseA: print("a") print("aa") case .caseB: print("b") print("bb") }
Tip: Prevent Xcode add extra indents when formatting by unchecking
Indent switch/case labels in
forSwift
inSettings > Text Editing
. -
Do not add a blank line after each case, within
switch
.// Bad switch value { case .caseA: print("A") doA() case .caseB: print("B") doB() case .caseC: print("C") doC() } // Good switch value { case .caseA: print("A") doA() case .caseB: print("B") doB() case .caseC: print("C") doC() }
-
Do not add a line break after each single line case, within
switch
.// Bad switch value { case .caseA: print("a") case .caseB: print("b") } // Good switch value { case .caseA: print("a") case .caseB: print("b") }
-
Use same line syntax for a short computed property.
// Bad var a: Bool { true } // Bad var a: Bool { true } // Good var a: Bool { true }
Declaration
Methods
- Keep method signatures short and clear.
- Do not keep empty methods, autogenerated methods, methods that only call super implementation.
- Method must do exactly what is declared in the signature, to avoid implicit side effects.
- Mark methods that perform heavy operations with special words:
load
,fetch
,create
. - On average, a method’s body should not exceed the limit of 80 lines of code.
Properties
- Prefer outlets as
weak
, but be aware of a case where strong reference is required. Suppose we have an outlet ofNSLayoutConstraint
inUITableView
where we need to activate and deactivate it. It’s possible that, once deactivated, the outlet will be released from memory since there’s no strong reference pointing to it. - Variables of Boolean type should have is, has prefixes.
Closures
-
Do not use the words
callback
,block
, andclosure
as parts of the names. Instead, provide names that describe the task the closure performs or specify the moment it is called.// Bad callback, comparingBlock, sortingClosure, selectionAction, beginBlock // Good completion, comparator, sortCriteria, didSelectItem, willBeginParsing
- Use
typealias
for reused closure types. -
For better readability, use
Void
instead of empty braces.// Bad var action: () -> () // Good var action: () -> Void
Lightweight Generics
- Objective-C type declarations using lightweight generic parameterization are imported by Swift with information about the type of their contents preserved.
-
Do not have space between
TYPE
andGENERIC
.// Bad NSArray <NSString *> *a // Good NSArray<NSString *> *a
Reused resources
Formatter
- Keep formatters in a separate file.
- Do not create formatters in a cycle, they are heavy objects.
- Use namespaces to group formatters.
Constants
- Do not keep constants in a separate file.
- Keep as few constants as possible.
- Sets of settings keep in .plist files.
Colors
- Give colors reasonable names.
- Do not use the word
color
in color names. - Try to keep the color palette as small as possible. A large set of used colors is usually caused by inconsistency in design specifications.
- Expressive names help to manage and remember reused colors.
-
Avoid giving too specific names, make names general.
// Bad var textFieldBorder: UIColor var veryDarkGrey: UIColor // Good var ashGrey: UIColor var mandyRed: UIColor
-
Do not use
#colorLiteral
. Sometimes, the color is not shown properly on Xcode.// Bad let dingleyGreen = #colorLiteral(red: 0.398, green: 0.523, blue: 0.285, alpha: 1.0) // Good let dingleyGreen = UIColor(red: 0.398, green: 0.523, blue: 0.285, alpha: 1.0)
Images
-
Do not use
#imageLiteral
. Sometimes, the image is not shown properly on Xcode.// Bad let image = #imageLiteral(resourceName: "image-name") // Good let image = UIImage(named: "image-name")
Localization
- Localization is required regardless of the number of supported languages. Therefore adding an extra language won’t cause any difficulty in the future.
- Keep all strings in
Localizable.strings
file. Do not use storyboard localization -
For localizable string keys, use 3-level domain naming scheme with camel case: feature.useCase.description. Do not use snake case or any other cases to avoid ambiguity.
// Bad "Expiry date" = "Expiry date"; "OK" = "OK"; // Good "booking.confirmation.expiry_date" = "Expiry date"; "general.button.ok" = "OK";
Project Structure
- Organize .xcodeproj: Folder structure must reflect the logical structure of the project. Do not put all view controllers into a separate folder and all models into another one. Keep close related objects together.
- Keep file structure in sync with .xcodeproj.
- One .m / .swift == One class. No extra classes allowed.
Classes and Structures
- Prefer Swift native types over
NSObject
. -
Prefer value types to reference types for lightweight data types or until you explicitly need reference semantics.
// Bad final class Coordinate { var x: CGFloat var y: CGFloat } // Good struct Coordinate { var x: CGFloat var y: CGFloat }
- Prefer
let
overvar
as it indicates directly that the property cannot be changed, and that makes it easier for other team members to understand the intention. Always usevar
only when it’s required to be changed. Otherwise, go forlet
. - Mark classes as
final
by default unless you’ve explicitly designed them to be inheritable. - Expose only methods and variables that are meant to be exposed. Hide internal implementation by using the
private
access specifier. - Keep life cycle methods such as
deinit
,viewDidLoad
,viewWillAppear
, etc., at the top of the class body. - Prefer declaring
init
withdeinit
right below it if required. - Avoid using
self.
when you don’t need it since Swift does not require it to access an object’s properties or invoke its methods. In closure expressions, the presence ofself.
is a clear signal thatself
is being captured by the closure.
Design Patterns
Many design patterns have distinctive declaration styles. Being consistent in method naming helps other developers to understand your intentions, relations between objects even without reading the code itself.
Delegate, Data Source
-
Always add the sender to method signatures:
// Bad func parserDidEndParsingNode(_ node: Node) // Good func parser(_ parser: Parser, didEndParsingNode node: Node)
-
Do not use verbs in past form. Use did + Verb in present form. The purpose is to avoid exotic forms of irregular verbs.
Target-Action
- Always add the sender to the method signatures.
-
Use did as a prefix to describe how the action is triggered.
// Bad @objc func showCart(_ sender: Any?) // Good @objc func didSelectCart(_ sender: UIButton)
Listener
-
For methods which are called by
NotificationCenter
, always add thenotification
parameter to the signature.// Bad func applicationDidEnterForeground() // Good func applicationDidEnterForeground(_ notification: Notification)
Code clarity & Best practices
Method and variable naming
Good code has a high signal/noise ratio. To declare methods and variables, use as little information as required to understand their responsibility.
A good rule of thumb is that, when declaring methods and variables that require a comment to explain what they are, it usually indicates that its name might not be clear enough. However, it could depend on the case and couldn’t apply to every case. Use it as an indicator. But, in the end, it leaves to the developer’s judgment upon the case.
Avoid large initialization
Initialize objects only with required parameters. Avoid large init
methods. Do not init objects with delegate.
Avoid creating large protocols
Avoid creating large protocols. Each protocol must describe a single responsibility. Many small protocols are better than one large.
Naming classes should be straightforward
Avoid words with unclear meaning, such as Common
, Utils
, Helper
, Custom
. The name of a class must leave no questions about its task.
// Bad
DataManager, StringUtils, PriceHelper, CustomStepper
// Good
DataStorage, Formatter, PriceCalculator, StepperWithCount
Be wary of creating large structs
Generally, value types are better than reference types in terms of performance as they get allocated in the stack. So there’s no need for the reference counting overhead.
Keep in mind that, once struct
contains at least one reference type, its advantage over using class
will be nullified, or might even get worse. Since struct
is a pass-by-value, so its properties will need to be copied over and over when passing. This results in reference counting doing the job more than just using class
.
But, the trade-off of value semantic could be worth enough to ignore the performance gain sometimes depending on how big the struct
is. In that case, COW (Copy-on-write) should be implemented.
Guards for early exits
Use guard
to exit functions early.
// Bad
func doSomethingWithNumbers(number1: Int?, number2: Int?) {
if let number1 = number1 {
if let number2 = number2 {
// do something with numbers
} else {
print("Impossible")
}
} else {
print("Impossible")
}
}
// Good
func doSomethingWithNumbers(number1: Int?, number2: Int?) {
guard let number1 = number1, let number2 = number2 else {
return print("Impossible")
}
// do something with numbers
}
Avoid force unwrapping, force casts, and implicitly unwrapped optional
Force unwrapping, force casting, and implicitly unwrapped optional are inherently unsafe and should be avoided.
Prefer higher-order functions
Favor higher-order functions (e.g. map, filter, flatMap, compact, etc.) but don’t force it. Developers can still choose to use a for loop when it makes sense.