Methods and Subscripts
|
This section documents the Swift 6 language mode as shipped by Swift 6.3, as published in The Swift Programming Language at docs.swift.org, which is the reference these pages are written and verified against. 6.4-beta-only features are always flagged as such — never presented as baseline. This content was generated with the assistance of AI and should be verified against docs.swift.org before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Methods are functions associated with a particular type — classes, structures and enumerations can all define
instance methods and type methods. Subscripts let any type provide []-bracket access to a logical member,
the way Array and Dictionary do. Both build directly on
Functions, applying the same parameter and return-value rules
in a type’s context.
Instance Methods and self
class Counter {
var count = 0
func increment() {
count += 1
}
func increment(by amount: Int) {
count += amount
}
func reset() {
self.count = 0 // `self` disambiguates the property from a same-named parameter, if any
}
}
let counter = Counter()
counter.increment()
counter.increment(by: 5)
counter.reset()
An instance method is called with dot syntax on a specific instance and has implicit access to every property and
method of that instance; self refers to the current instance and is only required for disambiguation, most
commonly inside an initializer parameter (self.count = count) or, on a value type, inside a mutating method
that reassigns the whole instance (next).
mutating Methods on Value Types and Assigning to self
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)
// let fixedPoint = Point(x: 3.0, y: 3.0)
// fixedPoint.moveBy(x: 2.0, y: 3.0) // error: fixedPoint is a `let` struct -- moveBy cannot be called on it
enum TriStateSwitch {
case off, low, high
mutating func next() {
switch self {
case .off: self = .low // a mutating enum method can assign a wholly new case to `self`
case .low: self = .high
case .high: self = .off
}
}
}
var ovenLight = TriStateSwitch.low
ovenLight.next() // .high
A struct or enum method that needs to modify self (or any of its stored properties) must be marked mutating — since instance methods on a value type otherwise cannot modify their properties, unlike a class instance
method, which can always mutate its instance’s var properties without any modifier. A mutating method may
even assign an entirely new value to self, as TriStateSwitch.next() does; calling a mutating method on a
let value-type instance is a compile error, since the instance is fixed and cannot be mutated at all (see
Structures and Classes).
Type Methods
class SomeClass {
class func someTypeMethod() { // `class`: overridable by a subclass
print("called on the type itself, not an instance")
}
}
SomeClass.someTypeMethod()
struct LevelTracker {
static var highestUnlockedLevel = 1 // `static`: a stored type property
static func unlock(_ level: Int) { // `static`: a type method, not overridable (structs can't subclass anyway)
if level > highestUnlockedLevel { highestUnlockedLevel = level }
}
var currentLevel = 1
mutating func advance(to level: Int) -> Bool {
if LevelTracker.highestUnlockedLevel >= level {
currentLevel = level
return true
}
return false
}
}
A type method is called on the type itself rather than an instance, declared with static func (structs, enums,
and non-overridable class methods) or class func (a class method a subclass is permitted to override) — the
same static-vs-class split covered for type properties in
Properties. Inside a type method, an unqualified reference to
another static/class member resolves against the type itself, with no self needed for that purpose.
Subscripts
struct TimesTable {
let multiplier: Int
subscript(index: Int) -> Int { // read-only: single expression body, no `set`
multiplier * index
}
}
let threeTimesTable = TimesTable(multiplier: 3)
print(threeTimesTable[6]) // 18
struct Matrix {
let rows: Int, columns: Int
var grid: [Double]
init(rows: Int, columns: Int) {
self.rows = rows
self.columns = columns
grid = Array(repeating: 0.0, count: rows * columns)
}
func indexIsValid(row: Int, column: Int) -> Bool {
row >= 0 && row < rows && column >= 0 && column < columns
}
subscript(row: Int, column: Int) -> Double { // multi-parameter, multidimensional subscript
get {
assert(indexIsValid(row: row, column: column), "Index out of range")
return grid[(row * columns) + column]
}
set {
assert(indexIsValid(row: row, column: column), "Index out of range")
grid[(row * columns) + column] = newValue
}
}
}
var matrix = Matrix(rows: 2, columns: 2)
matrix[0, 1] = 1.5
print(matrix[0, 1]) // 1.5
struct DefaultRow {
subscript(index: Int, defaultingTo fallback: Int = 0) -> Int { // a parameter with a default value
index >= 0 ? index : fallback
}
}
print(DefaultRow()[-1]) // 0
enum Planet: Int { case mercury = 1, venus, earth }
extension Planet {
subscript(name: String) -> Planet? { // a subscript added retroactively, in an extension
switch name.lowercased() {
case "mercury": .mercury
case "venus": .venus
case "earth": .earth
default: nil
}
}
}
print(Planet.earth["earth"] as Any)
struct Cosmos {
static subscript(name: String) -> Int { // a type subscript, called on the type itself
name.count
}
}
print(Cosmos["Earth"]) // 5
A subscript is declared with subscript(parameters) → ReturnType { }, using the same get/set shorthand as a
computed property — a single expression with no get is read-only, and set may be added for a read-write
subscript. Subscripts support multiple parameters of any type (including with default values and variadics),
letting a single type provide several distinct subscript "overloads" differentiated by parameter type or count,
as Matrix’s two-dimensional `[row, column] form shows; they can be declared in an extension exactly like a
method or computed property; and a static subscript (or class subscript) applies to the type itself rather
than an instance.
Callable Values via callAsFunction, and @dynamicCallable
struct Adder {
var base: Int
func callAsFunction(_ x: Int) -> Int { // lets an Adder value be "called" like a function
base + x
}
}
let addFive = Adder(base: 5)
print(addFive(3)) // 8 -- sugar for addFive.callAsFunction(3)
@dynamicCallable
struct RandomNumberGenerator {
func dynamicallyCall(withKeywordArguments args: KeyValuePairs<String, Int>) -> Double {
let lower = args.first(where: { $0.key == "lowerBound" })?.value ?? 0
let upper = args.first(where: { $0.key == "upperBound" })?.value ?? 1
return Double.random(in: Double(lower)...Double(upper))
}
}
let generator = RandomNumberGenerator()
_ = generator(lowerBound: 1, upperBound: 10) // resolved to dynamicallyCall(withKeywordArguments:) at compile time
callAsFunction lets any instance be invoked with call syntax directly (addFive(3)), useful for types that
model a single natural operation, like a mathematical function or a configured transformation. @dynamicCallable
is looser still: the compiler rewrites any call syntax on the type into a call to dynamicallyCall(withArguments:)
or dynamicallyCall(withKeywordArguments:), whichever the call’s argument shape matches, which is how libraries
model dynamic, Python-like calling conventions without knowing the parameter list ahead of time — see
Attributes and Compiler Control for
@dynamicCallable alongside Swift’s other compiler attributes.
See Also
-
Functions — the parameter/return rules methods and subscripts build on.
-
Structures and Classes — why
mutatingexists at all: value semantics vs. reference semantics. -
Properties —
static/classtype properties, the property-side counterpart to type methods. -
Extensions and Nested Types — adding methods and subscripts to an existing type from an extension.