访问控制

访问控制:限定其他源文件或者模块代码对你的代码的访问级别

  • 明确的给单个类型(类,结构体,枚举)设置访问级别
  • 给类型的属性、方法、构造器、下标等设置访问级别
  • 限定协议在一定范围使用,包括协议中的全局变量、常亮、函数

Swift有显式提供多种访问级别,也有为场景提供了默认的访问级别

模块和源文件

Swift中的访问控制基于模块和源文件

模块指的是独立的代码单元,框架或应用程序会作为一个独立的模块来构建和发布。在 Swift 中,一个模块可以使用import关键字导入另外一个模块。

在 Swift 中,Xcode 的每个目标(例如框架或应用程序)都被当作独立的模块处理。如果你是为了实现某个通用的功能,或者是为了封装一些常用方法而将代码打包成独立的框架,这个框架就是 Swift 中的一个模块。当它被导入到某个应用程序或者其他框架时,框架内容都将属于这个独立的模块。

源文件就是 Swift 中的源代码文件,它通常属于一个模块,即一个应用程序或者框架。尽管我们一般会将不同的类型分别定义在不同的源文件中,但是同一个源文件也可以包含多个类型、函数之类的定义。

访问级别

Swift提供了5种访问级别

  • 开放访问和公开访问(open、public):可以访问同一模块源文件中的任何实体,在模块外也可以导入该模块来访问源文件中的所有实体。我们通常将框架中的某个接口可以被任何使用时,设置为此级别
  • 内部访问(internal):可以访问同一模块源文件的任何实体,但是不能从外部访问该模块源文件中的实体。用于某接口只在应用程序或者内部使用时
  • 文件私有访问限制(filepart):限值实体只能被所定义的文件内部访问。当需要把这些细节被整个文件使用的时候,使用文件私有访问隐藏了一些特定功能的实现细节。
  • 私有访问限值(private): 限值实体只能在所定义的作用域使用,需要把这些细节被整个作用域使用的时候,使用文件私有访问隐藏了一些特定功能的实现细节。

Open为最高访问级别(限制最少) Private为最低访问级别 限制最多

Open和Public区别:(Open只作用于类和类的成员)

  • Public或者其他更严访问级别的类,只能在它们定义的模块内部被继承。
  • Public或者其他更严访问级别的类成员,只能在它们定义的模块内部的子类中重写。
  • Open的类,可以在它们定义的模块中被继承,也可以在引用它们的模块中被继承。
  • Open的类成员,可以在它们定义的模块中子类中重写,也可以在引用它们的模块中的子类重写。

把一个类标记为Open,显式地表明,你认为其他模块中的代码使用此类作为父类,然后你已经设计好了你的类的代码了。

访问级别基本原则

基本原则:实体不可以在某个实体中定义访问级别更低(限制更多)的实体

例如:函数的访问级别不能高于他的参数类型或者返回类型的访问级别。

默认访问级别

如果不为代码中的实体指定访问级别,默认为internal级别。
大多数情况下我们不需要显示指定实体的访问级别

单target应用程序的访问级别

对于单目标应用程序,应用的所有功能都为该应用进行服务而不需要提供给其他应用或者模块进行使用,因此使用默认的internal即可。也可以使用文件私有访问或者私有访问级别来进行一些功能的实现细节的隐藏

框架访问级别

在开发框架时需要将对外的接口定义为Open或者Public级别,即为对外的接口API

注意

框架内部实现仍使用默认的内部访问级别internal,当需要对框架内部其他部分隐藏的细节可以使用private或者fileprivate。对于框架对外api部分,需要设置为openpublic

单元测试目标的访问级别

当我们用单元测试目标去访问程序代码,为了访问那些非公开访问或者开发访问代码,可以在导入的应用程序模块的语句前使用@testable特性,然后在允许测试的编译设置(Build Options -> Enable Testability)下编译这个应用程序模块,单元测试目标就可以访问应用程序模块中所有内部级别的实体。

