Swift 访问控制(Access Control)详解
Swift 的访问控制用于限制代码中类型、属性、方法、初始化器、下标等成员的访问权限,防止外部误用内部实现,增强封装性。
1. 访问级别概览(由高到低)
| 级别 | 关键字 | 说明 |
|---|---|---|
open | open | 类和类成员可被继承和重写(仅类可用) |
public | public | 可被任何模块访问,可重写(但类不可继承) |
internal | internal(默认) | 仅当前模块内可访问 |
fileprivate | fileprivate | 仅当前源文件内可访问 |
private | private | 仅当前作用域(类、结构体、枚举、扩展)内可访问 |
模块(Module):一个独立编译单元(如 Framework、App Target)
源文件(Source File):一个.swift文件
2. 各访问级别详解
open(最高)
- 仅用于类和类成员
- 允许其他模块继承和重写
open class Vehicle {
open func start() { print("Engine started") }
}
// 另一个模块
class Car: Vehicle {
override open func start() { print("Car started") } // 允许
}
public
- 可被任何模块访问
- 类成员可被重写,但类本身不可被继承
public class Logger {
public func log(_ msg: String) { print(msg) }
}
// 外部模块
class MyLogger: Logger { } // 错误!public 类不可继承
想允许继承?用
open
internal(默认)
- 模块内部任意访问
- 不写关键字即为
internal
class DatabaseManager { // internal
func connect() { ... }
}
fileprivate
- 仅当前
.swift文件内可访问 - 常用于辅助方法、扩展
struct User {
fileprivate var id: Int = 0
}
extension User {
func reset() {
id = 0 // 同一文件,可访问
}
}
// 其他文件
let user = User()
user.id // 错误!fileprivate
private
- 仅当前作用域(类、结构体、枚举、扩展)内可访问
- 最严格
class BankAccount {
private var balance: Double = 0
func deposit(_ amount: Double) {
balance += amount
}
}
extension BankAccount {
func info() -> String {
return "Balance: \(balance)" // 同一扩展,可访问
}
}
let account = BankAccount()
account.balance // 错误!
3. 访问控制适用范围
| 成员 | 可设置访问级别 |
|---|---|
| 类型(class/struct/enum) | Yes |
| 属性(var) | Yes |
| 方法(func) | Yes |
| 初始化器(init) | Yes |
| 下标(subscript) | Yes |
| 协议(protocol) | Yes |
| 扩展(extension) | Yes(扩展本身无级别,继承被扩展类型的级别) |
4. 关键规则(必须遵守)
规则 1:成员不能比其类型更开放
public class MyClass {
private func secret() { } // OK
public var name: String // OK
}
public struct MyStruct {
fileprivate var id: Int // 错误!fileprivate < public
}
修正:
internal var id: Int或降低MyStruct为internal
规则 2:函数参数和返回值不能比函数更严格
class Helper { }
public class API {
private func process(data: Helper) { } // 错误!参数比函数更严格
}
修正:
internal func process(data: Helper)
规则 3:元组类型的访问级别 = 最严格成员的级别
func getInfo() -> (String, fileprivate Struct) {
// 返回类型为 fileprivate
}
规则 4:getter/setter 可单独设置
public struct User {
private(set) var id: Int = 0 // 可读,外部不可写
public var name: String { // 可读可写
get { _name }
set { _name = newValue }
}
private var _name: String = ""
}
let user = User()
print(user.id) // OK
user.id = 1 // 错误!
user.name = "Tom" // OK
5. 扩展(Extension)中的访问控制
public class Counter {
private var value = 0
}
extension Counter {
func increment() { value += 1 } // 默认继承 Counter 的级别
fileprivate func debug() { print(value) }
}
扩展中添加的成员,访问级别独立设置
6. 协议(Protocol)的访问控制
public protocol Loggable {
func log()
}
internal protocol Configurable { ... }
协议要求的所有成员,至少要和协议一样开放
public protocol JSONExportable {
func toJSON() -> String
}
struct Post: JSONExportable {
func toJSON() -> String { ... } // 至少 internal
}
7. 初始化器(init)的特殊规则
默认规则:
struct:无参初始化器与结构体同级class:指定初始化器与类同级
自定义初始化器:
public struct Point {
public var x, y: Double
private init() { // 私有构造
x = 0; y = 0
}
public init(x: Double, y: Double) {
self.x = x; self.y = y
}
}
外部无法调用
Point(),只能用Point(x:y:)
8. 实际应用场景
| 场景 | 推荐访问级别 |
|---|---|
| 公开 API(如 SDK) | public / open |
| 框架内部工具类 | internal |
| 辅助方法 | fileprivate |
| 敏感数据 | private |
| 只读属性 | private(set) |
9. 常见错误示例
// 错误 1:成员比类型更公开
public struct API {
fileprivate var token: String // 错误!
}
// 错误 2:参数比函数严格
public func login(user: private User) // 错误!
// 错误 3:协议要求未满足
public protocol Validatable {
func validate() -> Bool
}
struct Form: Validatable {
private func validate() -> Bool { true } // 错误!private < public
}
10. 最佳实践(Best Practices)
// 1. 默认使用 private / fileprivate
class NetworkManager {
private let session = URLSession.shared
private func parse<T: Decodable>(_ data: Data) -> T? { ... }
}
// 2. 公开 API 明确标注 public
public struct APIClient {
public init() { }
public func request<T: Decodable>(_ type: T.Type) -> T? { ... }
}
// 3. 使用 private(set) 保护状态
public struct Counter {
public private(set) var count = 0
public mutating func increment() { count += 1 }
}
11. 访问控制 vs 封装
| 目标 | 手段 |
|---|---|
| 隐藏实现 | private / fileprivate |
| 防止继承 | final + private |
| 控制读写 | private(set) |
| 模块隔离 | internal |
面试高频题
open和public的区别?
→open允许继承和重写,public只允许重写。- 为什么
private成员在扩展中仍可访问?
→ 扩展与原类型在同一作用域。 - 如何让结构体只能通过工厂方法创建?
public struct User {
private init() { }
public static func create() -> User { User() }
}
小结:访问控制金字塔
open ← 仅类,允许继承重写
public ← 跨模块访问
internal ← 模块内(默认)
fileprivate ← 文件内
private ← 作用域内(最严)
需要 访问控制 + 模块化设计实战?如:
- 构建一个
CoreKit框架 - 设计
public API与内部实现分离 - 使用
private防止误用
欢迎继续提问!