This is the documentation of Intrepid's best practices and style regarding the Swift language.
Feedback and change requests are encouraged! The current maintainers of this style guide are the developers of Intrepid. If you have an issue with any of the existing rules and would like to see something changed, please see the contribution guidelines, then open a pull request. ⚡
Attempt to encourage patterns that accomplish the following goals:
- Increased rigor, and decreased likelihood of programmer error
- Increased clarity of intent
- Aesthetic consistency
We use the SwiftLint library from Realm to enforce code style practices across our projects. This will be enabled by default for projects using Jenkins Pipelines. There is a .swiftlint.yml
file available here which is the current general standard for Intrepid projects (used by default for Jenkins). The rules can be changed on a project-by-project basis, but this should be avoided if possible. To use your own SwiftLint file on a project be sure to include the it within the project's directory.
- Indent using 4 spaces. Never indent with tabs. Be sure to set this preference in Xcode.
- End files with a newline.
- Make liberal use of vertical whitespace to divide code into logical chunks.
- Don’t leave trailing whitespace.
- Not even leading indentation on blank lines.
Code should strive to be separated into meaningful chunks of functionality. These larger chunks should be indicated by using the // MARK: -
keyword.
Using just // MARK: section title
inserts the section title
into the function menu, bolding it, and giving it a unique section icon (see View Lifecycle
).
Adding the dash, // MARK: - section title
, not only inserts the section title
into the function menu, bolding it, and giving it a unique section icon but also adds a separator into the function menu (see light gray line above View Lifecycle
). This makes it easier to identify grouped code.
When grouping protocol conformance, always use the name of the protocol and only the name of the protocol
// MARK: - UITableViewDelegate
// MARK: - UITableViewDelegate Methods
// MARK: - Table View Delegate
Guard statements are meant to be used as early return logic only. They should not be used for regular control flow in place of a traditional control flow statement.
guard let value = someMethodThatReturnsOptional() else { return nil }
guard
let strongSelf = self,
let foo = strongSelf.editing
else { return }
This is any situation where you'd want to do more work in the guard than just return or throw.
guard let value = someMethodThatReturnsOptional() else {
doSomeNecessaryThing()
return nil
}
guard
let strongSelf = self,
let foo = strongSelf.editing
else {
doSomeNecessaryThing()
throw Error.FooUnknown
}
Unless you require functionality that can only be provided by a class (like identity or deinitializers), implement a struct instead.
Note that inheritance is (by itself) usually not a good reason to use classes, because polymorphism can be provided by protocols, and implementation reuse can be provided through composition.
For example, this class hierarchy:
class Vehicle {
let numberOfWheels: Int
init(numberOfWheels: Int) {
self.numberOfWheels = numberOfWheels
}
func maximumTotalTirePressure(pressurePerWheel: Float) -> Float {
return pressurePerWheel * Float(numberOfWheels)
}
}
class Bicycle: Vehicle {
init() {
super.init(numberOfWheels: 2)
}
}
class Car: Vehicle {
init() {
super.init(numberOfWheels: 4)
}
}
could be refactored into these definitions:
protocol Vehicle {
var numberOfWheels: Int { get }
}
extension Vehicle {
func maximumTotalTirePressure(pressurePerWheel: Float) -> Float {
return pressurePerWheel * Float(numberOfWheels)
}
}
struct Bicycle: Vehicle {
let numberOfWheels = 2
}
struct Car: Vehicle {
let numberOfWheels = 4
}
Rationale: Value types are simpler, easier to reason about, and behave as expected with the let
keyword.
Protocols that describe what something is should be named as nouns.
Collection, ViewDelegate, etc.
Protocols that describe the capability of something should be named using the suffixes able
, ible
, or ing
.
Equatable, Reporting, Sustainable, etc.
When specifying the type of an identifier, always put the colon immediately after the identifier, followed by a space and then the type name.
class SmallBatchSustainableFairtrade: Coffee { ... }
let timeToCoffee: NSTimeInterval = 2
func makeCoffee(type: CoffeeType) -> Coffee { ... }
func swap<T: Swappable>(inout a: T, inout b: T) { ... }
Prefer let
-bindings over var
-bindings wherever possible
Use let foo = …
over var foo = …
wherever possible (and when in doubt). Only use var
if you absolutely have to (i.e. you know that the value might change, e.g. when using the weak
storage modifier).
Rationale: The intent and meaning of both keywords is clear, but let-by-default results in safer and clearer code.
A let
-binding guarantees and clearly signals to the programmer that its value is supposed to and will never change. Subsequent code can thus make stronger assumptions about its usage.
It becomes easier to reason about code. Had you used var
while still making the assumption that the value never changed, you would have to manually check that.
Accordingly, whenever you see a var
identifier being used, assume that it will change and ask yourself why.
Methods of parameterized types can omit type parameters on the receiving type when they’re identical to the receiver’s.
struct Composite<T> {
…
func compose(other: Composite) -> Composite {
return Composite(self, other)
}
}
struct Composite<T> {
…
func compose(other: Composite<T>) -> Composite<T> {
return Composite<T>(self, other)
}
}
Rationale: Omitting redundant type parameters clarifies the intent, and makes it obvious by contrast when the returned type takes different type parameters.
Use whitespace around operators when defining them.
func <| (lhs: Int, rhs: Int) -> Int
func <|< <A>(lhs: A, rhs: A) -> A
func <|(lhs: Int, rhs: Int) -> Int
func <|<<A>(lhs: A, rhs: A) -> A
Rationale: Operators consist of punctuation characters, which can make them difficult to read when immediately followed by the punctuation for a type or value parameter list. Adding whitespace separates the two more clearly.
When defining an operator to conform to a protocol such as Equatable
, define it within the scope of the extension or type definition that declared the protocol conformance.
extension Person: Equatable {
static func == (lhs: Person, rhs: Person) -> Bool {
return lhs.id == rhs.id
}
}
extension Person: Equatable { }
func == (lhs: Person, rhs: Person) -> Bool {
return lhs.id == rhs.id
}
Rationale: Better code organization, easier to tell at a glance which operator belongs to which type.
When specifying the type of a dictionary, always leave one space after the colon, and no extra spaces around the brackets.
let capitals: [Country: City] = [Sweden: Stockholm]
let capitals: [Country: City] = [ Sweden: Stockholm ]
For literal dictionaries that exceed a single line, newline syntax is preferable:
let capitals: [Country: City] = [
Sweden: Stockholm,
USA: WashingtonDC
]
let capitals: [Country: City] = [Sweden: Stockholm, USA: WashingtonDC]
Unless it impairs readability or understanding, it preferable to rely on Swift's type inference where appropriate.
let hello = "Hello"
let hello: String = "Hello"
This does not mean one should avoid those situations where an explicit type is required.
let padding: CGFloat = 20
var hello: String? = "Hello"
Rationale: The type specifier is saying something about the identifier so it should be positioned with it.
Enum cases should be defined in camelCase
with leading lowercase letters. This is counter to Swift 2.x where uppercase was preferred.
enum Directions {
case north
case south
case east
case west
}
enum Directions {
case North
case South
case East
case West
}
Rationale: Uppercase syntax should be reserved for typed declarations only.
If you have an identifier foo
of type FooType?
or FooType!
, don't force-unwrap it to get to the underlying value (foo!
) if possible.
Instead, prefer this:
if let foo = foo {
// Use unwrapped `foo` value in here
} else {
// If appropriate, handle the case where the optional is nil
}
Or when unwrapping multiple optionals, prefer this:
if let foo = foo.optionalProperty as? SomeType,
let bar = bars.filter({ $0.isMyBar }).first,
foo.hasBizz,
bar.hasBazz { // Notice the new line between conditionals and execution code
foo.bizz()
bar.bazz()
} else {
// If appropriate, handle the case where the optionals are nil
}
Rationale: Explicit if let
-binding of optionals results in safer code. Force unwrapping is more prone to lead to runtime crashes.
Optional chaining in Swift is similar to messaging nil
in Objective-C, but in a way that works for any type, and that can be checked for success or failure.
Use optional chaining if you don’t plan on taking any alternative action if the optional is nil
.
let cell: YourCell = tableView.ip_dequeueCell(indexPath)
cell.label?.text = “Hello World”
return cell
Rationale: The use of optional binding here is overkill.
Implicitly unwrapped optionals have the potential to cause runtime crashes and should be used carefully. If a variable has the possibility of being nil
, you should always declare it as an optional ?
.
Implicitly unwrapped optionals may be used in situations where limitations prevent the use of a non-optional type, but will never be accessed without a value.
If a variable is dependent on self
and thus not settable during initialization, consider using a lazy
variable.
lazy var customObject: CustomObject = CustomObject(dataSource: self)
Rationale: Explicit optionals result in safer code. Implicitly unwrapped optionals have the potential of crashing at runtime.
Top-level functions, types, and variables should always have explicit access control specifiers:
public var whoopsGlobalState: Int
internal struct TheFez {}
private func doTheThings(things: [Thing]) {}
However, definitions within those can leave access control implicit, where appropriate:
internal struct TheFez {
var owner: Person = Joshaber()
}
When dealing with functionality that relies on ObjC systems such as the target-selector pattern, one should still strive for appropriate access control. This can be achieved through the @objC
attribute.
@objc private func handleTap(tap: UITapGestureRecognizer)
public func handleTap(tap: UITapGestureRecognizer)
Rationale: It's rarely appropriate for top-level definitions to be specifically internal
, and being explicit ensures that careful thought goes into that decision. Within a definition, reusing the same access control specifier is just duplicative, and the default is usually reasonable.
When possible, omit the get
keyword on read-only computed properties and
read-only subscripts.
var myGreatProperty: Int {
return 4
}
subscript(index: Int) -> T {
return objects[index]
}
var myGreatProperty: Int {
get {
return 4
}
}
subscript(index: Int) -> T {
get {
return objects[index]
}
}
Rationale: The intent and meaning of the first version is clear, and results in less code.
When accessing properties or methods on self
, leave the reference to self
implicit by default:
private class History {
var events: [Event]
func rewrite() {
events = []
}
}
Only include the explicit keyword when required by the language—for example, in a closure, or when parameter names conflict:
extension History {
init(events: [Event]) {
self.events = events
}
var whenVictorious: () -> Void {
return {
self.rewrite()
}
}
}
Rationale: This makes the capturing semantics of self
stand out more in closures, and avoids verbosity elsewhere.
Classes should start as final
, and only be changed to allow subclassing if a valid need for inheritance has been identified. Even in that case, as many definitions as possible within the class should be final
as well, following the same rules.
Rationale: Composition is usually preferable to inheritance, and opting in to inheritance hopefully means that more thought will be put into the decision.
Specifications around the preferable syntax to use when declaring, and using functions.
With Swift 3, the way that parameter names are treated has changed. Now the first parameter will always be shown unless explicitly requested not to. This means that functions declarations should take that into account and no longer need to use long, descriptive names.
func move(view: UIView, toFrame: CGRect)
func preferredFont(forTextStyle: String) -> UIFont
func moveView(view: UIView, toFrame frame: CGRect)
func preferredFontForTextStyle(style: String) -> UIFont
If you absolutely need to hide the first parameter name it is still possible by using an _
for its external name, but this is not preferred.
func moveView(_ view: UIView, toFrame: CGRect)
Rationale: Function declarations should flow as a sentence in order to make them easier to understand and reason about.
Avoid needless repetition when naming functions. This is following the style of the core API changes in Swift 3.
let blue = UIColor.blue
let newText = oldText.append(attributedString)
let blue = UIColor.blueColor()
let newText = oldText.appendAttributedString(attributedString)
... some specifications on calling functions
- Avoid declaring large arguments inline
- For functions with many arguments, specify each arg on a new line and the
)
on the final line - Use trailing closure syntax for simple functions
- Avoid trailing closures at the end of functions with many arguments. (3+)
It is preferable to associate a closure's type from the left hand side when possible.
let layout: (UIView, UIView) -> Void = { (view1, view2) in
view1.center = view2.center
// ...
}
let layout = { (view1: UIView, view2: UIView) in
view1.center = view2.center
// ...
}
It is preferable to omit Void
in closure arguments and return types whenever possible.
let noArgNoReturnClosure = { doSomething() } // no arguments or return types, omit both
let noArgClosure = { () -> Int in return getValue() } // void argument, use '()'
let noReturnClosure = { (arg) in doSomething(with: arg) } // void return type, omit return type
let noArgNoReturnClosure = { (Void) -> Void in doSomething() }
let noArgClosure = { (Void) -> Int in return getValue() }
let noReturnClosure = { (arg) -> Void in doSomething(with: arg) }
Rationale: A Void
return type can be inferred, thus it is unnecessarily verbose to include it
When defining closure type, prefer ()
for parameters, and Void
for return types.
typealias NoArgNoReturnClosure = () -> Void
typealias NoArgClosure = () -> Int
typealias NoReturnClosure = Int -> Void
typealias NoArgNoReturnClosure = (Void) -> ()
typealias NoArgClosure = (Void) -> Int
typealias NoReturnClosure = Int -> ()
Rationale: Void
is more readable than ()
, especially when wrapped in more parentheses. () -> Void
is also the standard in Apple APIs and documentation.
Shorthand argument syntax should only be used in closures that can be understood in a few lines. In other situations, declaring a variable that helps identify the underlying value is preferred.
doSomethingWithCompletion() { result in
// do things with result
switch result {
// ...
}
}
doSomethingWithCompletion() {
// do things with result
switch $0 {
// ...
}
}
Using shorthand syntax is preferable in situations where the arguments are well understood and can be expressed in a few lines.
let sortedNames = names.sort { $0 < $1 }
Use trailing closure syntax only if there's a single closure expression parameter at the end of the argument list.
UIView.animateWithDuration(1.0) {
self.myView.alpha = 0
}
UIView.animateWithDuration(1.0, animations: {
self.myView.alpha = 0
})
When a function takes multiple closures as arguments it can be difficult to read. To keep it clean, use a new line for each argument and avoid trailing closures. If you're not going to use the variable from the closure input, name it with an underscore _
.
UIView.animateWithDuration(
SomeTimeValue,
animations: {
// Do stuff
},
completion: { _ in
// Do stuff
}
)
UIView.animateWithDuration(SomeTimeValue, animations: {
// Do stuff
}) { complete in
// Do stuff
}
(Even though the default spacing and syntax from Xcode might do it this way)
When referring to self
within a closure you must be careful to avoid creating a strong reference cycle. Always use a capture list, such as [weak self]
when it's necessary to use functions or properties outside of the closure.
lazy var someClosure: () -> String? = { [weak self] in
guard let safeSelf = self else { return nil }
return safeSelf.someFunction()
}
viewModel.someClosure { self in
self.outsideFunction()
}
Rationale If a closure holds onto a strong reference to a property being used within it there will be a strong reference cycle causing a memory leak.
weak
is always preferable as it creates an optional so that crashes are prevented.unowned
is only useful when you are guaranteed that the value will never benil
within the closure. Since this creates the possibility for unsafe access it should be avoided.