Swift 🏎
- Code formatting
- Project Structure
- Classes and Structures
- Design Patterns
-
Code clarity & Best practices
- Method and variable naming
- Access control
- Avoid large initialization
- Avoid creating large protocols
- Naming classes should be straightforward
- Be wary of creating large structs
- Guards for early exits
- Avoid force unwrapping, force casts, and implicitly unwrapped optional
- Prefer higher-order functions
- Align multiline arguments and collection
- Naming Cells for UICollectionView and UITableView
- Prefer Implicit Return in Closures, Functions, and Getters
- Reference Resources
Code formatting
General
-
-
-
- Use prefixes when working with an Objective-C codebase.
- Otherwise, do 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.
let width = 320
let width: CGFloat = 320.0
-
Braces and Indents
-
Curly braces open on the same line with the method signature and close on a new line.
func viewDidLoad() { super.viewDidLoad() }
func viewDidLoad() { super.viewDidLoad() }
-
-
Add a blank line after the opening brace ({), within
class
,protocol
,struct
,extension
orenum
.class User() { let name: String let age: Int }
class User() { let name: String let age: Int }
enum Direction { case north case south }
-
Do not add a blank line after the opening brace, within
func
.func doSomething() { print("done") }
func doSomething() { print("done") }
-
Use the same indent for switch and case sentence, within
switch
.switch value { case .caseA: print("a") print("aa") case .caseB: print("b") print("bb") }
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
.switch value { case .caseA: print("A") doA() case .caseB: print("B") doB() case .caseC: print("C") doC() }
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
.switch value { case .caseA: print("a") case .caseB: print("b") }
switch value { case .caseA: print("a") case .caseB: print("b") }
-
Use same line syntax for a short computed property.
var a: Bool { true } // or var a: Bool { true }
var a: Bool { true }
Declaration
Methods
-
-
-
-
load
,fetch
,create
. -
Properties
-
weak
, but be aware of a case where strong reference is required. Suppose there is an outlet ofNSLayoutConstraint
inUITableView
where it needs to be activated and deactivated. It’s possible that, once deactivated, the outlet will be released from memory since there’s no strong reference pointing to it. -
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.callback, comparingBlock, sortingClosure, selectionAction, beginBlock
completion, comparator, sortCriteria, didSelectItem, willBeginParsing
-
typealias
for reused closure types. -
For better readability, use
Void
instead of empty braces.var action: () -> ()
var action: () -> Void
Lightweight Generics
-
-
Do not have space between
TYPE
andGENERIC
.NSArray <NSString *> *a
NSArray<NSString *> *a
Reused resources
Formatter
Constants
Colors
-
-
color
in color names. -
-
-
Avoid giving too specific names, make names general.
var textFieldBorder: UIColor var veryDarkGrey: UIColor
var ashGrey: UIColor var mandyRed: UIColor
-
Do not use
#colorLiteral
. Sometimes, the color is not shown properly on Xcode.let dingleyGreen = #colorLiteral(red: 0.398, green: 0.523, blue: 0.285, alpha: 1.0)
let dingleyGreen = UIColor(red: 0.398, green: 0.523, blue: 0.285, alpha: 1.0)
-
- Variations of colors should reuse the original color from the design system.
- The variation can be declared within the view (private usage) or as a separate color (public usage).
// darkGrey is defined in color system but only the variation is declared in code var darkGrey: UIColor { UIColor(red: 0.35, green: 0.35, blue: 0.35).withAlphaComponent(0.3) }
var darkGrey: UIColor var darkGreyA30: UIColor { darkGrey.withAlphaComponent(0.3) } // or view.backgroundColor = Color.darkGrey.withAlphaComponent(0.3)
Images
-
Do not use
#imageLiteral
. Sometimes, the image is not shown properly on Xcode.let image = #imageLiteral(resourceName: "image-name")
let image = UIImage(named: "image-name")
Localization
-
-
Localizable.strings
file. Do not use storyboard localization. -
For localizable string keys, use a 3-level domain naming scheme with camel case: feature.useCase.description. Do not use snake case or any other cases to avoid ambiguity.
"Expiry date" = "Expiry date"; "OK" = "OK";
"booking.confirmation.expiryDate" = "Expiry date"; "general.button.ok" = "OK";
Project Structure
Classes and Structures
-
NSObject
. -
Prefer value types to reference types for lightweight data types or until explicitly need reference semantics.
final class Coordinate { var x: CGFloat var y: CGFloat }
struct Coordinate { var x: CGFloat var y: CGFloat }
-
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
. -
final
by default unless they’re explicitly designed to be inheritable. -
private
access specifier. -
deinit
,viewDidLoad
,viewWillAppear
, etc., at the top of the class body. -
init
withdeinit
right below it if required. -
self.
unless necessary 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 intentions, relations between objects even without reading the code itself.
Delegate, Data Source
-
Always add the sender to method signatures:
func parserDidEndParsingNode(_ node: Node)
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
-
-
Use did as a prefix to describe how the action is triggered.
@objc func showCart(_ sender: Any?)
@objc func didSelectCart(_ sender: UIButton)
Listener
-
For methods which are called by
NotificationCenter
, always add thenotification
parameter to the signature.func applicationDidEnterForeground()
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.
Access control
Prefer writing access control as the leading property’s annotation, including private
, fileprivate
, open
, public
, and internal
.
struct Person {
dynamic lazy private var age = 20
}
struct Person {
private dynamic lazy var age = 20
}
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.
DataManager, StringUtils, PriceHelper, CustomStepper
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.
func doSomethingWithNumbers(number1: Int?, number2: Int?) {
if let number1 = number1 {
if let number2 = number2 {
// do something with numbers
} else {
print("Impossible")
}
} else {
print("Impossible")
}
}
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.
Align multiline arguments and collection
aLongFunction(number1: Int, number2: Int,
text1: String, number3: Int
)
func anotherLongFunction(
number1: Int, number2: Int,
text1: String, number3: Int
) {}
aLongFunction(
number1: Int,
number2: Int,
text1: String,
number3: Int
)
func anotherLongFunction(
number1: Int,
number2: Int,
text1: String,
number3: Int
) {}
var names: [String] = [
"Alice",
"Bob",
"Cait", "David"
]
names.append(
contentsOf: [ "Edward",
"Fiona", "Greg"
]
)
var names: [String] = [
"Alice",
"Bob",
"Cait",
"David"
]
names.append(
contentsOf: [
"Edward",
"Fiona",
"Greg"
]
)
Naming Cells for UICollectionView and UITableView
CollectionView
and TableView
can be omitted in the name of the cell.
// UICollectionViewCell
final class SomeCollectionViewCell: UICollectionViewCell {}
final class SomeCollectionCell: UICollectionViewCell {}
// UITableViewCell
final class SomeTableViewCell: UITableViewCell {}
final class SomeTableCell: UITableViewCell {}
// UICollectionViewCell
final class SomeCell: UICollectionViewCell {}
// UITableViewCell
final class SomeCell: UITableViewCell {}
Prefer Implicit Return in Closures, Functions, and Getters
Omit return
in closure
, func
, and getter
when using a one-line expression.
// Closure
coordinates.map { return $0.lat + 1.0 }
// Function
func sum(lhs: Int, rhs: Int) -> Int { return lhs + rhs }
// Getter
var fullName: String { return "\(firstName) \(lastName)" }
// Closure
coordinates.map { $0.lat + 1.0 }
// Function
func sum(lhs: Int, rhs: Int) -> Int { lhs + rhs }
// Getter
var fullName: String { "\(firstName) \(lastName)" }