访问控制语法

//声明实体访问级别
public class SomePublicClass {}
internal class SomeInternalClass {}
fileprivate class SomeFilePrivateClass {}
private class SomePrivateClass {}
public var somePublicVariable = 0
internal let someInternalConstant = 0
fileprivate func someFilePrivateFunction() {}
private func somePrivateFunction() {}
//默认为内部访问级别
class SomeInternalClass {} // 隐式内部访问级别
var someInternalConstant = 0 // 隐式内部访问级别

自定义类型

在自定义类型时指定访问的级别,然后这个类型就在访问级别限制范围内进行使用

定义的类型访问级别会影响到类型成员(属性、方法、构造器、下标)的默认访问级别。如果你将类型指定为私有或者文件私有级别,那么该类型的所有成员的默认访问级别也会变成私有或者文件私有级别。如果你将类型为公开或者内部访问级别(或者不明确指定访问级别,而使用默认的内部访问级别),那么该类型的所有成员的默认访问级别将是内部访问。

注意:

必须显式指定才能将成员设置为公开访问类型,避免不消息暴露内部使用接口

元组类型

元组的访问级别 是由元组中访问级别最严格的类型来约定。

注意

元组不同股类、结构体、枚举、函数那样有单独的定义。其访问级别在被使用时自动推断出,而无法明确指定

函数类型

函数的访问类型由访问级别最严格的参数类型或者返回类型的访问级别决定

如果访问级别不复合函数定义的环境的默认访问级别,那么就需要明确指定该函数的访问级别

//定义一个函数
func someFunction() -> (SomeInternalClass, SomePrivateClass) {
}
/**
    这样是无法通过编译的,这个函数返回了一个元组,元组中包含了两个自定义类,根据元组访问
    级别,元组为privite的,因此函数也为private访问级别,因此并不是默认的internal级
    别,因此需要用private修饰符明确指出函数访问级别
*/

private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
    // 此处是函数实现部分
}

枚举类型

枚举成员的访问级别与枚举类型相同,不能单独指定美剧成员的访问级别

原始值和关联值

枚举定义中的任何原始值或关联值的类型的访问级别至少不能低于枚举类型的访问级别。

例如不能在internal的枚举中定义private的原始值类型

嵌套类型

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

子类

子类的访问级别也不能高于父类

可以通过重写为继承来的类成员(方法、属性、下标、构造器等)提供更高的访问级别

public class A {
    private func someMethod() {}
}
internal class B: A {
    override internal func someMethod() {}
}
//类B继承自A,重写了类A中的方法将访问级别指定为更高的访问级别  
//将someMethod方法 由private级别重写为internal级别

我们甚至可以在子类中,用子成员去访问级别更低的父类成员,只要这一操作在相应访问级别的限制范围内(即,在同一源文件中访问父类fileprivate级别的成员,在同一模块中内访问父类internal级别成员)

public class A {
    private func someMethod() {}
}
internal class B: A {
    override internal func someMethod() {
        super.someMethod()
    }
}
//子类中访问父类中级别更低成员(保证操作在相应访问级别的限值范围内)

常亮、变量、属性、下标

常量、变量、属性不能有比类型(成员的定义类型)更高的访问级别,

下标也不能有比索引类型或返回类型更高的访问级别

Getter和Setter

常量、变量、属性、下标的GettersSetters的访问级别和它们所属类型的访问级别相同

Setter的访问级别可以低于Getter的访问级别,这样就可以控制变量、属性或下标的读写权限。
在var或者subscript关键字之前,可以通过添加fileprivate(set)private(set)internal(set)来为写入指定更低权限

注意:

这个规则同时适用于存储型和计算型属性。
即使你不明确指定存储型的GetterSeeterSwift也会隐式为其创建GetterSetter,用于访问该属性的后备存储。
使用fileprivate(set),private(set)internal(set)可以改变Setter的访问级别,这对计算属性同样适用

