Swift语法2.24(访问控制)

访问控制(Access Control)

本文内容包括:

  • 模块和源文件
  • 访问级别
  • 访问级别的基本原则
  • 默认访问级别
  • Single-Target应用程序的访问级别
  • Framework的访问级别
  • Unit Test Target的访问级别
  • 访问控制语法
  • 自定义类型
  • 元组类型
  • 函数类型
  • 枚举类型
  • 原始值和关联值
  • 嵌套类型
  • 子类
  • 常量,变量,属性,下标
  • getter和setter
  • 初始化
  • 默认初始化方法
  • 结构体的默认成员初始化方法
  • 协议
  • 协议继承
  • 遵循协议
  • 扩展
  • 通过扩展遵循协议
  • 泛型
  • 类型别名

访问控制(access control) 限定其他源文件或模块中的代码你的代码的某些部分的访问.
这个特性可以让你 隐藏代码的实现细节 , 并且可以提供接口(interface)来访问和使用代码

你可以 给某个的类型(类,结构体,枚举) 以及这些 类型的属性,方法,构造器,下标等 设置 明确的 访问级别(access level).
协议可以被 限定在一定的上下文中使用 , 全局常量,变量和函数 也是如此

Swift 不仅提供了不同的访问级别 , 而且 通过为典型的场景提供默认的访问级别的方式 减少了对于指定明确的访问控制级别的需求.
事实上,如果只是开发一个单一目标(single-target)应用程序,你可以不用指定明确的访问级别

注意:
可以将访问控制应用于你的代码的各个部分(properties/types/functions等)
并且在下面的章节中我们会以 实体(entity) 代替它们,为简单起见.

模块(module)和源文件(source file)

Swift中的 访问控制模型(access control model) 是基于 模块 和 源文件 这两个概念

模块单个的代码分布单元
一个框架(framework)或一个应用程序(application) 被构建和发布单个单元(single unit)
并且 **可以被导入另外一个模块 **通过使用Swift关键字 import

在Swift语法中,Xcode的每个 build target , 例如一个应用程序包或框架(app bundle or framework) , 都被视为独立的模块

如果你为了 封装代码在多个应用中重用代码将代码的各个部分打包成一个独立的框架 , 那么当它被导入到某个应用程序或者其他框架时 , 你在框架中定义的内容都将属于这个独立的模块

一个源文件就是一个模块中的单个Swift源代码文件
(实际上就是应用程序或者框架中的一单个文件)

尽管我们一般会将不同的类型分别定义在不同的源文件中
但是单个源文件也可以包含多个类型,函数等的定义

访问级别(Access Level)

Swift 为 **代码中的实体(entity) ** 提供了三种不同的 访问级别
这些 访问级别 不仅 与源文件中定义的实体相关 , 同时 也与源文件所属的模块相关

  • public access(公共访问)
    实体 可以 在 **定义它的模块之内的任意源文件中 **被使用
    实体 也可以 在 **导入定义它的模块的任意源文件中 **被使用
    指定一个框架的公共接口 时 , 你通常使用 public access

  • internal access(内部访问)
    实体 可以 在 **定义它的模块之内的任意源文件中 **被使用
    实体 不可以 在 **定义它的模块之外的任意源文件中 **被使用
    定义一个应用程序或一个框架的内部结构 时,你通常使用 internal access

  • private access(私有访问)
    实体 只能定义它的源文件中 被使用。
    使用 private access 可以 隐藏某些功能的实现细节

public 为最高的(限制最宽松的)访问级别
private 为最低的(限制最严格的)访问级别

注意

Swift中的 private access 和大多数其他语言中的 private access 不同

Swift中的实体的访问级别如果被设置为 private access ,那么实体的作用范围是 实体所在的源文件 ,而不是实体所在的声明

这就意味着,一个类型 可以访问 其所在源文件中的所有private实体 , 但 定义在其他源文件中该类型的扩展 不能访问 该类型的私有成员(private members)

访问级别基本原则

Swift中的访问级别遵循一个总基本原则

一个实体的定义不可以依赖访问级别更低(限制更为严格)的另一个实体

