General Patterns
Singleton
Singletons are often unnecessary, but if you find one necessary, name the single instance shared
(if there can only ever be one) or default
(if a custom instance can be created), and place it in the class’ static scope.
Good |
Bad |
class Foo {
static let shared = Foo()
private init() {
}
}
// Call-site example:
let foo = Foo.shared
let bar = bar(foo: .shared)
class Bar {
static let `default` = Foo()
init() {
}
}
// Call-site example:
let bar = Bar.default
let baz = baz(bar: .default)
|
class Foo {
static let sharedInstance = Foo()
private init() {
}
}
// Call-site example:
let foo = Foo.sharedInstance
let bar = bar(foo: .sharedInstance)
class Foo {
static let foo = Foo()
private init() {
}
}
// Call-site example:
let foo = Foo.foo
let bar = bar(foo: .foo)
class Bar {
init() {
}
}
let defaultBar = Bar()
// Call-site example:
let bar = Bar
let baz = baz(bar: defaultBar)
|
Lazy initialization
Thankfully, Swift makes this easy with its lazy keyword. The only matter of style is how to handle resetting the lazy value.
Good |
Bad |
lazy var foo = Foo()
lazy private(set) var bar = Bar()
|
private var _foo: Foo? = nil
var foo: Foo {
get {
if let foo = _foo {
return foo
}
_foo = Foo()
return _foo
}
set {
_foo = newValue
}
}
private var _bar: Bar? = nil
var bar: Bar {
if let bar = _bar {
return bar
}
_bar = Bar()
return _bar
}
|
lazy private(set) var baz: Baz = {
let baz = Baz()
baz.qux = "qux?"
return baz
}()
|
private var lazyBaz: Baz? = nil
var baz: Baz {
get {
if let baz = lazyBaz {
return baz
}
let baz = Baz()
baz.qux = "qux?"
lazyBaz = baz
return baz
}
private set {
lazyBaz = newValue
}
}
|
Resettable Lazy Fields
For resettable lazy fields, declare a private backing field as optional and a more-exposed field getter that will set it if it’s nil
. For this, you can use Swift Lazy Patterns’ ResettableLazy
:
Good |
Bad |
private var _foo = ResettableLazy<Foo> { Foo() }
internal var foo: Foo! {
get {
return _foo.value
}
set {
if let newValue = newValue {
_foo.value = newValue
}
else {
_foo.reset()
}
}
}
print(foo)
foo = nil
|
internal lazy var foo: Foo!
print(foo)
foo = nil // This does not reset the lazy value! https://bugs.swift.org/browse/SR-5172
|
Assertions
Assertions can be a powerful way to help yourself debug; if a very bad state occurs, instead of it being quietly logged, the app halts immediately and won’t continue. Remember assertions are ignored in production builds so they won’t lead to end-user crashes.
When no logic is necessary and an assertion should always be thrown, use assertionFailure("message")
instead of assert(false, "message")
.
Good |
Bad |
guard let bar = self.bar else {
assertionFailure("foo requires bar")
return
}
|
guard let bar = self.bar else {
print("foo requires bar")
return
}
guard let bar = self.bar else {
return
}
|
Factory Initializers
A common Objective-C approach to fancy initializers is to create a static function named like arrayWithObject:
or stringByAppendingString:
. Swift no longer needs these bodges. Instead, create another initializer (perhaps in an extension
), whose first parameter is named like the old factory methods. These are often also annotated as convenience
initializers.
Good |
Bad |
class Foo {
let bar: Bar
required init(bar: Bar) {
self.bar = bar
}
convenience init?(with baz: Baz) {
guard let bar = baz.bar else {
return nil
}
self.init(bar: bar)
}
}
let example = Foo(with: Baz())
|
class Foo {
let bar: Bar
required init(bar: Bar) {
self.bar = bar
}
static func foo(withBaz baz: Baz) -> Foo? {
guard let bar = baz.bar else {
return nil
}
return Foo(bar: bar)
}
}
let example = Foo.foo(withBaz: Baz())
|
Global Constants
See also:
There will always be a need for global constants. However, it’s better to keep them out of global scope. Prefer placing these constants in struct
s, extension
s, etc.
Good |
Bad |
public extension Int {
public static let answer = 42
}
public extension MyTimelyOperation {
public static let maxDuration: TimeInterval = 0.5
}
public extension Notification.Name {
public static let hotFolderUpdate = Notification.Name("org.bh.hotFolderUpdate")
}
// Usage:
reverseEngineerQuestion(from: .answer)
extension MyTimelyOperation {
static var shortOperation: MyTimelyOperation {
return MyTimelyOperation(duration: self.maxDuration / 3.0)
}
}
let shortOperation = MyTimelyOperation.shortOperation
let notification = Notification(name: .hotFolderUpdate)
|
public let answer = 42
public let maxTimeSensitiveOperationDuration: TimeInterval = 0.5
public let notificationNameHotFolderUpdate = Notification.Name("org.bh.hotFolderUpdate")
// Usage:
reverseEngineerQuestion(from: answer)
extension MyTimelyOperation {
static var shortOperation: MyTimelyOperation {
return MyTimelyOperation(duration: maxTimeSensitiveOperationDuration / 3.0)
}
}
let shortOperation = MyTimelyOperation.shortOperation
let notification = Notification(name: notificationNameHotFolderUpdate)
|
Compile-Time Warnings and Errors
Treat warnings as errors. Very seldom is it okay to allow code with a warning to be committed. It’s up to you to make sure code that you commit is warning- and error-free.
The same goes for tests; if a test fails, treat that like a compiler error. It’s up to you to ensure every build passes its tests.
Runtime Warnings and Errors
It’s often necessary to indicate that a function could not complete its duties. The preferred Objective-C way was to use an NSError**
. In Swift, that is to throw
an Error
. Embrace this, and use it to bring more information and compile-time peace-of-mind.
Good |
Bad |
struct Foo {
enum JsonParseError: Error {
case unknownSymbol(symbol: String)
case unexpectedEnd
}
func parse(json: String) throws -> ParsedJson {
...
guard let nextCharacter = reader.nextCharacter() else {
throw JsonParseError.unexpectedEnd
}
...
switch symbol {
...
default: throw JsonParseError.unknownSymbol(symbol: symbol)
}
...
return parsedJson
}
}
Example usage:
func didReceive(json: String) {
do {
self.parsedJson = try self.parse(json: json)
}
catch let error: JsonParseError {
let message: String
switch error {
case .unknownSymbol(let symbol):
message = "What is this?: \"\(symbol)\""
case .unexpectedEnd:
message = "That EOF came out of nowhere!"
}
assertionFailure(message)
}
}
let empty = try! Foo().parse(json: "{}")
if let unimportantString = self.unimportantString,
let unimportant = try? Foo().parse(json: unimportantString) {
...
}
|
struct Foo {
var parsedJson: ParsedJson?
enum JsonParseError: Int {
case noError = 0 // Non-error defined in "Error" enum
case unknownSymbol = 1
case unexpectedEnd = 2
}
// An `inout` `parsedJson` would also be inappropriate here, even if it does reduce spooky action and mutability
mutating func parse(json: String) -> JsonParseError {
self.parsedJson = nil
...
guard let nextCharacter = reader.nextCharacter() else {
return .unexpectedEnd
}
...
switch symbol {
...
default: return .unknownSymbol
}
...
self.parsedJson = parsedJson
return .noError
}
}
Example usage:
mutating func didReceive(json: String) {
let error = self.parse(json: json) // spooky action: Where is the result?
switch error {
case .unknownSymbol
assertionFailure("Some symbol was wrong!") // Less information
case .unexpectedEnd:
assertionFailure("That EOF came out of nowhere!")
case .noError:
break
}
}
var emptyWrapper = Foo()
let error = emptyWrapper.parse(json: "{}")
guard error == .noError else {
assertionFailure("Didn't expect \(error) when parsing {}")
exit(error.rawValue) // Not the same effect
}
let empty = emptyWrapper.parsedJson
if let unimportantString = self.unimportantString {
var unimportantWrapper = Foo()
_ = unimportantWrapper.parse(json: unimportantString) // What is ignored? It's unclear
if let unimportant = unimportantWrapper.parsedJson {
...
}
}
|
Runtime Errors in Callbacks
This is all well and good, but when receiving a callback from an asynchronous function, you might also want to know if any error occurred in there. Swift’s standard library provides the Result
type for this:
Good |
Bad |
func someAsync(then callback: @escaping (Result<String, Error>) -> Void) {
onAnotherThread {
do {
callback(.success(try someDangerousStringGenerator()))
}
catch let error {
callback(.failure(cause: error))
}
}
}
Example usage:
someAsync { result in
switch result {
case .failure(let cause): print("ERROR: Could not get string. Cause:", cause)
case .success(let result): print(result)
}
}
|
func someAsync(then callback: @escaping (String?, Error?) -> Void) {
onAnotherThread {
do {
callback(try someDangerousStringGenerator(), nil)
}
catch let error {
callback(nil, error)
}
}
}
Example usage:
someAsync { result, error in
if let error = error {
print("ERROR: Could not get string. Cause:", cause)
}
else if let result = result {
print(result)
}
else {
preconditionFailure("Undefined state! No result, and no error!")
}
}
|
Getting Results from Functions
There are two ways you should get results from a function: by them it directly, or by using a callback. Do not use “return parameters” (via inout
). Note that inout
, itself, has valid uses in our style, for instance when the point of the function is to mutate some other object, but it should not be used to “return” a value.
If more than 1 value must be returned, use a tuple. If a great many values must be returned (like, 4 or more), create a specialized struct.
To indicate an error, instead of using an inout Error
, use the throws
keyword.
Good |
Bad |
func asynchronous(callback: (Result) -> Void) { ... }
func manyReturnValues() -> (ResultA, ResultB, ResultC) { ... }
func wayTooManyReturnValues() -> SpecializedStruct { ... }
func danger() throws -> Result { ... }
func nicelySynchronous() -> Result { ... }
Remember, inout still has its place:
func prepareForDelivery(payload: inout Payload, options: PayloadDeliveryOptions) { ... }
// Usage:
prepareForDelivery(payload: &payload, options: [])
… but always consider using a mutating func in an extension instead:
extension Payload {
mutating func prepareForDelivery(options: PayloadDeliveryOptions) { ... }
}
// Usage:
payload.prepareForDelivery(options: [])
|
func asynchronous(return: inout Result?) { ... }
func manyReturnValues(returnA: inout ResultA?, returnB: inout ResultB?, returnC: inout ResultC?) { ... }
func wayTooManyReturnValues() -> (ResultA, ResultB, ResultC, ResultD, ResultE, ResultF, ResultG) { ... }
func danger(error: inout Error) -> Result? { ... }
// More discouraged than bad; sometimes signatures require this
func nicelySynchronous(callback: (Result) -> Void) { ... }
|