Cocoa-, Xcode-, and Swift-Specific Features
Selecting a kind of type
Prefer using Structs, Protocols, and Extensions more than classes and subclasses; Swift will behave more predictably and optimize better this way. It also allows for some patterns which are impossible with subclassing.
GOLDEN RULE If you are having trouble describing your API’s functionality in simple terms, you may have designed the wrong API.
Documentation comments are highly encouraged. We do not have any preferences of its style, so long as the doc comment is provided. You can view them in the Quick Help sidebar on the right of Xcode when the typing cursor is on the name, or by ⌥-clicking the name.
For reference, here are a couple examples of documentation comments:
|Sparse Doc CommentAcceptable||GoodDetailed Doc Comment|
Note that documentation comments are not to explain what something does in a way its name cannot, but instead to elaborate on what its name already says, including intended uses, parameter limits, possible return values, related code, external documentation, etc. Do not use documentation comments as an excuse to poorly name anything.
Also, be sure you know what something actually does before documenting it.
RULE-OF-THUMB Less documentation is better than incorrect documentation.
ACTUAL EXAMPLE FROM CODEBASE 😱
Here’s a couple starting guides on how to write awesome documentation:
By default, use
Int for integers and
CGFloat for fractions (or
Length if you have access to SwiftUI), as they automatically scale to the current processor architecture. If a specific task requires a specific number type like
NSDecimalNumber, then that’s OK, but for common operations, use those aforementioned types.
In Swift, “optional” means “able to be
nil”. The great thing is that, in Swift, anything can be optional, even primitives like
Ints! This is a very powerful feature, and such power comes with a lot of responsibility.
Do Not Use Exclamation Points*†
!” means “I know this might be
nil, but I’m smart enough to know it isn’t
nil right now. It’s okay to crash if I’m wrong.” The problem here is that there is still a chance for crashing*, whereas there’s almost always another approach which does not provide a chance of crashing.
The following are preferred ways to unwrap an optional:
?[operators. These somewhat mirror Objective-C’s behavior, meaning “If it’s
nil, stop evaluating and return
nilif anything expects a value. Either way, continue as normal.”
- If the question-mark syntax does not accomplish the goal, or would be overly verbose for this context,
if var) is the next best option. This creates a new sub-context in which the existence of the value is guaranteed, but not before nor after.
- If one or more values are required for a context (usually a function), or if the if let nesting is getting too deep,
guard var) is preferred. This is much like an inverted
if let, in that it creates a sub-context in which the nonexistence of the value is guaranteed, the difference being that after that context, its existence is guaranteed. * These should go at the top of a context which requires the value, rather than farther down.
- If you have a backup value handy, use
??to use it as an alternative in case the ideal value doesn’t exist.
* There is one exception where exclamation points are acceptable: On nib-initialized fields which are guaranteed to be available after load. If you are using SwiftUI, this exception does not apply.
† Obviously, using them to mean “Boolean invert” / “not” is okay (
Another way to write a function that may or may not return a value is to declare it with the
throws keyword. This is perfectly acceptable, and in fact encouraged when it would lead to a more clear API. However, never return an optional value from a throwing function. Instead, where you would
return nil, throw a new, custom, descriptive error that tells why you couldn’t return a value. This is because, once the error has been handled, the API caller shouldn’t have to then deal with a
nil value, as from their perspective all errors that would cause that have been handled. Additionally, that approach wouldn’t play well with the
if should never be used in place of
guard, and vice-versa. As its name implies,
guard should be used to guard against a bad state; if something is required, it acts as a guard that everything is OK before proceeding.
When entering a
else block constitutes a bad state, use
assertionFailure (or at least log a message) when a
else block was entered.
Do not use
if in place of each other!
guard is meant to be used as a guard against a bad state, not as an
Extensions are the bread and butter of Swift and Cocoa. Always prefer creating an extension function over a utility function. Always prefer creating an extension over subclassing. It’s OK to make
private extensions if something is only needed in the current context and nowhere else.
GOLDEN RULE Always prefer extensions over subclassing.
This is more natural. “I’m looking for a common URL; I will look in the URL class.”
This is less natural. “I’m looking for a common URL; I will look in one of possibly several utility classes.”
Equally bad is placing the constant in global scope, since it’s still not obvious how to find it, and it overpopulates autocomplete.
Separate logical sections of code with
// MARK: and
// MARK: - messages. Use
// MARK: - more sparingly than
When implementing a protocol (or subclassing) outside an
extension, make sure you place a
// MARK: message before the variables and functions you’ve implemented, making sure the mark’s message is the name of the protocol.
See also: Blank Lines
In case you don’t know or need a refresher, here are the basics:
vars are very powerful, much moreso than allowing a value to be re-set. There are three ways to use a
- As a normal (unobserved*) instance, whose value can be set and gotten. The scope of these operations can be set individually, like
- As an observed* instance whose changes are monitored just before andor just after the value is changed
- As a dynamic† value, whose setter and getter are specified by the developer. In this mode, it can also be read-only.
These are mutually exclusive; an unobserved instance cannot* report when it changes, an observed instance cannot have a dynamic† getter and setter, and a dynamic† value does not have a backing stored value.
It’s also important to know that a
structinherits the mutability and observation status of the instance in which it’s held. That is to say,
let parentStruct’s fields won’t be changeable, regardless of whether they’re
var parentStruct’s fields will be changeable if (and only if) they are
vars, but not if they’re
lets. Even better, if
didSet, it will be notified if any of its fields changes.
Do note that this behavior does not exist in
classinstances, even if those instances are inside a
struct(the best immutability those will get is not being able to change the reference; its own fields’ mutability won’t change). This is one reason why you should avoid using
classes as much as possible.
With the basics over, here’s the guidelines:
- Normal, unobserved*
vars should be used only for values that need to change rapidly, but whose changes aren’t important.
vars should be used for important values, which should propagate other changes or send notifications when they change.
vars should be used in place of functions which only get or set a single value
* Here, “unobserved” refers to lacking the built-in
didSetblocks; these can still be observed using key-value approaches. Conversely, “observed” refers to the presence of these blocks and does not imply KVO.
† Here, “dynamic” refers to returning a generated, abstracted, translated, or conditional value (like a function). It does not refer to using the
dynamickeyword for KVO.
Function Overloading and Default Parameters
In Objective-C, it was necessary to create multiple methods when you wanted one parameter to be optional. With Swift’s default parameters, this is no longer necessary.
Of course, overloading is okay if it is necessary for the current task.
Function parameter labels
Sometimes it’s a good idea to label function parameters, and sometimes it’s not.
- Parameter labels may be omitted when the parameter reflects the main purpose of the function. For instance, the string message of a log function, the new value of a setter, or the two items in a two-item comparison function. This overrides the following guidelines.
- Boolean parameters must always have labels
- Unlike Objective-C, the first parameter can have a label separate from the method name. When possible, prefer this.
- When the value being passed in has a specific purpose other than just providing required data, try to give it a separate, more-English label than its variable name (not simple ones like
- Do not provide a label for parameters whose name is in the function name
- When calling an external API which does not label its parameters, but leaves their purpose confusing, add “label” block-comments
RULE-OF-THUMB When in doubt, use a label.
Swift was designed around less-mutating, more-functional approaches first. In general, use approaches that create changed immutable copies, rather than ones that mutate an existing value. This may sound inefficient, but remember that the Swift compiler and runtime expect this to be the default approach, and will optimize for it.
If you run tests and find that using a non-mutating approach is too slow, then it’s okay to delicately choose which parts of your approach to convert to mutating. Use a scalpel, not a mallet when deciding what needs to be mutated. If you’re using Swift’s functional collection functions like
filter, first try preceding these with
.lazy to turn it into a
LazySequence, whose performance is improved by waiting until the last moment to perform the operations. This can be even faster than using mutation.
This is much more expressive, safer, and often even faster than mutating.
In my tests, this takes an average of ~82% as long as the mutating approach.
In my tests, this takes an average of ~122% as long as the functional approach.
One of the beautiful things Swift does is decrease the amount of control flow that’s needed by turning common patterns into production-ready functions in the standard library, called higher-order functions. When at all possible, prefer these functions and paradigms over traditional control flow.
Enum Case Associated Values
Associated values are a very powerful Swift paradigm, but there are many ways to use them. In our codebase, we prefer this approach:
- Always label the associated values
- When reading the values in a switch case, always give the reader variable the same name as the label
- When pattern-matching in a switch case, always use the label
- When pattern-matching in a switch case and ignoring the associated values, always write the label followed by an underscore, rather than just an underscore or omitting the associated values entirely
NSCache is very useful when applied correctly. It cannot be used for all caching needs.
This is because
NSCache can and will evict things even if everything is under the limits.
In case you’re unfamiliar with
NSCache, it is a Cocoa class which allows you to give limits on both total number of items in that cache, as well as total cost (with each item optionally given a cost when it is cached). As previously mentioned,
NSCache only loosely respects these guidelines. For performance reasons, it might evict items even if no limits have been reached. Similarly, it might not yet have evicted items well after its limits have been passed.
NSCache to cache values which cannot be recreated (for instance, items with UUIDs)!
If your requirements are okay with this looseness, then
NSCache might be the right tool for the job. Otherwise, you should probably write your own cache which is specialized to fit those requirements.