例如:

  • 一个public变量的定义中不能包含internal或private类型
    因为 internal/private级别的类型 可能在任何使用该public变量的地方 都不可用

  • 一个函数的访问级别不能高于其参数类型和返回值类型的访问级别
    因为函数在被调用时 其参数类型或返回值类型可能会 不可用于周围代码

默认访问级别

大多数情况下 , 如果你 不为代码中的实体显式指定访问级别 的话 , 代码中的实体 默认internal访问级别

在某些情况下 , 实体的根据默认环境所拥有的默认访问级别根据比较与其相关的实体的访问级别所得出访问级别 不匹配 时, 需要 显式指定 访问级别

单一目标(Single-Target)应用程序的访问级别

当你 编写一个单一目标应用程序 时,你的 应用中的代码通常是自包含在应用中 并且 不需要给其他应用的模块使用.
默认的internal访问级别 已经满足了这个需求,所以我们不需要设置访问级别
然而你可以 为了对模块中的其他代码隐瞒一些功能的实现细节标记你的一部分代码为private

框架(Frameworks)的访问级别

当你 开发框架 时,把框架的公开接口设置为public 以便它可以 被其他模块(例如一个应用导入框架的应用) 查看和访问.

这个公开的接口 就是 这个框架的 应用程序接口(Application Programming Interface)

注意

你的 框架的任何内部实现细节 仍可以使用 默认访问级别internal , 或者 可被标记private 如果你想要 对框架内部的其他代码部分隐藏 的话.

如果你想要 使一个实体变成你框架的API的一部分 ,你需要将它 标记为public

单元测试目标(Unit Test Targets)的访问级别

当你的 应用程序包含Unit Test Target 时,你的 **应用程序中的代码需要被提供给该模块 ** 以便被测试.
默认 情况下只有标记为 public的实体 才可以 被其他模块 访问。

然而一个 单元测试目标(unit test target) 可以 访问任何internal实体 ,如果你使用 @testable属性 来标记一个 产品模块导入声明 并将其编译为具有测试功能的产品模块

访问控制语法

通过 修饰符public,internal,private定义实体的访问级别
eg.
<pre><code>`
public class SomePublicClass {}
internal class SomeInternalClass {}
private class SomePrivateClass {}

public var somePublicVariable = 0
internal let someInternalConstant = 0
private func somePrivateFunction() {}
`</code></pre>

若无其他规定,默认的访问级别为internal
这意味着SomeInternalClass和someInternalConstant在不使用修饰符显式声明访问级别的情况下仍然拥有internal访问级别:
<pre><code>class SomeInternalClass {} //隐式的访问级别 internal var someInternalConstant = 0 //隐式的访问级别 internal</code></pre>

自定义类型

如果想为一个自定义类型指定访问级别,在定义类型时进行指定即可
新类型只能在它的访问级别允许的范围内被访问
例如,你定义了一个private的类,那这个类就只能在定义它的源文件中可以被用作一个属性,函数参数或者返回值的类型

类型的访问控制的级别 也会 影响到 类型的成员(属性,方法,构造器,下标)的默认访问级别

  • 如果 将类型指定为public, 那么 该类型的所有成员的默认访问级别将是internal
    (但是可以单独设置某个成员为internal或private访问级别)

  • 如果 将类型指定为internal(或者不明确指定访问级别而使用默认的internal) ,那么 该类型的所有成员的默认访问级别将是internal
    (但是可以单独设置某个成员为private访问级别)

  • 如果 将类型指定为private ,那么 **该类型的所有成员的默认访问级别也会变成private **
    (也只能是private)

注意

上面提到,一个public类型的所有成员的访问级别 默认为internal , 而不是public

如果你想 将类型的某个成员指定为public级别 ,那么你 必须显式指定
这样确保了 该类型的公开的API是你选定公开的 ,并 避免了将一个类型的internal内容作为公开的API来呈现 的错误

