swift-api-design-guidelines

Apply Swift API Design Guidelines to name, label, and document Swift APIs. Covers argument label rules (prepositional phrase rule, grammatical phrase rule,…

INSTALLATION
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill swift-api-design-guidelines
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

$2c

Argument labels determine how a call site reads. Apply these rules in order.

When to omit the first argument label

Grammatical phrase rule. When the first argument forms a grammatical phrase with the base name, omit the label. Move any leading words from what would be the label into the base name instead.

// GOOD — reads as "add subview y"

view.addSubview(y)

// BAD — redundant label breaks the phrase

view.add(subview: y)

Value-preserving type conversions. When an initializer performs a value-preserving (widening) conversion, omit the first argument label.

// GOOD — widening conversion, no label

let value = Int64(someUInt32)

let str = String(someCharacter)

// Narrowing or lossy conversions keep a label

let approx = Int64(truncating: someDecimal)

let str = String(describing: someObject)

Indistinguishable arguments. When all arguments cannot be usefully distinguished, omit all labels.

// GOOD — arguments are peers

let smaller = min(x, y)

zip(sequence1, sequence2)

When to use a prepositional label

Prepositional phrase rule. When the first argument completes a prepositional phrase with the base name, label it with the preposition.

// GOOD — "remove boxes having length 12"

x.removeBoxes(havingLength: 12)

// GOOD — "fade from red"

view.fade(from: red)

// GOOD — "relative path from root"

path.relativePath(from: root)

Exception — abstraction boundary. When the first two arguments represent parts of a single abstraction, fold the preposition into the base name so each component gets its own label.

// GOOD — x and y are parts of a single abstraction (a point)

a.moveTo(x: b, y: c)

// BAD — preposition attaches to first arg, leaving y unlabeled

a.move(toX: b, y: c)

Default: label everything else

When no special rule above applies, label the argument.

// GOOD

array.split(maxSplits: 2)

button.setTitle("OK", for: .normal)

controller.dismiss(animated: true)

array.sorted(by: >)

Argument label decision table

Situation

Rule

Example

First arg completes grammatical phrase

Omit label, merge words into base name

addSubview(y)

Value-preserving init conversion

Omit first label

Int64(someUInt32)

Arguments are indistinguishable peers

Omit all labels

min(x, y)

First arg completes prepositional phrase

Label with preposition

fade(from: red)

First two args form a single abstraction

Fold preposition into base name

moveTo(x: b, y: c)

Everything else

Label it

split(maxSplits: 2)

For extended examples and edge cases, see references/argument-labels-and-parameters.md.

Side-Effect Naming

Name functions and methods by their side effects.

Functions with side effects — imperative verbs

When a function mutates state, name it as an imperative verb phrase.

// Mutates — imperative verb

array.sort()

array.append(newElement)

list.remove(at: index)

timer.invalidate()

Functions without side effects — nouns or adjective phrases

When a function returns a result without mutating anything, name it as a noun phrase, adjective phrase, or read as a description of what it returns.

// Pure — noun/description

let d = point.distance(to: origin)

let area = rect.intersection(other)

let line = text.trimmingCharacters(in: .whitespaces)

Boolean properties and methods

Boolean properties and methods read as assertions about the receiver.

// GOOD — reads as "line is empty"

line.isEmpty

set.contains(element)

url.isFileURL

// BAD — not an assertion

line.empty       // verb? adjective?

set.includes     // incomplete phrase

For more examples, see references/side-effects-and-mutating-pairs.md.

Mutating and Nonmutating Pairs

When an operation has both mutating and nonmutating variants, name them as a pair.

Verb-described operations — -ed/-ing suffix

When the operation is naturally described by a verb:

  • Mutating: imperative verb (sort, append, reverse)
  • Nonmutating: past participle -ed or present participle -ing

Default to -ed (past participle). When -ed is ungrammatical — typically when the verb does not form a natural past participle, or when adding -ed produces an awkward phrase — use -ing (present participle) instead.

Mutating

Nonmutating

Why

sort()

sorted()

-ed — "a sorted array"

reverse()

reversed()

-ed — "a reversed collection"

append(y)

appending(y)

-ing — "appended" is ungrammatical here

stripNewlines()

strippingNewlines()

-ing — "stripped newlines" is awkward

Noun-described operations — form- prefix

When the operation is naturally described by a noun:

  • Nonmutating: the noun itself (union, intersection)
  • Mutating: form prefix (formUnion, formIntersection)
// Nonmutating — returns new value

let combined = a.union(b)

// Mutating — modifies in place

a.formUnion(b)

Factory methods — make- prefix

Factory methods that create a new value start with make.

let iterator = collection.makeIterator()

let buffer = parser.makeBuffer()

Pair decision table

Operation described by

Mutating name

Nonmutating name

Example pair

Verb (default)

verb

verb + -ed

sort() / sorted()

Verb (-ed is ungrammatical)

verb

verb + -ing

stripNewlines() / strippingNewlines()

Noun

form + Noun

noun

formUnion(b) / union(b)

For the full -ed/-ing decision tree and stdlib examples, see references/side-effects-and-mutating-pairs.md.

Documentation Comments

Every public declaration must have a documentation comment.

Summary rules by declaration kind

Declaration

Summary describes

Function / method

What it does and what it returns

Subscript

What it accesses

Initializer

What it creates

Type / property / variable

What it is

Write summaries as a single sentence fragment, beginning with a verb (for actions) or a noun phrase (for entities), ending in a period.

/// Returns the element at the specified index.

func element(at index: Int) -> Element { ... }

/// The number of elements in the collection.

var count: Int { ... }

/// Creates a new array with the given elements.

init(_ elements: some Sequence<Element>) { ... }

/// Accesses the element at the specified position.

subscript(index: Int) -> Element { ... }

Symbol markup

Use standard symbol markup after the summary when relevant:

  • - Parameter name: for individual parameters
  • - Parameters: block for multiple parameters
  • - Returns: for the return value
  • - Throws: for errors thrown
  • - Complexity: for algorithmic complexity
/// Removes and returns the element at the specified position.

///

/// - Parameter index: The position of the element to remove.

/// - Returns: The removed element.

/// - Complexity: O(*n*), where *n* is the length of the collection.

mutating func remove(at index: Int) -> Element { ... }

O(1) complexity rule

Document the complexity of any computed property that is not O(1). Callers assume properties are O(1) by default. If a property does more than constant-time work, state the complexity explicitly.

/// The total weight of all items.

///

/// - Complexity: O(*n*), where *n* is the number of items.

var totalWeight: Double {

    items.reduce(0) { $0 + $1.weight }

}

For documentation patterns and examples, see references/conventions-and-special-rules.md.

Clarity and Naming

Clarity at the point of use is the most important goal. Every design decision serves the person reading a call site.

Clarity over brevity. Longer names are acceptable when they remove ambiguity. Do not abbreviate.

// GOOD

employees.remove(at: position)

// BAD — ambiguous: remove the element? remove at position?

employees.remove(position)

Include words needed to avoid ambiguity. If omitting a word makes the call site unclear, keep it.

// GOOD — "at" clarifies the argument's role

friends.remove(at: index)

// BAD — is "index" the element to remove or the position?

friends.remove(index)

Omit needless words. Do not repeat type information already available from the context.

// GOOD

allViews.remove(cancelButton)

// BAD — "Element" repeats the type

allViews.removeElement(cancelButton)

Name variables and parameters by role, not type. Use the entity's role in the current context, not its type name.

// GOOD — describes the role

var greeting: String

func add(_ observer: NSObject, for keyPath: String)

// BAD — names the type

var string: String

func add(_ object: NSObject, for string: String)

Compensate for weak type information. When a parameter type is Any, AnyObject, or a fundamental type like Int or String, add role-clarifying words to the name.

// GOOD — role is clear despite weak types

func addObserver(_ observer: NSObject, forKeyPath path: String)

// BAD — what does "string" mean here?

func add(_ object: NSObject, for string: String)

For extended naming examples and patterns, see references/naming-and-clarity.md.

Fluent Usage and Protocols

Call sites read as grammatical English. Prefer names that form grammatical phrases at the point of use.

// GOOD — reads fluently

x.insert(y, at: z)          // "x, insert y at z"

x.subviews.remove(at: i)    // "x's subviews, remove at i"

x.makeIterator()             // "x, make iterator"

// BAD — ungrammatical

x.insert(y, position: z)

x.subviews.remove(i)

Initializer first argument. The first argument to an initializer should not form a phrase continuing the type name.

// GOOD

let foreground = Color(red: 32, green: 64, blue: 128)

// BAD — "Color with red" reads awkwardly

let foreground = Color(havingRGBValuesRed: 32, green: 64, blue: 128)

Protocol naming conventions:

Protocol describes

Naming pattern

Examples

What something is

Noun

Collection, IteratorProtocol

A capability

-able, -ible, or -ing suffix

Equatable, Hashable, Sendable

General Conventions

Casing. Types and protocols use UpperCamelCase. Everything else uses lowerCamelCase. Acronyms that are commonly all-caps in American English appear uniformly upper- or lower-cased based on position.

var utf8Bytes: [UTF8.CodeUnit]

var isRepresentableAsASCII = true

var userSMTPServer: SMTPServer

Methods and properties over free functions. Prefer methods and properties. Use free functions only when:

  • There is no obvious selfmin(x, y)
  • The function is an unconstrained generic — print(value)
  • The function syntax is established domain notation — sin(x)

Default arguments over method families. Prefer a single method with default parameters over a family of methods that differ only in which parameters they accept. Place defaulted parameters at the end. Parameters with default values should always have argument labels — defaulted parameters are usually omitted at call sites, so their labels must be clear when they do appear.

// GOOD — labeled with defaults

func decode(_ data: Data, encoding: String.Encoding = .utf8) -> String?

// BAD — method family

func decode(_ data: Data) -> String?

func decode(_ data: Data, encoding: String.Encoding) -> String?

Overload safety. Methods may share a base name when they operate in different type domains or when their meaning is clear from context. Avoid return-type-only overloads that cause ambiguity at the call site.

For casing edge cases, overload patterns, and tuple/closure naming, see references/conventions-and-special-rules.md.

Common Mistakes

-

Omitting needed argument labels. Using remove(position) instead of remove(at: position) when the role of the argument is ambiguous without the label.

-

Using -ed when -ing is correct. Applying stripped() when the past participle is ungrammatical — use stripping() instead. Test: does "a [verb]-ed [noun]" read naturally?

-

Using verb names for side-effect-free operations. Naming a nonmutating method sort() that returns a new collection — use sorted() to signal no mutation.

-

Naming by type instead of role. Using string instead of greeting, or array instead of elements, when the role would be more informative.

-

Missing documentation comments. Leaving public declarations undocumented, or writing summaries that describe the implementation rather than the purpose.

-

Not documenting non-O(1) computed properties. Exposing a linear-time computed property without a Complexity: note, causing callers to assume O(1) and use it in loops.

-

Applying form- prefix to verb-based operations. Writing formSort() instead of just sort() — the form prefix is only for noun-based operations (formUnion).

-

Factory methods without make- prefix. Naming factory methods as createIterator() or buildBuffer() instead of makeIterator() and makeBuffer().

-

Repeating type information in names. Writing removeElement(cancelButton) or stringValue: String when the type is already evident from context.

-

Return-type-only overloads. Defining overloads that differ only in return type, creating ambiguity when the compiler cannot infer the expected type.

-

Unlabeled tuple members and closure parameters. Exposing tuples or closures in public API without naming their components, forcing callers to use positional access.

Review Checklist

Argument Labels

  • First argument follows the correct label rule (grammatical phrase, prepositional, conversion, or labeled)
  • Prepositional labels do not incorrectly group independent arguments
  • Value-preserving conversion initializers omit the first label
  • All non-special-case arguments have labels

Naming Semantics

  • Mutating methods use imperative verb form
  • Nonmutating methods use -ed/-ing or noun form
  • Mutating/nonmutating pairs follow the correct pattern (verb pair or noun/form-noun pair)
  • Boolean properties read as assertions (isEmpty, isValid, contains)
  • Variables and parameters are named by role, not type

Documentation

  • Every public declaration has a doc comment
  • Summaries are single sentence fragments ending in a period
  • Summaries describe the correct thing per declaration kind (action, access, creation, entity)
  • Non-O(1) computed properties document their complexity
  • Parameters, return values, and thrown errors are documented with symbol markup

Conventions

  • Types and protocols use UpperCamelCase; everything else uses lowerCamelCase
  • Acronyms are uniformly cased based on position
  • Default arguments are preferred over method families
  • Overloads do not differ only in return type
  • Protocol names follow the noun (is-a) or suffix (capability) convention

References

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card