首页 / Swift 编程语言教程 / 方法:实例方法与类型方法

Swift 编程语言教程

方法:实例方法与类型方法

本教程共 93 篇 · 第 59 篇 · 更新于 2026-08-08 · 约 8 分钟阅读

SwiftSwift 编程语言教程方法实例方法类型方法staticclassmutating

本节目标:学完你能区分挂在实例上的方法和挂在类型上的方法,并理解 static、class 与 mutating 各自的用途。

方法(method)就是和某个类型绑定的函数。类、结构体、枚举都能定义方法——这一点和 Objective-C 不同,那边的方法只能属于类。Swift 把”函数属于类型”这事放开到了三种类型上,让你无论选哪种自定义类型都能写方法。方法分两类:实例方法(instance method)和类型方法(type method)。

1-1 实例方法:属于某个具体对象

实例方法是属于某个具体实例的函数。它用来操作该实例的数据,或者提供和该实例相关的功能。定义实例方法就是把函数写进类型的花括号里,语法和普通函数一模一样。

class Counter {
    var count = 0
    func increment() {
        count += 1
    }
    func increment(by amount: Int) {
        count += amount
    }
    func reset() {
        count = 0
    }
}

Counter 类有三个实例方法:increment() 让计数加一,increment(by:) 按指定数量加,reset() 归零。它们都能直接访问同类型的 count 属性,不需要额外声明可见性。调用时也用点语法,只是这次点在实例上:

let counter = Counter()
counter.increment()
counter.increment(by: 5)
counter.reset()

关键点:实例方法不能脱离实例单独调用,必须先有对象。它的参数也和函数一样同时有参数名(函数体内用)和参数标签(调用时用),因为方法本质就是和类型绑定的函数。

1-2 类型方法:属于类型本身

有些功能不属于”某一个对象”,而是属于”这一类事物”整体。比如”统计所有玩家解锁到的最高关卡”,这个最高值不属于任何单个玩家,而是全游戏共享的。这类场景用类型方法(type method)。

声明类型方法,在 func 前面加 static 关键字。如果是类,还可以用 class 关键字代替 static,区别在于 class 允许子类重写这个方法。

struct LevelTracker {
    static var highestUnlockedLevel = 1
    var currentLevel = 1

    static func unlock(_ level: Int) {
        if level > highestUnlockedLevel { highestUnlockedLevel = level }
    }
    static func isUnlocked(_ level: Int) -> Bool {
        return level <= highestUnlockedLevel
    }
    mutating func advance(to level: Int) -> Bool {
        if LevelTracker.isUnlocked(level) {
            currentLevel = level
            return true
        }
        return false
    }
}

类型方法用点语法调用,但点是打在类型名上,不是实例上:LevelTracker.unlock(2)。在类型方法内部,隐式的 self 指向类型本身而非实例,所以你写 highestUnlockedLevel 实际访问的是 LevelTracker.highestUnlockedLevel,可以省略前缀。

这里有个组合用法值得注意:LevelTracker 用类型属性 highestUnlockedLevel 记录全局进度,用类型方法 unlock/isUnlocked 操作它;同时用实例属性 currentLevel 和实例方法 advance(to:) 跟踪单个玩家。类型成员和实例成员在同一个类型里和平共处,各管各的层次。

1-3 static 与 class 怎么选

规则很清楚:结构体、枚举里只能用 static,因为它们不能被继承,谈不上”让子类重写”。类里两者都能用——用 static 表示这个方法锁死、子类不能改;用 class 表示敞开重写的大门,子类可以给出自己的版本。

如果你拿不准,类里先用 static。等到第 62 章学重写时你会发现,只有标了 class 的类型方法才允许被子类覆盖。类型属性同理:static 不可重写,class 可重写(仅类可用)。

1-4 值类型方法要改自身得加 mutating

结构体和枚举是值类型,按默认规则,它们的实例方法不能修改属性——因为那会破坏值语义的”不可变”预期。但如果你确实想在方法里改属性,或者干脆把 self 整个换成新实例,就得在 func 前加 mutating 关键字。

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)

加了 mutating 后,方法就能改自己的属性,而且改动会写回原实例。mutating 方法甚至能把 self 整个替换成新实例,比如枚举里切换 case:

enum TriStateSwitch {
    case off, low, high
    mutating func next() {
        switch self {
        case .off: self = .low
        case .low: self = .high
        case .high: self = .off
        }
    }
}

注意一个硬限制:你不能在 let 常量上调用 mutating 方法。因为常量的值(包括值类型实例本身)不能变,哪怕里面是 var 属性也不行。这条和之前讲的结构体常量行为一致。类不存在这个限制,因为类是引用类型,方法改的是对象内部,mutating 关键字也只对值类型有意义。

1-5 一个易混点:self 在类型方法里指类型

前面说实例方法里的 self 指当前实例;类型方法里的 self 指的却是类型本身。所以当你在类型方法里写 self.someTypeProperty,其实是在访问类型属性,用来和同名参数区分。这个细节在写既有类型属性又有同名参数的类型方法时特别管用。

1-6 小结

实例方法管”这一个对象”的行为,用点语法打在实例上调用;类型方法管”这一类”的行为,打在类型名上调用,声明用 static(类也可用 class 放行重写)。值类型的方法若要改自身,记得加 mutating,且不能在常量上调用。把这三者关系理顺,你写出来的类型既能描述数据,又能封装行为,面向对象的感觉就出来了。

1-7 把方法当值传递

在 Swift 里,方法本质上也是函数,函数是一等公民,所以方法也能被当作值传来传去。你可以把某个实例方法赋值给变量,再后续调用它。这点在一些回调、排序场景里很实用。不过要留意:把实例方法存成变量后,它仍绑定着原来的那个实例,调用时会作用在那个实例上。这和闭包捕获 self 是相通的思路。理解了”方法等于绑定了实例的函数”,你对 Swift 里函数与方法的统一模型会更通透。

1-8 调用主体的边界

再理清一个边界:实例方法必须通过实例调用,不能脱离实例单独写 Counter.increment();类型方法则必须打在类型名上,不能打在实例上。混用会编译报错。另外,类型方法里不能访问实例属性,因为此刻根本没有”当前实例”;反过来,实例方法既能访问实例成员,也能访问类型成员。把”有没有实例”这个前提记牢,方法调用的报错基本都能立刻定位。新手常把”忘记创建实例就直接调方法”这类错误,根源就在于没分清调用主体。写方法前先问一句:这逻辑属于某一个对象,还是属于这一类?答案直接决定你用实例方法还是类型方法。