
Swift 访问控制完全指南用 open、public、package、internal、fileprivate、private 精确管理代码可见性【免费下载链接】the-swift-programming-language-in-chinese中文版 Apple 官方《Swift 编程语言》项目地址: https://gitcode.com/gh_mirrors/th/the-swift-programming-language-in-chinese导读访问控制Access Control是 Swift 语言中用于管理代码可见性的核心机制它让你能够隐藏实现细节、只暴露精心设计的对外接口从而为模块化开发、框架设计与单元测试提供坚实保障。本文基于中文版《Swift 编程语言》The Swift Programming LanguageTSPL的 访问控制章节系统讲解模块、源文件与包三级模型、六种访问级别及其适用场景并结合本仓库的源码与文档证据深入剖析每个访问级别对类型、函数、枚举、构造器、协议、扩展、泛型与类型别名的具体影响。读完本文你将能独立设计一套从单 Target 应用到多模块框架再到单元测试场景下的完整访问控制策略并准确理解private(set)、testable、open继承等进阶写法的底层规则。访问控制模型模块、源文件与包Swift 的访问控制并非简单地给代码打标签而是建立在一个由三个层级构成的分发模型之上模块Module代码分发的独立单元。一个框架或应用程序作为一个整体被构建和发布并通过import关键字被其他模块导入。在 Swift 中Xcode 的每个构建目标应用程序或框架都被视为一个独立模块——当你把代码打包成独立框架并在多个应用间复用时框架内定义的所有内容即构成一个独立模块。源文件Source File模块中的一个 Swift 源代码文件。虽然习惯上会把不同类型分别放在不同文件中但同一个源文件完全可以包含多个类型、函数等的定义。包Package一组模块的集合作为一个整体进行开发。包由构建系统配置而非 Swift 源代码决定使用 Swift Package Manager 时在Package.swift中通过 PackageDescription 模块的 API 定义包使用 Xcode 时则在Package Access Identifier构建设置中指定包名。理解这三层模型是掌握访问级别的前提——每个访问级别都精确对应着源文件内、模块内、包内、模块外这四个可见范围中的某一个。在中文版 TSPL 中本主题的完整论述见 AccessControl.md 开篇章节它是后续所有规则的基石。六种访问级别从 open 到 privateSwift 为实体提供了六种访问级别它们从限制最少到限制最多依次排列具体取决于实体所在的源文件、模块与包访问级别可见范围典型用途open模块内任意源文件 导入该模块的其他模块且允许继承/重写框架中希望被外部子类化的基类public模块内任意源文件 导入该模块的其他模块框架对外 APIpackage同一模块内任意源文件不能超出包范围多模块应用/框架内部的模块间共享internal同一模块内任意源文件应用/框架的内部结构体也是默认级别fileprivate定义它的源文件内仅在当前文件内使用的实现细节private声明所在作用域 同一文件中该声明的扩展内单个声明内部使用的实现细节几点关键辨析open 与 public 的区别open仅适用于类及类的成员它额外允许模块外的代码对类进行继承和对成员进行重写。将类显式指定为open意味着你已考虑到其他模块将该类作为父类的场景并为此设计了代码。package 的定位package介于 public 与 internal 之间适合在包含多个模块的应用或框架中共享代码但又不希望这些代码成为对外公开 API 的情形。private 的边界private限制的是声明所在的作用域以及同一文件中该声明的扩展extension而非整个文件——这是它与fileprivate的本质区别。访问级别的指导原则Swift 访问控制遵循一条贯穿全章的总原则实体的定义不能依赖于访问级别比它更低更严格的其他实体。两个最典型的推论是一个public变量其类型的访问级别不能是internal、fileprivate或private因为在 public 变量被使用的地方这些类型可能不可见。函数的访问级别不能高于其参数类型和返回类型的访问级别因为函数可能在这些类型不可用的上下文中被调用。这条原则将反复出现在下文元组类型、函数类型、构造器、泛型、类型别名等各个小节中是判断某段代码能否编译的最有力武器。默认访问级别与三种典型场景默认值除少数特例外所有实体未显式指定访问级别时默认均为internal因此大多数情况下你无需写任何访问级别修饰符。单 Target 应用程序代码只供自身使用默认的internal已完全满足需求若想进一步隐藏实现细节可把部分代码降级为fileprivate或private。框架对外接口必须显式指定为open或public这部分接口即框架的 API内部实现细节可继续使用默认的internal需要时用private/fileprivate进一步隐藏。单元测试 Target默认情况下其他模块只能访问open或public实体。若在导入产品模块时使用testable属性并开启测试编译选项则测试 Target 可访问所有internal实体。这一点在 语言参考的 Attributes.md 中有更精确的表述testable会把被导入模块中internal的实体当作public处理把internal/public的类及类成员当作open处理——这正是测试代码能够实例化内部类型并重写其成员的原因。访问控制语法在实体声明前添加访问级别修饰符即可定义其访问级别public class SomePublicClass {} internal struct SomeInternalStruct() {} private func somePrivateFunction() {}若不写显式修饰符则默认是internalstruct SomeInternalStruct() {}SomeInternalStruct在这里隐式地是 internal。自定义类型的访问级别与成员继承规则为自定义类型指定访问级别就是在定义类型时加上修饰符新类型即可在其访问级别允许的范围内使用。例如一个fileprivate类只能在定义它的源文件中充当属性的类型、函数的参数类型或返回类型。一个关键规则是类型的访问级别会影响其成员的默认访问级别类型为private或fileprivate时其成员默认也是private或fileprivate类型为internal或public或未指定而默认 internal时其成员默认是internal。重要提示public类型的成员默认是internal而不是public想让某个成员是public必须显式声明。这一设计确保公共 API 都是你刻意选择发布的避免把内部接口意外暴露出去。public class SomePublicClass { // 显式指定为 public 类 public var somePublicProperty 0 // 显式指定为 public 类成员 var someInternalProperty 0 // 隐式指定为 internal 类成员 fileprivate func someFilePrivateMethod() {} // 显式指定为 fileprivate 类成员 private func somePrivateMethod() {} // 显式指定为 private 类成员 } class SomeInternalClass { // 隐式指定为 internal 类 var someInternalProperty 0 // 隐式指定为 internal 类成员 fileprivate func someFilePrivateMethod() {} // 显式指定为 fileprivate 类成员 private func somePrivateMethod() {} // 显式指定为 private 类成员 } fileprivate class SomeFilePrivateClass { // 显式指定为 fileprivate 类 func someFilePrivateMethod() {} // 隐式指定为 fileprivate 类成员 private func somePrivateMethod() {} // 显式指定为 private 类成员 } private class SomePrivateClass { // 显式指定为 private 类 func somePrivateMethod() {} // 隐式指定为 private 类成员 }元组类型取成员中最严格的级别元组类型没有独立的定义其访问级别由组成元组的各个类型中最严格的那个决定且不能显式指定。例如一个由internal类型和private类型构成的元组其整体访问级别是private。同理包含 private 成员的元组无法在定义它的源文件之外被访问。函数类型计算得出可能强制要求显式声明函数类型的访问级别由参数类型和返回类型中最严格的级别计算得出。如果计算结果与上下文默认值internal不匹配就必须在函数定义中显式指定访问级别。看这个无法编译的例子func someFunction() - (SomeInternalClass, SomePrivateClass) { // 此处是函数实现部分 }返回类型是一个由internal类与private类组成的元组因此该元组是private级别而函数默认是internal违反了函数访问级别不能高于返回类型的指导原则。编译器会直接报错function must be declared private or fileprivate because its result uses a private type。正确写法是private func someFunction() - (SomeInternalClass, SomePrivateClass) { // 此处是函数实现部分 }把该函数声明为public或internal都是非法的因为public/internal的使用者可能无法访问返回类型中的private类。枚举类型成员与枚举同级原始值/关联值有限制枚举成员的访问级别与所属枚举完全相同不能为单个成员单独指定级别public enum CompassPoint { case north case south case east case west }CompassPoint是public四个成员也全部是public。此外枚举的原始值或关联值类型的访问级别至少不能低于枚举本身——你不能在一个internal枚举里使用private类型作为原始值类型。嵌套类型继承外层类型public 是特例嵌套类型的访问级别与包含它的类型相同唯一的例外是定义在public类型中的嵌套类型其默认访问级别是internal想让嵌套类型也是public必须显式声明。这一规则与public 类型的成员默认 internal一脉相承。子类继承与重写的边界可以继承同一模块中所有可访问的类也可以继承不同模块中由open修饰的类。子类的访问级别不得高于父类你不能用public子类继承internal父类。重写成员时同一模块的类可重写上下文中可访问的任意成员其他模块的类只能重写open成员。一个有趣的例外是通过重写可以提升成员的访问级别。下面这个例子中类A是public其方法someMethod()是fileprivate子类B将访问级别降低为internal却把someMethod()重写为internal高于原级别public class A { fileprivate func someMethod() {} } internal class B: A { override internal func someMethod() {} }更进一步只要调用父类成员的操作发生在允许的访问级别上下文中例如同一源文件内调用fileprivate成员、同一模块内调用internal成员子类成员调用更低权限的父类成员也是合法的public class A { fileprivate func someMethod() {} } internal class B: A { override internal func someMethod() { super.someMethod() } }因为父类A和子类B定义在同一个源文件中B才能在重写的方法里调用super.someMethod()。常量、变量、属性与下标常量、变量或属性的访问级别不能高于其类型的访问级别下标不能高于其索引类型或返回类型的访问级别。若其类型是private则声明本身也必须是privateprivate var privateInstance SomePrivateClass()如果类型是 private 而声明没有显式给出private编译器会报错如property must be declared private because its type uses a private type、subscript must be declared private because its element type uses a private type。Getters 与 Setters读写分离的利器常量、变量、属性和下标的getter与setter自动获得与它们所属实体相同的访问级别。但你可以为setter指定一个比 getter 更低的访问级别从而限制写入范围——通过在var或subscript前写fileprivate(set)、private(set)、internal(set)或package(set)即可。注意此规则同时适用于存储属性和计算属性。即使存储属性没有显式编写 getter/setterSwift 也会合成隐式的 getter 和 setter这些隐式 setter 同样可以被private(set)等修饰。经典示例TrackedString用private(set)实现外部只读、内部可写struct TrackedString { private(set) var numberOfEdits 0 var value: String { didSet { numberOfEdits 1 } } }TrackedString与value默认都是internal而numberOfEdits是private(set)getter 仍是internal但只能在结构体内部赋值外部呈现为只读属性。测试它的行为var stringToEdit TrackedString() stringToEdit.value This string will be tracked. stringToEdit.value This edit will increment numberOfEdits. stringToEdit.value So will this one. print(The number of edits is \(stringToEdit.numberOfEdits)) // 打印The number of edits is 3。你可以从其他源文件读取numberOfEdits的当前值但无法修改它——编译器会给出numberOfEdits setter is inaccessible之类的错误。这既保护了编辑跟踪功能的实现细节又保留了方便的读取方式。getter 与 setter 也可分别指定显式级别。把TrackedString变成 public 结构体时组合public与private(set)即可让 getter 为public、setter 为privatepublic struct TrackedString { public private(set) var numberOfEdits 0 public var value: String { didSet { numberOfEdits 1 } } public init() {} }构造器自定义构造器的访问级别可以低于或等于它所初始化的类型唯一例外是必要构造器required initializer详见 初始化章节它必须与所属类具有相同的访问级别。与函数和方法类似构造器的参数类型访问级别不能比构造器自身更严格。默认构造器只要结构体/类为所有存储属性提供了默认值且未自定义构造器Swift 就会自动生成无参默认构造器。它的访问级别与类型相同除非类型是public——public 类型的默认构造器是internal想让其他模块能通过无参构造器初始化必须在类型定义中显式提供public的无参构造器。结构体成员逐一构造器memberwise initializer若任一存储属性是private成员逐一构造器即为private若任一属性是fileprivate则为fileprivate否则为internal。同样地想让public结构体在其他模块中可通过成员逐一构造器初始化需要显式提供public版本。协议、协议继承与协议遵循为协议显式指定访问级别即可限制它只能在特定范围内被遵循。规则包括协议要求与协议同级别协议中每个要求都必须与协议本身访问级别相同不能单独调整。编译器会提示protocol requirements implicitly have the same access as the protocol itself。public 协议要求全部 public实现 public 协议时所有要求的实现也必须是public。这与普通类型不同——普通 public 类型的成员默认是 internal。协议继承新协议的访问级别最高只能与其继承的协议相同不能写public协议继承internal协议。协议遵循是全局的与 Objective-C 一致同一程序中一个类型不可能用两种不同方式遵循同一个协议。协议遵循的上下文访问级别是类型和协议中访问级别较低的那个。例如一个public类型遵循internal协议则该遵循的上下文是internal且对协议每个要求的实现必须至少是internal——这保证了协议要求在其可见范围内始终可用。本仓库的 Properties.md 也印证了这一机制属性包装器示例中number被标记为private确保它只能在实现内部访问外部代码只能经由 wrappedValue 的 getter/setter 访问。扩展默认级别与私有成员互通对类、结构体或枚举进行扩展时扩展内新增成员的默认访问级别与原始类型一致扩展public/internal类型时新增成员默认internal扩展fileprivate类型时默认fileprivate扩展private类型时默认private。也可以用显式修饰符标记整个扩展为内部所有成员设定新的默认级别单个成员仍可单独覆盖。但用扩展遵循协议时不能给扩展加访问级别修饰符此时协议自身的访问级别会成为各要求实现的默认级别。扩展的私有成员是 Swift 中非常实用的特性扩展同一文件内的类型时扩展里的代码表现得与声明在原始类型中完全一样。因此你可以在原始声明中声明private成员并在同一文件的扩展中访问它在一个扩展中声明private成员并在同一文件的另一个扩展中访问它在扩展中声明private成员并在同一文件的原始声明中访问它。这意味着你可以用扩展来组织代码结构例如把协议遵循集中放在扩展中而不必担心private成员的可见性protocol SomeProtocol { func doSomething() } struct SomeStruct { private var privateVariable 12 } extension SomeStruct: SomeProtocol { func doSomething() { print(privateVariable) } }运行s.doSomething()会打印12——扩展成功访问了原始声明中的private属性。泛型与类型别名泛型泛型类型或泛型函数的访问级别取决于它本身的访问级别与其类型参数约束的访问级别取两者中较低者。类型别名在访问控制层面类型别名被视为独立的类型其访问级别不能高于所表示的类型。一个private类型别名可以为private、fileprivate、internal、public甚至open类型做别名但public类型别名不能为internal、fileprivate或private类型做别名。此规则同样适用于为满足协议遵循而把类型别名用于关联类型的情况。违反时编译器会报type alias cannot be declared public because its underlying type uses a private type之类的错误。在中文版 TSPL 仓库中的上下文本文对应的完整章节位于 TSPL.docc/LanguageGuide/AccessControl.md它是中文版《Swift 编程语言》当前对应 Swift 6.2.3语言指南Language Guide的组成部分与 The-Swift-Programming-Language.md 文档目录 中的 TheBasics、Properties、Initialization、Protocols、Extensions、Generics 等章节相互配合。该仓库由 SwiftGG 团队维护使用 Swift-DocC 构建可在仓库根目录运行docc previewXcode 环境下为xcrun docc preview在浏览器中预览全部中文文档详见 README.md。需要与访问控制搭配学习的内容还包括属性Properties属性观察器与private组合使用的实战场景构造过程Initialization必要构造器与默认构造器的完整规则协议Protocols 与 扩展Extensions访问控制在这些主题中的延伸应用语言参考之 Attributes特性testable特性的精确定义是理解单元测试访问控制的关键补充。小结一张访问控制决策表使用场景推荐级别框架对外 API 中允许外部继承/重写的类open框架对外 API 中只读不可继承的接口public多模块应用内部、模块间共享但不对外的代码package应用/框架内部默认实现internal默认值仅在当前源文件内使用的实现细节fileprivate仅限当前作用域含同文件扩展的实现细节private属性对外只读、内部可写private(set)/fileprivate(set)/package(set)单元测试访问模块内部实现testable导入 启用测试编译选项掌握这六种访问级别与定义不能依赖更低级别实体的指导原则你就能像设计 API 一样精确地雕刻代码的可见边界——既保护实现细节又为用户与协作者提供清晰、稳定、可预期的接口。【免费下载链接】the-swift-programming-language-in-chinese中文版 Apple 官方《Swift 编程语言》项目地址: https://gitcode.com/gh_mirrors/th/the-swift-programming-language-in-chinese创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考