首页

时间和精神的房子
壹只iOS程序员的修行世界,欢迎来访

如果文章对您有所帮助
将是我最大的荣幸

Swift中的访问控制

Objective-C 拥有 4 个访问控制相关的修饰符 @public @protected @private 和 @package,他们只能用来修饰成员变量。@public 修饰的成员变量可以任何地方进行访问,@protected 修饰的成员变量可以当前类及其子类中访问,@private 修饰的成员变量只可以在当前类访问。@package 修饰的成员变量在当前 Framework 中访问。成员变量的默认修饰符为 @protected。

但是对于动态语言来说,访问控制并没有真正的意义,就算是 @private 的成员变量你仍然有无数种方式来访问他们。所以在 Objective-C 中访问控制本质上只是用来隐藏实现细节,告诉使用者哪些才是真正需要关心的,并为不需要关心部分的直接调用创造一些麻烦。

在 Objective-C 中这些访问控制的修饰符很少被使用,因为 Objective-C 有很多更好的隐藏细节的方式。一般情况下,我们使用 @property 隐藏成员变量来实现读写权限控制。需要 @public 权限时,我们只需要将 @property 定义在 .h 文件中。需要 @private 权限时,只需要把 @property 定义在 Class Extension 中,并将其放在 .m 文件。需要 @protected 权限时,只需要将 Class Extension 单独定义在 .h 文件,并将其同时 import 到父类和子类。

因为 Objective-C 的动态性导致不可能有真正意义上的访问控制。不管通过什么方式来隐藏细节,我们都可以通过 Runtime 来访问任意的变量、方法、类等。甚至在 .m 中的定义,只要将 .m 文件从 Target Membership 中移除,也可以通过直接 import .m 文件来实现对隐藏的变量、方法的直接调用

其实你已经发现,Objective-C 中访问控制的修饰符与 C++ 类似,与 Cocoa 的编程习惯并不相符。所以在 Swift 中苹果对访问控制的修饰符重新进行了设计。

Swift 中有 5 种优先级的修饰符 private fileprivate internal public open。private 只能在当前作用域访问。fileprivate 只能在当前文件访问。internal 只能在模块内访问。public 和 open 可以在模块外访问,但 public 修饰的类不允许在模块外继承类或重写类的成员(final 在任何地方都不能继承),open 则可以。

Objective-C 到 Swift 从 .h.m 两个文件变成 .swift 单个文件。所以 Swift 就必须通过优先级修饰符来解决接口和实现的分离。下边是他们的对应关系。

  • Objective-C 在 .h 定义的类、属性、方法对应 Swift 中的 open 优先级。
  • Objective-C 在 .h 定义结构体、枚举对应 Swift 中的 public 优先级。
  • Objective-C 在 .m 定义的类对应 Swift 中的 private 或 fileprivate 优先级。
  • Objective-C 在 .m 定义的属性、方法、结构体、枚举对应 Swift 中的 fileprivate 优先级。

上述对应关系并没有实际意义。因为 Swift 中建议的访问控制级别跟我们在 Objective-C 中的习惯并不一样。比如 Swift 建议的默认优先级是 internal 而并不是 public。我们可以在对比中发现在访问控制方面 Swift 为什么比 Objective-C 更加安全合理。

之所以用 Objective-C 的访问控制来对应 Swift 的修饰符是因为 Swift 提供了远比这些复杂的访问控制。Swift 除了上述 5 种访问控制的修饰符外,还有很多相关的规则。这虽然带来了更高的学习成本,但也带来了更加灵活的访问控制,也充分体现了 Swift 对安全的追求。下边列举 Swift 中对访问控制主要的规则。你可以查看语法书(The Swift Programming Language)来了解更详细的信息。

  • open 只作用于类及其成员,因为只有类及其成员可以被继承和重写。
  • 函数的访问级别不能高于它的参数类型和返回类型的访问级别。如果默认访问级别不符合此规则就需要显式指明访问级别。
  • 实体的默认级别为 internal,只有在开发框架时才需要考虑使用 open 或 public 暴露 API。
  • 单元测试 Tagert 可以通过 @testable import 实现对项目 Tagert 中所有内部级别实体的访问。
  • 如果类型为 private 或 fileprivate,那么所有类型成员和嵌套类型的默认访问级别也将是 private 或 fileprivate。
  • 如果类型为 public 或 internal,那么所有类型成员和嵌套类型的默认访问级别将是 internal。
  • 元组的访问级别将由元组中访问级别最严格的类型来决定。
  • 枚举成员的访问级别和该枚举类型相同,你不能为枚举成员单独指定不同的访问级别。
  • 枚举定义中的任何原始值或关联值的类型的访问级别至少不能低于枚举类型的访问级别。
  • 子类的访问级别不得高于父类的访问级别。
  • 可以通过重写为继承来的类成员提供更高的访问级别。
  • 下标不能拥有比索引类型或返回类型更高的访问级别。
  • Setter 的访问级别可以低于对应的 Getter 的访问级别,这样就可以控制变量、属性或下标的读写权限。
  • 自定义构造器的访问级别可以低于或等于其所属类型的访问级别。
  • 必要构造器的访问级别必须和所属类型的访问级别相同。
  • 协议中的每一个要求都具有和该协议相同的访问级别,你不能将协议中的要求设置为其他访问级别。
  • 类型可以采纳比自身访问级别低的协议。
  • 继承来的协议拥有的访问级别最高也只能和被继承协议的访问级别相同。
  • 采纳了协议的类型的访问级别取它本身和所采纳协议两者间最低的访问级别。
  • 扩展成员具有和原始类型成员一致的访问级别。
  • 如果你通过扩展来采纳协议就不能显式指定扩展的访问级别。协议会为该扩展中所有协议要求的实现提供默认的访问级别。
  • 类型别名会被当作不同的类型,其访问级别不可高于其表示的类型的访问级别。

参考资料

Objective-C: How do you access parent properties from subclasses?
Best way to define private methods for a class in Objective-C
GCC Online Docs 2.1 Include Syntax
Xcode: Which files need to be members of my target? (Target Membership)

关注作者

分享本文

目录