struct TrackedString {
    private(set) var numberOfEdits = 0
    var value: String = "" {
        didSet {
            numberOfEdits += 1
        }
    }
}

因为结构体的numberOfEdits属性采用了private(set)修饰符,意味着numberOfEdits属性只能在结构体定义中赋值。此时numberOfEdits属性Getter依然是是默认的访问级别internal,但是Setter的访问级别是private,这表示该属性只能在内部修改,而在结构体外部则表现为一个只读属性。

构造器

自定义构造器的访问级别可以低于或等于其所属的的类型的访问级别

但是必要构造器的访问级别必须和所属的类型的访问级别相同

默认构造器

默认构造器的访问级别和所属类型的访问级别相同,除非类型的访问级别是public
当类型级别为public,那么默认构造器的访问级别将为internal
如果想要在其他模块使用public的无参构造器,你只能自己提供一个

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

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

与默认构造器类似,如果希望一个public级别的结构体也能在其他模块中使用默认的成员逐一构造器,你依然只能自己提供一个public级别的成员逐一构造器

协议

在定义协议时指定协议的访问级别,限制协议只能在适当访问级别范围内被遵循

协议中的每一个要求都具有和该协议相同的访问级别。你不能将协议中的要求设置为其他访问级别,这样才能确保该协议的所有要求对于任意遵循着都将会可用

注意:

如果定义一个public级别的协议,则其协议的所有实现也会是public级别的。不同于其他类型,例如,当类型时public级别,其成员的访问级别却只是internal

协议继承

如果定义一个继承自其他协议的新协议,那么该新协议的访问级别最高只能和父协议相同,而不能高于父协议

协议遵循

  1. 一个类型可以遵循级别比它更低的协议。
    例如,你可以定义一个public级别类型,它可以在其他模块中使用,同时它也可以采纳一个internal级别的协议,但是只能在该协议所在的模块中作为符合该协议的类型使用。

  2. 遵循协议的上下文级别是类型和协议中级别中较小的那个。
    例如:类型是public级别,但它要遵循的协议是internal级别,那么这个类型对该协议的遵循上下文就是internal级别

  3. 当编写或者扩展一个类型让它遵循一个协议时,必须保证该类型对协议中每个要求的实现,至少与遵循协议的上下文级别一致。
    例如:一个public类型遵循一个internal协议,这个类型对协议的所有实现至少都已应该是internal级别的

Extension

  1. 可以在访问级别允许的情况下对类、结构体、枚举进行扩展。扩展成员具有和原始类型成员一致的访问级别
  2. 可以明确指定扩展的访问级别(例如:private extension),从而给扩展中成员一个新的访问级别,但是新的默认访问级别仍会被单独指定的级别覆盖

通过扩展添加协议一致性

通过扩展来采纳协议,那么你就不能显式指定该扩展的访问级别了。协议拥有相应的访问级别,并会为该扩展中所有协议要求的实现提供默认的访问级别。

Extension的私有成员

扩展同一文件内的类、结构体或者枚举,extension里的代码会表现的和声明在元类型里一模一样。

  • 在类型的声明里声明一个私有成员,在同一文件的extension里访问
  • 在extension里声明一个私有成员,在同一文件的另一个extension里访问
  • 在extension里声明一个私有成员,在同一文件的类型声明里访问

泛型

泛型类型或泛型函数的访问级别取决于泛型类型或泛型函数本身的访问级别,还需结合类型参数的类型约束的访问级别,根据这些访问级别中的最低访问级别来确定。

类型别名

定义的任何类型别名都会被当做不同的类型,以便进行访问控制。
类型别名的访问级别不可高于其表示的类型的访问级别。

例如: private 级别的类型别名可以作为 private、file-private、internal、public 或者 open 类型的别名,但是 public 级别的类型别名只能作为 public 类型的别名,不能作为 internal、file-private 或 private 类型的别名。

注意:

这条规则 也适用于为满足协议遵循而将类型别名用于关联类型的情况