Apple’s official Swift naming and API design guidelines hosted on swift.org are considered part of this style guide and are followed as if they were repeated here in their entirety.
This document serves to amend those guidelines with non-contradictory clarifications relevant to the Odyssey codebase, reiterating the relevant Swift API design guideline principles.
It is a living document, subject to correction and amendment through a feedback and ratification process via RFC.
Note
💁 This document is meant to serve us, not the other way around - it is a practical guide, not a dogmatic one.
- Form imperative verb phrases like
fetch,load,generate, etc. for methods with side effects. - Use noun phrases for methods with no side effects, or better yet
- Use computed properties for pure accessors, especially those that are O(1).
- Omit
getor choose an alternative — it adds no clarity and is not used in Apple or Swift standard APIs, except as reserved for special kinds of legacy Objective-C methods.
// ✅ Do this
func textPoint(for base: Base) -> CGPoint
func fetchSubscription() async -> Subscription
var scorekeepingMode: ScorekeepingMode⛔️ Avoid get prefixes:
// ⛔️ Don't do this
func getTextPointForBase(base: Base) -> CGPoint
func getSubscriptionInformation() -> Single<NetworkSubscriptionInformation>
func getScorekeepingMode() -> ScorekeepingModeWhen designing a protocol-witness ensure that the resulting API should follow the method naming guidelines in the SADG
- Closures don’t support argument labels which can lead to confusion at the call site
- Adding wrapper functions for closures with argument labels and parameter names rather than using the closures directly can mitigate confusing parameters
If your protocol-witness closure endpoint happens to satisfy the SADG rules around method naming, there is no need for a wrapper method. Otherwise, add a wrapper method for clarity.
// ✅ Do this
struct NumberFormatter {
private var formatNumber: (Double, NumberFormatter.Style, Locale) -> String
func format(_ value: Double, style: NumberFormatter.Style, locale: Locale) -> String {
formatNumber(value, style, locale)
}
init(formatNumber: @escaping (Double, NumberFormatter.Style, Locale) -> String) {
self.formatNumber = formatNumber
}
}
// Usage, includes labeled parameters
let formatter = NumberFormatterWitness.live
let price = formatter.format(42.5, style: .currency, locale: Locale(identifier: "en_US"))Without the wrapper method the arguments passed to the closure would be missing labels and less clear at the point of use:
// ⛔️ Don't do this
formatter.formatNumber(42.5, .currency, Locale(identifier: "en_US"))Closure properties can still be left as internal variables for the purposes of overriding them in unit tests.
- Use role-specific, descriptive words like
Style,Mode, orVariant. - Drop
Type,Info,Dataunless it absolutely is the only option to resolve ambiguity — they dilute rather than promotes clarity.
// ✅ Do this
enum SomeViewStyle {}
enum SomeViewLayout {}⛔️ Avoid meaningless or generic type suffixes:
// ⛔️ Don't do this
enum SomeViewType {}
enum SomeViewLayoutType {}
Prefer small, focused files named for their type:
- A file containing a single type is named for that type
Foo.swift - A file containing a single type and few auxiliary types is named for the primary type
Foo.swift - A file containing an extension for protocol conformance is named for the type and the protocol
Foo+Protocol.swift - A file containing multiple extensions for conformation should be split by conformance unless overly burdensome, in which case a more general by role specific term should be used
Foo+Testing.swift- ⛔️ Do not name extensions like
Foo+GC.swift- all our code is GameChanger code
- ⛔️ Do not name extensions like
- A file containing other related declarations not scoped under a common type or namespace can be named descriptively,
Testing.swift
✅ Nest types to express scoped and hierarchical relationships among types when possible and reasonable.
// ✅ Do this
struct Parser {
enum Error: Swift.Error {
case invalidEncoding(Data)
case invalidToken(String)
}
// …
}⛔️ Do not repeat parent type names in types that should be nested instead.
// ⛔️ Don't do this
struct Parser {
// …
}
enum ParserError {
// …
}✅ Use uninhabited enums as a way to namespace a group of related static constants.
// ✅ Do this
struct ContentView: View {
enum Constants {
static let tileSize: CGSize(width: 64, 64)
static let tileCornerRadius: CGFloat = 8
static let tileBackgroundColor = Color.red
}
}⛔️ Do not use another type or declare these constants on an unrelated parent.
// ⛔️ Don't do this
struct Constants {
private init() {}
static let tileSize: CGSize(width: 64, 64)
static let tileCornerRadius: CGFloat = 8
static let tileBackgroundColor = Color.red
}
// Or this…
struct ContentView: View {
static let tileSize: CGSize(width: 64, 64)
static let tileCornerRadius: CGFloat = 8
static let tileBackgroundColor = Color.red
}⛔️ Do not use Objective-C style prefixes, module names can be used to disambiguate themselves and child types
// ⛔️ Don't do this
class GCStyledTableViewCell: UITableViewCell {
// …
}Acronyms and initialisms that commonly appear as all upper case in American English should be uniformly up- or down-cased according to case conventions:
// ✅ Do this
let userID: UUID
let idService: IDService
let isIDValid: Bool
let rtmpStream: RTMPStream
func fetchRTMPStream() async -> RTMPStream
let urlRequest: URLRequest
func makeURLRequest() -> URLRequest⛔️ Don’t use inconsistent or non-standard casing:
// ⛔️ Don't do this
let userId: Uuid
let idService: IdService
let isIdValid: Bool
let rTMPStream: RtmpStream
func fetchRtmpStream() async -> RtmpStream
let urlRequest: UrlRequest
func makeUrlRequest() -> UrlRequest✅ Prefer guard to reduce nesting and emphasize the “happy path”
Aim to put the happy path at the left margin of any method by reducing nesting, using guards where appropriate to force early returns.
func process(_ input: String?) {
guard let input else { return }
print("Processing \(input)")
}⛔️ Don’t use deeply nested if chains when early exits are clearer:
func process(_ input: String?) {
if let input {
print("Processing \(input)")
}
}Prefer guards to put exceptional cases first and eliminate extra levels of nesting (Google), but do not sacrifice clarity where an if statement will be easier to read. If you are going out of your way to write a negated guard that is more confusing than an if statement, use the if statement instead.
Trailing commas in array and dictionary literals are required when each element is placed on its own line. Doing so produces cleaner diffs when items are added to those literals later.
// ✅ Do this
let configurationKeys = [
"bufferSize",
"compression",
"encoding", // GOOD.
]
// ⛔️ Don't do this
let configurationKeys = [
"bufferSize",
"compression",
"encoding" // AVOID.
]Omit the test prefix for Swift Testing function names. Provide a clear description in the @Test macro’s display name.
@Test("Test fetching videos")
func fetchVideos() async throws {
let videos = try await videoService.fetchVideos()
#expect(videos.count > 0, "Videos should be fetched")
}Tip
💡 See also Apple’s documentation on converting from XCTest to Swift Testing.
Prefer multi-line conditionals unless the conditional and body are sufficiently simple to express in a single line, such as a simple top of method guard or if statement with a single expression for each the condition and body.
// ✅ These are OK
guard let self else { return }
if singleCondition { singleExpression }
// ⛔️ But don't do this
guard let foo = self.foo, foo > bar, bar < baz else { doSomethingElse(); return }
// ✅ Do this instead
guard
let foo = self.foo,
foo > bar,
bar < baz
else {
doSomethingElse()
return
}Additional guidance may be gleaned and codified here from commonly cited resources:
…provided they do not contradict the Swift API Design Guidelines.