<pre><code>`
public class SomePublicClass { // 显式的 public 类
public var somePublicProperty = 0 // 显式的 public 类成员
var someInternalProperty = 0 // 隐式的 internal 类成员
private func somePrivateMethod() {} // 显式的 private 类成员
}

class SomeInternalClass { // 隐式的 internal 类
var someInternalProperty = 0 // 隐式的 internal 类成员
private func somePrivateMethod() {} // 显式的 private 类成员
}

private class SomePrivateClass { // 显式的 private 类
var somePrivateProperty = 0 // 隐式的 private 类成员
func somePrivateMethod() {} // 隐式的 private 类成员
}
`</code></pre>

元组类型

元组类型的访问级别元组中使用的多个类型的访问级别最严格的那个访问级别
例如,如果你构建了一个包含两种不同类型的元组,其中一个类型为internal级别,另一个类型为private级别,那么这个元组的访问级别为private

注意

元组不像类,结构体,枚举,函数那样有单独的定义。

元组的访问级别在它被使用时会被自动推断出来,并且不能被显式的指定

函数类型

函数的访问级别函数的参数类型和返回值类型的访问级别最严格的那个访问级别

如果这种 经过比较得出的访问级别函数根据环境所拥有的默认访问级别 不匹配,那么就需要 显式地指定该函数的访问级别 作为函数定义的一部分

