Swift Style Guide
Naming
Use UpperCamelCase for type and protocol names, and lowerCamelCase for everything else.
Name booleans like
isSpaceship,hasSpacesuit, etc. This makes it clear that they are booleans and not other types.Acronyms in names (
ID,URL, etc) should be all-caps except when it’s the start of a name that would otherwise be lowerCamelCase, in which case it should be uniformly lower-cased.Event-handling functions should be named like past-tense sentences (e.g.
didTap, nothandleTap). The subject can be omitted if it's not needed for clarity.Avoid Objective-C-style acronym prefixes. This is not needed to avoid naming conflicts in Swift.
Style
Don't include types where they can be easily inferred.
Prefer letting the type of a variable or property be inferred from the right-hand-side value rather than writing the type explicitly on the left-hand side.
Don't use
selfunless it's necessary for disambiguation or required by the language.Name members of tuples for extra clarity. Rule of thumb: if you've got more than 3 fields, you should probably be using a struct.
When unwrapping an optional, prefer reusing the existing identifier rather than introducing a new one. However, it's fine to introduce a new name when doing so improves clarity at the use site.
Prefer
ifexpressions over ternary operators for single-expression return values. Use ternary operators for conditions nested in other expressions, such as SwiftUI modifier conditions. Generally preferifexpressions for assignments after=operators.Prefer using
forloops over the functionalforEach(…)method, unless usingforEach(…)as the last element in a functional chain.
Functions
- Avoid using
unownedcaptures. Instead prefer safer alternatives likeweakcaptures, or capturing variables directly.
Operators
- When extending bound generic types, prefer using generic bracket syntax (
extension Collection<Planet>), or sugared syntax for applicable standard library types (extension [Planet]) instead of generic type constraints.
Patterns
Prefer initializing properties at
inittime whenever possible, rather than using implicitly unwrapped optionals. A notable exception is UIViewController'sviewproperty.Avoid performing any meaningful or time-intensive work in
init(). Avoid doing things like opening database connections, making network requests, reading large amounts of data from disk, etc. Create something like astart()method if these things need to be done before an object is ready for use.Omit redundant memberwise initializers. The compiler synthesizes
internalmemberwise initializers for structs, so explicitinternalinitializers equivalent to the synthesized initializer should be omitted.Extract complex property observers into methods. This reduces nestedness and separates side-effects from property declarations.
Extract complex callback blocks into methods to reduced nestedness.
When validating preconditions at the start of a scope, prefer using
guardstatements overifstatements. This reduces nesting, and allows the compiler to verify that thereturnstatement is present.Avoid global functions whenever possible. Prefer methods within type definitions.
Prefer immutable values whenever possible. Use
mapandcompactMapinstead of appending to a new collection. Usefilterinstead of removing elements from a mutable collection.Prefer immutable or computed static properties over mutable ones whenever possible. Use stored
static letproperties or computedstatic varproperties over storedstatic varproperties whenever possible, as storedstatic varproperties are global mutable state.Handle an unexpected but recoverable condition with an
assertmethod combined with the appropriate logging in production. If the unexpected condition is not recoverable, prefer apreconditionmethod orfatalError(). This strikes a balance between crashing and providing insight into unexpected conditions in the wild. Only preferfatalErrorover apreconditionmethod when the failure message is dynamic, since apreconditionmethod won't report the message in the crash report.Default classes to
final.When defining type functions in classes, prefer
static funcoverclass func.When switching over an enum, generally prefer enumerating all cases rather than using the
defaultcase.Check for nil rather than using optional binding if you don't need to use the value.
Prefer dedicated logging systems like
os_logorswift-logover writing directly to standard out usingprint(…),debugPrint(…), ordump(…).Don't use
#file. Use#fileIDor#filePathas appropriate.Don't use
#filePathin production code. Use#fileIDinstead.Prefer using opaque generic parameters (with
some) over verbose named generic parameter syntax where possible.Prefer to avoid using
@unchecked Sendable. Use a standardSendableconformance instead where possible. If working with a type from a module that has not yet been updated to support Swift Concurrency, suppress concurrency-related errors using@preconcurrency import.Prefer using a generated Equatable implementation when comparing all properties of a type. For structs, prefer using the compiler-synthesized Equatable implementation when possible.
If available in your project, prefer using a
#URL(_:)macro instead of force-unwrapping URL(string:)! initializer.
SwiftUI
- For internal SwiftUI views, prefer using the synthesized memberwise init by defining internal properties rather than private properties. However, SwiftUI dynamic properties like
@Stateshould stay private.
Testing
In Swift Testing, name test cases as sentences using raw identifiers, rather than using lowerCamelCase. Don't prefix test case names with "
test". Use UpperCamelCase for test suite names. Always omit the display name string from the@Testor@Suitemacro.In Swift Testing, avoid expectation message strings that restate the expectation without adding additional context. Unlike
XCTAssert, the Swift Testing#expectmacro generates detailed failure messages that include the expectation condition.Avoid
guardstatements in unit tests. XCTest and Swift Testing have APIs for unwrapping an optional and failing the test, which are much simpler than unwrapping the optionals yourself. Use assertions instead of guarding on boolean conditions.In test suites, test cases should be
internal, and helper methods and properties should beprivate.Avoid force-unwrapping in unit tests. Force-unwrapping (!) will crash your test suite. Use safe alternatives like
try XCTUnwraportry #require, which will throw an error instead, or standard optional unwrapping (?).
Performance
Prefer using
count(where: { ... })overfilter { ... }.count.Prefer using
isEmptyover comparingcountagainst zero.Prefer using
flatMap { ... }overmap { ... }.reduce([], +).Prefer using
containsoverfilter(_:).isEmpty,first(where:) != nil, andrange(of:) != nil.Prefer using
first(where: { ... })overfilter { ... }.first.Prefer using
min()oversorted().first.Prefer
lazy.mapovermapwhen the chain reduces to a single result (joined(separator:),min,max,reduce,contains, etc).
Apple Frameworks
- Use constructors instead of Make() functions for NSRange and others.
- Prefer CIFilter's typed factory methods (via
CIFilterBuiltins) over the string-basedCIFilter(name:)initializer and KVOsetValue(_:forKey:).