首页 / Swift 编程语言教程 / 访问控制

Swift 编程语言教程

访问控制

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

Swiftaccess control访问控制openpublicinternalfileprivateprivate

本节目标:理解 Swift 的访问级别,学会用 private/fileprivate/internal/public/open/package 控制代码在模块、源文件、包与声明之间的可见范围。

写一个小程序时,所有代码随你取用,几乎不用操心谁能看见谁。可一旦代码变多、要拆成框架给别人用,你就得想清楚:哪些是对外公开的能力,哪些是内部实现、不该泄露的细节。访问控制(access control)就是 Swift 用来划定”可见范围”的机制——它把实现细节藏起来,只暴露你愿意给的接口。

1-1 三个基本概念

Swift 的访问控制建立在三个概念上。

模块(module)是代码分发的基本单元:一个框架或 App,作为整体被构建、被别的模块用 import 引入。Xcode 里每个构建目标(target)就是一个独立模块。源文件(source file)是模块里单个的 .swift 文件。包(package)则是用 Swift Package Manager 等工具组织起来的一组模块。

访问级别就是相对”定义该代码的源文件""它所属的模块""它所属的包”来衡量的。理解了这三者,下面的级别才好懂。

1-2 六个访问级别

Swift 提供从宽松到严格的六个访问级别。从高(最开放)到低(最封闭)依次是:

  • openpublic:允许在定义模块的任何源文件使用,也允许别的模块引入后使用。通常用来标一个框架的对外接口。open 只用于类和类成员,比 public 更开放——它允许别的模块继承、重写。
  • package:允许在定义包内的任何源文件使用,但包外不行。适合把一个 App 或框架拆成多个模块、又只想在包内共享时。
  • internal:允许在定义模块的任何源文件使用,模块外不行。这是绝大多数代码的默认级别。
  • fileprivate:只允许在定义它的那个源文件里使用。用来把”只在这一文件内有用”的实现细节藏起来。
  • private:只允许在”包裹它的声明”及同文件的该声明扩展里使用,范围比 fileprivate 更小。
Note

openpublic 的区别只在类和类成员上:public 类不能被别的模块继承或重写,而 open 可以。给框架设计公共类时,若你考虑过”允许外部当父类”,才标 open

1-3 默认访问级别

如果你不写任何访问修饰符,大多数代码默认是 internal。也就是说,写个简单单目标的 App,你几乎从不需要显式标访问级别——默认的 internal 已经满足”模块内随便用”。只有想对外公开(标 public/open)或想隐藏细节(标 fileprivate/private)时,才需要动手。

一个总体原则贯穿所有规则:不能让一个实体建立在”访问级别更低”的另一个实体之上。比如一个 public 变量的类型不能是 internalprivate 的类型——否则在变量能被用到的地方,它的类型却可能不可见。函数也不能比它的参数类型和返回类型更开放。编译器会替你把这道关。

1-4 自定义类型与它的成员

给类型标访问级别时,写在该类型定义处。类型的访问级别会”传染”给它的成员(属性、方法、初始化器、下标)的默认级别。

关键一点:public 类型的成员默认是 internal,不是 public。也就是说,你标了一个类是 public,但它的方法、属性并不会自动对外公开,还得逐个显式标 public。这条规矩防止你”不小心把内部实现暴露成公共 API”。

public class SomePublicClass {
    public var somePublicProperty = 0     // 显式 public
    var someInternalProperty = 0          // 隐式 internal
    fileprivate func someFilePrivateMethod() {}
    private func somePrivateMethod() {}
}

反过来,如果类型是 privatefileprivate,它的成员默认也跟着是 private / fileprivate;类型是 internal(或默认),成员默认 internal

1-5 子类与重写

对子类有个限制:子类的访问级别不能高于父类。你不能写一个 public 子类去继承一个 internal 父类——因为父类的细节在外部可能根本不可见,继承就无意义。

但重写可以把一个成员的可见度”调高”。比如父类有个 fileprivate 方法,子类(同模块、甚至同文件)可以 override 成 internal

public class A {
    fileprivate func someMethod() {}
}
internal class B: A {
    override internal func someMethod() {}
}

只要重写发生在允许访问原成员的上下文里(同文件或同模块),就能把可见度提上来。这一条让”父类低调、子类在合适范围内放开”成为可能。

1-6 读写分离:只暴露 getter

常量、变量、属性、下标的 getter 和 setter,默认跟它们所属实体的访问级别一致。但你有权把 setter 设得比 getter 更严格,从而做出”外部只读、内部可写”的属性。

写法是在 var 前加 private(set)internal(set)fileprivate(set)

struct TrackedString {
    private(set) var numberOfEdits = 0
    var value: String = "" {
        didSet {
            numberOfEdits += 1
        }
    }
}

numberOfEdits 的 getter 是默认的 internal,但 setter 被限成 private——只有 TrackedString 内部能改它,外部只能读。于是别的文件能查”被改了几次”,却改不了这个计数,实现细节被稳稳护住。

如果是 public 结构体想对外只读,就写 public private(set):getter 是 public,setter 仍是 private

public struct TrackedString {
    public private(set) var numberOfEdits = 0
    public var value: String = "" {
        didSet { numberOfEdits += 1 }
    }
    public init() {}
}
Tip

“只读对外、只内部改”是访问控制里极常用的模式,用 private(set) 一行就能搞定,不必写两个属性去手动同步。

1-7 扩展里的访问控制

扩展可以在被扩展类型”可见”的任意访问上下文里写。扩展里新增的成员,默认访问级别和原类型成员一致:扩展一个 publicinternal 类型,新成员默认 internal;扩展 fileprivate 类型,新成员默认 fileprivate

你也可以给整个扩展标一个访问级别,作为里面所有成员的”新默认”,个别成员还能再覆盖。但有一种例外:当扩展是用来”添加协议遵循”时,不能给扩展标访问级别——这时协议自己的级别决定了每个协议要求实现的默认级别。

还有一个贴心规则:和原类型写在同一个文件里的扩展,视作原类型声明的一部分。所以你可以在原声明里写 private 成员,在同文件的扩展里访问它;反过来在扩展里写 private 成员,原声明里也能访问。这让”用扩展来整理代码”无论是私有还是公有成员都顺手。

1-8 小结

访问控制用 open/public/package/internal/fileprivate/private 把代码可见范围收进模块、包、源文件、声明之内。默认是 internal,够用就不必写;只有想对外公开才标 public/open,想藏细节才标 fileprivate/privatepublic 类型的成员默认仍是 internal,要公开得逐个显式标。子类不能高于父类,但重写可提升可见度。用 private(set) 能做”对外只读、内部可写”。扩展的成员默认跟随原类型,同文件扩展视作一体。下章我们看类型嵌套,把相关的类型收进同一作用域。