下面的例子定义了一个名为someFunction的全局函数,并且没有明确地指定其访问级别.
也许你会认为该函数拥有默认的访问级别internal,但事实并非如此
事实上,如果按下面这种写法,代码将无法通过编译
<pre><code>func someFunction() -> (SomeInternalClass, SomePrivateClass) { // 此处是函数实现部分 }</code></pre>

这个函数的返回类型是一个元组,该元组中包含两个自定义的类(在前面定义过),其中一个类的访问级别是internal,另一个类的访问级别是private
根据元组访问级别的总原则,该元组的访问级别是private
所以你必须使用private修饰符明确指定该函数的访问级别
<pre><code>private func someFunction() -> (SomeInternalClass, SomePrivateClass) { // 此处是函数实现部分 }</code></pre>

将该函数指定为public或internal,或者使用默认的访问级别internal都是错误的,因为如果把该函数设置为public或internal级别可能会无法访问返回值类型-private类。

枚举类型

枚举的成员的访问级别枚举类型的访问级别 相同
不能为枚举成员 指定 不同的访问级别

如下,枚举类型CompassPoint被显式指定为public级别,它的成员North,South,East,West的访问级别同样也是public
<pre><code>public enum CompassPoint { case North case South case East case West }</code></pre>

原始值(rawValue)和关联值(associatedValue)

枚举定义中的任何原始值或关联值的类型的访问级别高于或等于枚举类型的访问级别
例如,你不能使用private级别的类型作为一个internal访问级别的枚举类型的原始值类型

嵌套类型

private类型中定义的嵌套类型自动拥有private访问级别
public或者internal级别类型中定义的嵌套类型自动拥有internal访问级别
如果想让public级别类型中的嵌套类型拥有public访问级别,那么需要明确指定该嵌套类型的访问级别为public

子类

可以继承 任何可以在当前上下文中被访问的类

子类的访问级别不得高于父类的访问级别

例如,你不能编写一个父类的访问级别为internal的public子类

此外,你可以在符合一定的访问级别的条件下重写任意可获得的类成员(方法,属性,构造器,下标等)

重写 可以使 继承来的类成员 相比于父类中的版本 更容易被访问

如下所示,类A的访问级别是public,包含一个访问级别为private方法someMethod().类B继承自类A,访问级别为internal.
但是在类B中重写了类A中访问级别为private的方法someMethod(),并指定为internal级别(比someMethod()的原是实现的访问级别更高)
<pre><code>`
public class A {
private func someMethod() {}
}

internal class B: A {
override internal func someMethod() {}
}
`</code></pre>

只要是在被允许的访问级别上下文中,一个比父类成员访问权限更高的子类成员调用该父类成员是有效的
<pre><code>`
public class A {
private func someMethod() {}
}

internal class B: A {
override internal func someMethod() {
super.someMethod()
}
}
`</code></pre>

因为父类A和子类B定义在同一源文件中,所以在子类B的someMethod()方法中调用super.someMethod()是有效的

常量,变量,属性,下标

常量,变量,属性不能拥有比它们的类型更高的访问级别。
例如,定义一个的public访问级别的属性,但是其类型的访问级别为private是无效的
同样,一个下标也不能拥有比它的索引类型或返回值类型更高的访问级别。

如果一个常量,变量,属性,下标的类型是private类型级别,那么它们也必须被显式指定指定为private访问级别
<pre><code>private var privateInstance = SomePrivateClass()</code></pre>

getter和setter

常量,变量,属性,下标的getter和setter自动接收和 它们所属类型的相同的访问级别

与getter的访问级别相比 你可以 给予setter一个更低的访问级别

这样就 可以限定变量,属性或下标的读写范围

你可以通过 在 varsubscript 之前写 private(set)internal(set) 来设置一个更低的访问级别

注意

此规则适用于 存储型属性 和 计算型属性.

即使你不显式地指定存储型属性的getter和setter,Swift仍然会隐式地生成getter和setter为你提供用对该属性的后备存储的访问.

使用private(set)和internal(set)可以改变这种setter的访问级别,这对计算型属性的显式的setter也是如此

如下所示定义了一个名为TrackedString的结构体,它记录了一个字符串属性被修改的次数
<pre><code>`
struct TrackedString //隐式的internal结构体
{
private(set) var numberOfEdits = 0
//显式的private结构体成员的setter和隐式的internal结构体成员的getter

var value: String = "" //隐式的internal结构体成员
{
    didSet 
    {
        numberOfEdits += 1
    }
}

}
`</code></pre>

TrackedString结构体定义了一个存储String值的属性value,并将初始值设为""(一个空字符串).该结构体还定义了另一个存储Int值的属性numberOfEdits,用于记录属性value被修改的次数.记录修改通过属性value的didSet观察器实现,每当给 value赋新值时numberOfEdits的值就会加一。

结构体TrackedString和属性value均没有显式指定访问级别,所以它们都拥有默认的访问级别internal.但是该结构体的numberOfEdits属性使用了private(set)修饰符,这意味着numberOfEdits属性只能在定义该结构体的源文件中赋值.
numberOfEdits属性的getter仍然有默认的internal访问级别,但是他的setter只能用于结构体TrackedString的定义所在的源文件中.这使结构体TrackedString的numberOfEdits属性只在当前的源文件中是可读写的,但当在同一个模块中的其他源文件使用时,将该属性呈现为只读属性.

如果你实例化TrackedString结构体,并多次对value属性的值进行修改,你就会看到numberOfEdits的值会随着修改次数而变化:
<pre><code>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”</pre></code>

虽然你可以在其他的源文件中实获取到numberOfEdits属性的值,但是你不能对其进行赋值.这一限制保护了该记录功能的实现细节,同时还提供了方便的访问方式.

注意如果有必要,你可以为getter和setter显式指定访问级别.下面的例子将TrackedString结构体明确指定为了public访问级别.因此结构体的成员拥有默认的访问级别internal.你可以结合public和private(set)修饰符把结构体中numberOfEdits属性的getter的访问级别设置为public,而setter的访问级别设置为private:
<pre><code>public struct TrackedString { public private(set) var numberOfEdits = 0 public var value: String = "" { didSet { numberOfEdits += 1 } } public init() {} }</code></pre>

构造器

自定义构造器的访问级别低于或等于它们初始化的类型的访问级别
唯一的例外是 必要构造器的访问级别必须和所属类型的访问级别相同

如同函数或方法的参数,构造器参数的访问级别也不能低于构造器本身的访问级别.

默认构造器

如同在默认构造器所述的,Swift会为结构体和类提供一个默认的无参数的构造器,只要它们为所有存储型属性设置了默认初始值,并且未提供自定义的构造器.

默认构造器的访问级别与所属类型的访问级别相同,除非类型的访问级别是public.
如果一个类型被指定为public级别,那么默认构造器的访问级别将为internal.
如果你希望一个public级别的类型也能在其他模块中使用这种无参数的默认构造器,你必须提供一个public访问级别的无参数构造器.

结构体默认的逐一成员构造器

如果结构体中任意存储型属性的访问级别为private,那么该结构体默认的成员逐一构造器的访问级别就是private,否则,这种构造器的访问级别是internal.

如同前面提到的默认构造器,如果你希望一个public级别的有默认逐一成员构造器结构体也能在其他模块中被使用,你必须自己提供一个public访问级别的逐一成员构造器.

协议

如果想 **为一个协议类型明确地指定访问级别 , 在定义协议时指定 **即可.
这能使你 创建 只能在特定的代码环境中 被遵循的协议.
(private级别的protocol,只能被同一源文件中的类型遵循
internal级别的protocol,只能被同一模块中的类型遵循
public级别的protocol,可以被同一模块中和其他模块中的类型遵循)

你不能对协议中的要求设置访问级别(如果设置了访问级别标识符,Xcode会报错:'xxx'modifier cannot be used in protocols,并提示你删除).
这样能确保该协议的所有要求都可以被任意遵循者都实现

协议继承

如果定义了一个继承自其他协议的新协议,新协议拥有的访问级别最高也只能和被继承协议的访问级别相同.
例如,你不能将继承自internal的协议的新协议定义为public的协议.

遵循协议

当一个类型能够遵循的一个指定访问级别的协议时 , 协议的要求在类型中的实现的访问级别 必须高于或等于 该类型的访问级别 和 该协议的访问级别最低的访问级别 并且 低于或等于 该类型的访问级别 和 该协议的访问级别最高的访问级别

在类型的定义中实现协议的要求时,Xcode会在要求的名称前补全相应的修饰符为 该类型的访问级别 和 该协议的访问级别最低的访问级别

注意
Swift和Objective-C一样,协议的一致性是全局的,在同一程序中一个类型不可能用两种不同的方式遵循同一个协议.

扩展

你可以在类,结构体,枚举的访问级别允许的代码环境中对类,结构体,枚举进行扩展.
扩展中添加的成员具有和原始类型中的成员默认一致的访问级别.
如果你扩展了一个public或者internal类型,扩展中的任何新成员将具有默认的internal访问级别.如果你扩展了一个private类型,扩展中任意新成员则拥有默认的private访问级别.

或者,你可以明确标记扩展的访问级别(例如,private extension)来给该扩展中的所有成员指定一个新的默认访问级别.
这个新的默认访问级别仍然可以在扩展中被个别的类型成员的显式的访问级别覆盖

<pre>
<code>
`
internal extension Int//只为扩展中所有新的成员指定默认访问级别internal
{
var valueInString :String//默认的internal
{
get
{
return String(self)
}
}

private var doubledValue :Int//用显式的private覆盖隐式的internal
{
    get
    {
        return self * 2
    }
}

}
`
</code>
</pre>

通过扩展遵循协议

如果你想通过扩展使某类型遵循协议,你不能在显式的为该扩展指定访问级别.(Xcode报错:'xxx' modifier cannot be used with extensions that declare protocol conformances)
在 协议-遵循协议 中有描述:

当一个类型能够遵循的一个指定访问级别的协议时 , 协议的要求在类型中的实现的访问级别 必须高于或等于 该类型的访问级别 和 该协议的访问级别 中 最低的访问级别 并且 低于或等于 该类型的访问级别 和 该协议的访问级别 中 最高的访问级别

在类型的定义中实现协议的要求时,Xcode会在要求的名称前补全相应的修饰符为 该类型的访问级别 和 该协议的访问级别 中 最低的访问级别

因此 当某类型想要通过扩展来遵循协议时 , 协议的要求在该类型的此扩展中的实现的访问级别 也取决于 协议的访问级别 , 所以 如果你想通过扩展使某类型遵循协议,你不能在显式的为该扩展指定访问级别

泛型

泛型类型或泛型函数的访问级别其本身的访问级别类型参数的类型约束的访问级别最低的访问级别

类型别名

你定义的任何类型别名都会被当作独特的类型,以便于进行访问控制.
类型别名的访问级别低于或等于其表示的类型的访问级别.
例如,private级别的类型别名可以作为public,internal,private类型的别名,但是public级别的类型不能作为internal或private类型的别名。

注意
这条规则也适用于 为满足协议一致性而将类型别名用于关联类型的情况 。


OVER
我自己都看吐了~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容