【Swift 访问控制全解析】一篇就够:从 open 到 private,让接口与实现各就其位

为什么需要"访问控制"

  1. 隐藏实现细节,只暴露必要接口
  2. 防止外部误用,减少后续兼容压力
  3. 支持模块化开发(App、Framework、Swift Package 多目标混合)

一句话:接口公开,实现隐藏;该见的见,不该见的永远看不见。

Swift 的三层作用域与六级权限

级别 可见范围 可继承/重写 典型用途
open 模块外可见,可子类化、可 override 框架的"设计出口"
public 模块外可见,不可子类化/override 稳定 API
package 同一 package 内所有模块 ✅/❌ Swift Package 跨模块协作
internal 默认级,仅当前模块 模块内部实现
fileprivate 仅当前源文件 同一文件内多个类型/扩展共享
private 仅当前声明 + 同一文件内扩展 最小化封装单元

默认策略速记

  • 不手写访问修饰符 → internal
  • 类型的默认成员 → 跟随类型(类型 public → 成员 internal)
  • 嵌套类型在 public 类型里 → internal(需要公开再写 public)

代码实战:六级权限一次看个够

swift 复制代码
// 文件:FrameworkA.swift  (属于 FrameworkA 模块)

// 1. open:允许外部模块子类化
open class OpenClass {
    open func overrideMe() {}          // 外部可 override
    public func notOverrideOutside() {} // 外部只能调,不能 override
}

// 2. public:稳定接口,不可继承
public struct PublicAPI {
    public init() {}
    internal var innerCounter = 0      // 模块外不可见
}

// 3. package:同一 Package 内共享
package protocol PackageService {
    func fetch() -> String
}

// 4. internal:不暴露给外部
internal class InnerHelper {
    @MainActor static let shared = InnerHelper()
    private init() {}
}

// 5. fileprivate:同一文件内复用
fileprivate extension String {
    var trimmed: String { trimmingCharacters(in: .whitespaces) }
}

// 6. private:隐藏到"声明内部"
public struct Trimmer {
    private(set) var count: Int = 0   // 只读公开,写私有
    public mutating func trim(_ s: inout String) {
        s = s.trimmed                 // 同一文件,可访问 fileprivate
        count += 1
    }
}

继承与重写中的"升权"

swift 复制代码
// 同一模块内
public class A {
    fileprivate func hidden() {}
}

internal class B: A {
    override internal func hidden() {} // 合法:在同一文件,升级访问权
}

规则回顾:

  1. 子类访问级别 ≤ 父类
  2. 重写可提高访问权,但不能降低
  3. 跨模块只能继承/重写 open 成员

协议、扩展、泛型、别名的细节

  1. 协议
  • 协议权限 ≥ 其所有要求
  • 继承的协议不能比父协议更开放
  • conformance 权限 = min(类型权限, 协议权限)
swift 复制代码
public protocol PublicProtocol {
    func foo()
}
internal class InternalImpl: PublicProtocol {
    func foo() {}   // 自动 internal,满足要求
}
  1. 扩展
  • 扩展可写访问修饰符,为内部成员统一设置默认级
  • 用于协议 conformance 的扩展不能写访问修饰符,由协议本身决定
swift 复制代码
extension Trimmer: CustomStringConvertible {
    public var description: String { "trimmed \(count) times" }
}
  1. 泛型
  • 泛型实体权限 = min(自身权限, 所有约束类型权限)
swift 复制代码
internal protocol CacheKey {}
public struct Cache<T: CacheKey> {} // 实际权限 internal
  1. 类型别名
  • 别名权限 ≤ 原类型权限
  • 利用别名可在模块外"隐藏"真实类型
swift 复制代码
public typealias Token = String   // OK
private typealias SecretDict = [String: Any] // 仅当前文件可用

Package 级访问:多模块仓库的"朋友圈"

场景:一个 Swift Package 包含 Network、UI、Core 三个模块,希望 Core 的接口仅被 Network/UI 使用,而不暴露给最终 App。

swift 复制代码
// Core 模块
package protocol DataLoader {
    package func load() -> Data
}

在 Package 外(App)import Core 后,无法看见 DataLoader,真正做到"仓库内共享,仓库外隔离"。

常见踩坑与调试技巧

错误提示 原因 解决
Cannot assign to property: 'count' is a get-only property 用了 private(set) 却在外部赋值 移除 setter 限制或提供内部 API
Class cannot be declared public because its superclass is internal 子类比父类"显眼" 提升父类或降低子类
Function cannot be declared internal because its parameter uses a private type 函数权限 > 参数权限 提升类型权限或降低函数权限

总结与工程实践建议

  1. 写框架先画"可见性矩阵":哪些类需要被继承?哪些 API 未来必须冻结?

    • 需要被继承 → open
    • 仅调用 → public
    • 仓库内复用 → package
    • 模块内复用 → internal
    • 文件内工具 → fileprivate
    • 纯内部辅助 → private
  2. 先写 internal,真正需要暴露时再升级,避免"过度公开"

  3. private(set)"只读公开"模式上瘾,可大幅减少后续 Breaking Change。

  4. @testable 而非"为了测试把 private 改成 public"。

  5. 大型 Package 采用"Core → Service → UI"三级依赖,配合 package 访问级,保证依赖方向无环,又隐藏核心实现。

扩展场景:访问控制 + SwiftUI + 插件化架构

SwiftUI 的 public 初始化器经常需要接收"仅内部使用的配置对象"。此时可用类型擦除 + 协议权限组合:

swift 复制代码
// 对外只能拿到协议
public protocol ConfigProtocol {}

// 实际配置在模块内
internal struct RealConfig: ConfigProtocol {
    var apiKey: String
}

public struct MyView: View {
    public init(config: ConfigProtocol) { ... }
}

App 只能持有 ConfigProtocol,无法直接访问 apiKey,实现"接口公开,配置隐藏"。

一句话背下来

"高"不能依赖"低",默认 internal 先写着;需要再升级,绝不一步到位全 public

相关推荐
HarderCoder4 小时前
Swift 6 实战:从“定时器轮询”到 AsyncSequence 的优雅实时推送
swift
Daniel_Coder10 小时前
iOS Widget 开发-9:可配置 Widget:使用 IntentConfiguration 实现参数选择
ios·swiftui·swift·widget·intents
非专业程序员Ping12 小时前
Vibe Coding 实战!花了两天时间,让 AI 写了一个富文本渲染引擎!
ios·ai·swift·claude·vibecoding
m0_4955627814 小时前
Swift的逃逸闭包
服务器·php·swift
m0_4955627815 小时前
Swift-static和class
java·服务器·swift
m0_495562782 天前
Swift-GCD和NSOperation
ios·cocoa·swift
HarderCoder2 天前
Swift 并发:我到底该不该用 Actor?——一张决策图帮你拍板
swift
HarderCoder2 天前
深入理解 DispatchQueue.sync 的死锁陷阱:原理、案例与最佳实践
swift
东坡肘子2 天前
Skip Fuse现在对独立开发者免费! -- 肘子的 Swift 周报 #0110
android·swiftui·swift