This guide is based on the following sources:
This guide should help you to improve your Swift code style, its readability, consistency and simplicity. It's not a manifest, it doesn’t tell you what to do (though sometimes it may tell you what not to do). It's a set of hints about subjectively best practices that might help you and your team decrease number of programmers errors.
- Spacing
- Comments
- Code organization
- Naming
- Closures
- Types
- Mutability –
let
overvar
- Optionals
- Static vs dynamic code
- Implicit getters
- Access control
- Explicit references to
self
- Value types over reference types
- Forbidden
Indent code with tabs, not spaces. End files with an empty line.
Vertical spaces should be used in long methods to separate its name from implementation. You may also want to use vertical spaces to separate logic within a function. Shorter methods (one or two lines) don't need such spacing.
Use comments to describe why something is written as it is, or working like it does. Remember that code should be self-documenting, so use comments only if necessary.
If you decide to add comments, keep them up-to-date. Unmaintained comments should be removed.
Source files should have the following organization.
// 1. imports
import MoneyKit
// 2. classes, structs, enums
class Wallet {
// 2.1. public, internal properties
let cards: [Card]
private(set) var cash: Cash
// 2.2. private properties
unowned private let owner: Person
// 2.3. initializers
init(cash: Cash, cards: [Card], owner: Person)
// 2.4. public, internal functions
func affordsTransaction(transaction: Transaction) -> Bool
// 2.5. private functions
private func cardWithSuffiecientCash(cash: Cash) -> Card?
}
// 3. extensions, protocol implementations
extension Wallet: Printable {
var description: String {
return "\(owner.name) has \(cash) cash and \(cards.count) cards"
}
}
Such organization helps others to reach important content earlier. It also saves time, confusion and improves readability.
Names should be meaningful and compact, written in camelCase. Try to ask yourself whether the name of a type sufficiently explains its behavior. Meaningful naming is very important to other developers because they define some expectations about their own roles.
It is strongly misadvised to name suffix your types with words like Manager
, Helper
or Utility
because they're meaningless and their role can be easily misinterpreted.
Names of classes, structs, enums, enum cases, typealiases, protocols and generic types should be capitalized. Generic types' names should start with letter T
, then U
, V
and so on.
Function names should be as descriptive and meaningful as possible. Try to express its intent in the name, by keeping it compact at the same time.
Arguments should also be descriptive. Remember that you can use argument labels, which may be more meaningful to a user.
The first argument's name can be a part of the function's name...
func convertPoint(point: Point, fromView view: View) -> Point
... or its label can be explicitly required.
func convert(#point: Point, toView view: View) -> Point
Use default values for arguments where a function expects any value or some specific value most of the time. If a particular argument is not required for a function, it's good to make it optional and nil
by default.
If the last argument of a function is a closure, use trailing closure syntax.
Trailing closure syntax should be used if a function accepts a closure as its last argument. If it's its only argument, parentheses may be ommited. Unused closure arguments should be replaced with _
(or fully ommited if no arguments are used). Argument types should be inferred.
func executeRequest<T>(request: Request<T>, completion: (T, Error?) -> Void)
executeRequest(someRequest) { (result, _) in /* ... */ }
Use implicit return
in one-line closures with clear context.
let numbers = [1, 2, 3, 4, 5]
let even = filter(numbers) { $0 % 2 == 0 }
Also, remember that global functions are closures and sometimes it's convenient to pass a function name as a closure.
func isPositive(number: Int) -> Bool
let numbers = [-1, 2, 3, -4, 5]
let positive = filter(numbers, isPositive)
Try to use native Swift types before you come up with your own. Every type can be extended, so sometimes instead of introducing new types, it's convenient to extend or alias existing ones.
Remember that Objective-C classes that have native Swift equivalents are not automatically bridged, e.g. NSString
is not implicitly bridged to String
in the following example.
func lowercase(string String) -> String
let string: NSString = /* ... */
lowercase(string) // compile error
lowercase(string as String) // no error
Types should be inferred whenever possible. Don't duplicate type identifier if it can be resolved in compile time:
// preferred
let name = "John Appleseed"
let planets = [.Mars, .Saturn]
let colors = ["red": 0xff0000, "green": 0x00ff00]
// not preferred
let name: String = "Amanda Smith"
let planets: [Planet] = [.Venus, .Earth]
let colors: [String: UInt32] = ["blue": 0x0000ff, "white": 0xffffff]
Also, associate colon with type identifier.
// preferred
class VideoArticle: Article
let events: [Timestamp: Event]
// not preferred
class VideoArticle : Article
let events : [Timestamp : Event]
Typealiases should be short and meaningful.
// preferred
typealias MoneyAmount = Double
// not preferred
typealias Money = Double
It's safer to assume that a constant is immutable, thus it's highly recommended to declare values as constants, using let
. Immutable constants ensure their values will never change, which results in less error-prone code.
Mutable var
variables should only be used when necessary, e.g. when you're absolutely sure you will be changing their values in the future.
Force unwrapping should be avoided as much as possible. Implicitly unwrapped optionals lead to less safe code and can cause unwanted crashes. Use optional chaining or if-let
bindings to unwrap optional values.
let user: User? = findUserById(123)
if let user = user {
println("found user \(user.name) with id \(user.id)")
}
Unwrapping several optionals in nested if-let
statements is forbidden, as it leads to "pyramid of doom". Swift allows you to unwrap multiple optionals in one statement.
let name: String?
let age: Int?
if let name = name, age = age where age >= 13 {
/* ... */
}
However, implicitly unwrapped optionals can sometimes be useful. They may be used in unit tests, where system under test should never be nil
. There's no point executing rest of the tests if one of them fails.
var sut: SystemUnderTest!
beforeEach {
sut = /* ... */
}
afterEach {
sut = nil
}
it("should behave as expected") {
sut.run()
expect(sut.running).to(beTrue())
}
Static code is code where logic and control can be resolved at compile-time. The Swift compiler is able to optimize predictable code to work better and faster. Try to make use of this feature and write as much static code as possible.
On the other hand, dynamic code's control flow is resolved at run-time, which means it's not predictable and, as a result, can't be optimized by the compiler. Avoid using dynamic
and @objc
attributes.
Read-only computed properties don't need an explicit getter, thus it can be ommited. This also applies to read-only subscripts.
struct Person {
let height: Float
let weight: Float
var bmi: Float {
return weight / (height * height)
}
}
Access control modifiers should be specified on a top-level scope.
public extension String {
var uppercaseString: String
}
Use access control modifiers only if necessary. Pay attention to variables. Use private
or private(set)
appropriately. Don't add modifiers if they are are already a default.
// preferred
extension String {
var uppercaseString: String
}
// not preferred
internal extension String {
var uppercaseString: String
}
Explicit references to self
should only take place in closures and when the language requires it.
struct Person {
let firstName: String
let lastName: String
var fullName: String {
return "\(firstName) \(lastName)"
}
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
}
Value types, such as structs, enums and tuples are usually simpler than reference types (classes) and they're always passed by copying. This means they're independent thread-safe instances, which makes code simpler and safer. In addition, let
and var
work as expected.
Value types are great for representing data in the app.
struct Country {
let name: String
let capital: City
}
On the other hand, reference types, such as classes, are passed by referencing the same mutable instance in memory. There are several cases when classes should be preferred. The first case emerges when subclassing current Objective-C classes. The second one is when you need to use reference types with mutable state, or if you need to perform some actions on deinitialization.
Reference types are great to represent behavior in the app.
class FileStream {
let file: File
var currentPosition: StreamPosition
}
Keep in mind that inheritance is not a sufficient argument to use classes. You should try to compose your types using protocols.
// preferred
protocol Polygon {
var numberOfSides: Int { get }
}
struct Triangle: Polygon {
let numberOfSides = 3
}
// not preferred
class Polygon {
let numberOfSides: Int
init(numberOfSides: Int)
}
class Triangle: Polygon {
init() {
super.init(numberOfSides: 3)
}
}
Types should never have prefixes because their names are already implicitly mangled and prefixed by their module name.
Semicolons are obfuscative and should never be used. Statements can be distributed in different lines.
Rewriting standard library functionalities should never take place. Your code will most probably be less optimized and more confusing to other developers.