Swift3.0的Access Control(访问控制)

访问控制能够限制你的代码从其他文件和模块中访问,这个特性能够让你隐藏你具体的实现,并且也可以让代码能够访问和使用。

你可以给class,structures,enumerations这几种类型增加访问等级,也可以给properties,methods,initializers,和属于这些类型的subscripts.可以限制明确的上下文,也可以是全局常量,变量和函数。

Access Levels(访问级别)#####

Swift对代码提供了五种不同的访问等级.这些访问等级既相对于被定义的源文件,也相对于文件所属的模块。

  • Openpublic能够让同一模块中的任何文件中访问,也可以通过导入定义的模块让其他模块或者源文件访问.当一个framework中说明是公用的访问接口时,用open或者public都可以.两者的不同下边做了具体说明.
  • Internal能够让同一模块中的任何文件中访问,但是不能让这个模块以外的文件访问.
  • File-private限制只能让自己定义的文件访问。在整个文件被访问时,使用file-private可以隐藏函数的具体实现。
  • Private能够限制只在声明的实体中访问。

Open是最高的访问级别,private是最低的访问级别。

Open仅可以在类和类成员中使用,它区别于public从以下几点:

  • 使用public的类,可以在它们定义模块中派生子类。
  • 使用public的类成员,可以在它们定义的模块中,被子类继承。
  • Open类可以在定义的模块中派生子类,可以在任何模块中导入定义的模块。
  • Open类可以在定义的模块中被子类继承,并且可以在任何模块中导入定义的模块。

Guiding Principle of Access Levels(访问级别的使用原则)####

在Swift中,访问等级遵循规定原则:访问级别统一性。

例如:

  • 一个public变量不能被internal,file-private,或private定义。因为public被访问的地方,其他被定义的不能被访问。
  • 函数的访问级别不能高于它的参数、返回类型的访问级别。因为如果函数定义为public而参数或者返回类型定义为internalprivate,就会出现函数可以被任何人访问,但是它的参数和返回类型不可以,同样会出现错误.

Default Access Levels(默认的访问级别)####

代码中的所有实体,如果你不明确的定义其访问级别,那么它们默认为internal级别。在大多数情况下,我们不需要明确的设置实体的访问级别,因为我们大多数时候都是在开发一个 App bundle。

Access Levels for Single-Target Apps(单目标应用程序的访问级别)####

当你编写一个单目标应用程序时,该应用的所有功能都是为该应用服务,不需要提供给其他应用或者模块使用,所以我们不需要明确设置访问级别,使用默认的访问级别internal即可。但是如果你愿意,你也可以使用private级别,用于隐藏一些功能的实现细节。

Access Levels for Frameworks(Frameworks的访问级别)####

当你开发Framework时,你需要使用openpublic标记为开放接口,以便其他人导入该Framework后可以正常使用其功能。这些被你定义为开放的实体,就是这个Framework的API。

注意:Framework的内部实现细节依然可以使用默认的internal级别,如果你想隐藏实现细节,或者也可以定义为`private`或`file private`级别。如果你想将它作为 `framework`的API,你可以使用`open`或`public`。

Access Control Syntax(访问控制语法)####

通过修饰符open, public,internal, file private, or private来声明实体的访问级别:

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() {}

除非有特殊的说明,否则实体都使用默认的访问级别internal,可以查阅默认访问级别这一节.这意味着SomeInternalClasssomeInternalConstant可以去掉internal,但是他们仍然拥有隐式的访问级别internal

class SomeInternalClass {}              // implicitly internal
let someInternalConstant = 0            // implicitly internal

Custom Types(自定义类型)####

如果你想为一个自定义类型指定一个明确的访问级别,那么你要明确一点。那就是你要确保新类型的访问级别和它实际的作用域相匹配。比如说,如果某个类里的属性、函数、返回值它们的作用域仅在当前的源文件中,那么你就可以将这个类声明为file-private类。

类的访问级别也可以影响到类成员(属性、函数、初始化方法等)的默认访问级别。如果你将类声明为privatefile private类,那么该类的所有成员的默认访问级别也会成为privatefile private。如果你将类申明为public或者internal类(或者不明确的指定访问级别,而使用默认的internal访问级别),那么该类的所有成员的访问级别是internal

注意:上面提到,一个`public`类的所有成员的访问级别默认为`internal`级别,而不是`public`级别。如果你想将某个成员申明为public级别,那么你必须使用修饰符明确的申明该成员。这样做的好处是,在你定义公共接口API的时候,可以明确的选择哪些属性或方法是需要公开的,哪些是内部使用的,可以避免将内部使用的属性方法公开成公共API的错误。
public class SomePublicClass {                  // explicitly public class
    public var somePublicProperty = 0            // explicitly public class member
    var someInternalProperty = 0                 // implicitly internal class member
    fileprivate func someFilePrivateMethod() {}  // explicitly file-private class member
    private func somePrivateMethod() {}          // explicitly private class member
}
 
class SomeInternalClass {                       // implicitly internal class
    var someInternalProperty = 0                 // implicitly internal class member
    fileprivate func someFilePrivateMethod() {}  // explicitly file-private class member
    private func somePrivateMethod() {}          // explicitly private class member
}
 
fileprivate class SomeFilePrivateClass {        // explicitly file-private class
    func someFilePrivateMethod() {}              // implicitly file-private class member
    private func somePrivateMethod() {}          // explicitly private class member
}
 
private class SomePrivateClass {                // explicitly private class
    func somePrivateMethod() {}                  // implicitly private class member
}

Tuple Types(元组类型)####

元组的访问级别使用是所有类型的访问级别使用中最为严谨的。比如说,如果你构建一个包含两种不同类型元素的元组,其中一个元素类型的访问级别为internal,另一个为private级别,那么这个元组的访问级别为private。也就是说元组的访问级别遵循它里面元组中最低级的访问级别。

注意:元组不同于类、结构体、枚举、函数那样有单独的定义。元组的访问级别是在它被使用时自动推导出的,而不是明确的申明。

Function Types(函数类型)####

函数的访问级别需要根据该函数的参数类型访问级别、返回类型访问级别得出。如果根据参数类型和返回类型得出的函数访问级别不符合上下文,那么就需要明确的申明该函数的访问级别。

下面的例子中定义了一个全局函数名为someFunction,并且没有明确的申明其访问级别。你也许会认为该函数应该拥有默认的访问级别internal,但事实并非如此。事实上,如果按下面这种写法,编译器是无法编译通过的:

func someFunction() -> (SomeInternalClass, SomePrivateClass) {
    // function implementation goes here
}

我们可以看到,这个函数的返回类型是一个元组,该元组中包含两个自定义的类(可查阅自定义类型)。其中一个类的访问级别是internal,另一个的访问级别是private,所以根据元组访问级别的原则,该元组的访问级别是private(元组的访问级别遵循它里面元组中最低级的访问级别)。

因为该函数返回类型的访问级别是private,所以你必须使用private修饰符,明确的申请该函数:

private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
    // function implementation goes here
}

将该函数申明为publicinternal,或者使用默认的访问级别internal都是错误的,因为如果把该函数当做publicinternal级别来使用的话,是无法得到private级别的返回值的。

Enumeration Types(枚举类型)####

枚举中成员的访问级别继承自该枚举,你不能为枚举中的成员指定访问级别。

比如下面的例子,枚举CompassPoint被明确的申明为public级别,那么它的成员NorthSouthEastWest的访问级别同样也是public

public enum CompassPoint {
    case North
    case South
    case East
    case West
}

Raw Values and Associated Values(原始值和关联值)####

用于枚举定义中的任何原始值,或关联的值类型必须有一个访问级别,至少要高于枚举的访问级别。比如说,你不能在一个internal访问级别的枚举中定义private级别的原始值类型。

Nested Types(嵌套类型)####

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

Subclassing(子类)####

子类的访问级别不得高于父类的访问级别。比如说,父类的访问级别是internal,子类的访问级别就不能申明为public

此外,在满足子类不高于父类访问级别以及遵循各访问级别作用域(即模块或源文件)的前提下,你可以重写任意类成员(方法、属性、初始化方法、下标索引等)。

如果我们无法直接访问某个类中的属性或函数等,那么可以继承该类,从而可以更容易的访问到该类的类成员。下面的例子中,类A的访问级别是public,它包含一个函数someMethod,访问级别为file-private。类B继承类A,并且访问级别申明为internal,但是在类B中重写了类A中访问级别为file-private的方法someMethod,并重新申明为internal级别。通过这种方式,我们就可以访问到某类中file-private级别的类成员,并且可以重新申明其访问级别,以便其他人使用:

public class A {
    fileprivate func someMethod() {}
}

internal class B: A {
    override internal func someMethod() {}
}

只要满足子类不高于父类访问级别以及遵循各访问级别作用域的前提下(即file-private的作用域在同一个源文件中,internal的作用域在同一个模块下),我们甚至可以在子类中,用子类成员访问父类成员,哪怕父类成员的访问级别比子类成员的要低:

public class A {
    fileprivate func someMethod() {}
}

internal class B: A {
    override internal func someMethod() {
        super.someMethod()
    }
}

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

Constants, Variables, Properties, and Subscripts(常量、变量、属性、下标)####

常量、变量、属性不能拥有比它们的类型更高的访问级别。比如说,你定义一个public级别的属性,但是它的类型是private级别的,这是编译器不允许的。同样,下标也不能拥有比索引类型或返回类型更高的访问级别。

如果常量、变量、属性、下标索引的定义类型是private级别的,那么它们必须要明确的申明访问级别为private

private var privateInstance = SomePrivateClass()

Getter和Setter####

常量、变量、属性、下标索引的GettersSetters的访问级别继承自它们所属成员的访问级别。

Setter的访问级别可以低于对应的Getter的访问级别,这样就可以控制变量、属性或下标索引的读写权限。在varsubscript定义作用域之前,你可以通过fileprivate(set),private(set)internal(set)先为它门的写权限申明一个较低的访问级别。

注意:这个规定适用于用作存储的属性或用作计算的属性。即使你不明确的申明存储属性的`Getter`、`Setter`,`Swift`也会隐式的为其创建`Getter`和`Setter`,用于对该属性进行读取操作。使用`fileprivate(set)`,`private(set)`和`internal(set)`可以改变Swift隐式创建的`Setter`的访问级别。在计算属性中也是同样的。

下面的例子中定义了一个结构体名为`TrackedString`,它记录了`value`属性被修改的次数:
struct TrackedString {
    private(set) var numberOfEdits = 0
    var value: String = "" {
    didSet {
        numberOfEdits++
    }
    }
}

TrackedString结构体定义了一个用于存储的属性名为value,类型为String,并将初始化值设为""(即一个空字符串)。该结构体同时也定义了另一个用于存储的属性名为numberOfEdits,类型为Int,它用于记录属性value被修改的次数。这个功能的实现通过属性valuedidSet方法实现,每当给value赋新值时就会调用didSet方法,给numberOfEdits加一。

结构体TrackedString和它的属性value均没有明确的申明访问级别,所以它们都拥有默认的访问级别internal。但是该结构体的numberOfEdits属性使用private(set)修饰符进行申明,这意味着numberOfEdits属性只能在定义该结构体的源文件中赋值。numberOfEdits属性的Getter依然是默认的访问级别internal,但是Setter的访问级别是private,这表示该属性只有在当前的源文件中是可读可写的,在当前源文件所属的模块中它只是一个可读的属性。

如果你实例化TrackedString结构体,并且多次对value属性的值进行修改,你就会看到numberOfEdits的值会随着修改次数更改:

var stringToEdit = TrackedString()
stringToEdit.value = "This string will be tracked."
stringToEdit.value += " This edit will increment numberOfEdits."
stringToEdit.value += " So will this one."
println("The number of edits is \(stringToEdit.numberOfEdits)")
// prints "The number of edits is 3"

虽然你可以在其他的源文件中实例化该结构体并且获取到numberOfEdits属性的值,但是你不能对其进行赋值。这样就能很好的告诉使用者,你只管使用,而不需要知道其实现细节。

Initializers(初始化)####

我们可以给自定义的初始化方法指定访问级别,但是必须要低于或等于它所属类的访问级别。但如果该初始化方法是必须要使用的话,那它的访问级别就必须和所属类的访问级别相同。

如同函数或方法参数,初始化方法参数的访问级别也不能低于初始化方法的访问级别。

Default Initializers(默认初始化方法)####

Swift为结构体、类都提供了一个默认的无参初始化方法,用于给它们的所有属性提供赋值操作,但不会给出具体值。默认初始化方法可以参阅Default Initializers。默认初始化方法的访问级别与所属类型的访问级别相同。

注意:如果一个类型被申明为public级别,那么默认的初始化方法的访问级别为internal。如果你想让无参的初始化方法在其他模块中可以被使用,那么你必须提供一个具有public访问级别的无参初始化方法。

Default Memberwise Initializers for Structure Types(结构体的默认成员初始化方法)####

如果结构体中的任一存储属性的访问级别为private,那么它的默认成员初始化方法访问级别就是private。尽管如此,结构体的初始化方法的访问级别依然是internal

如果你想在其他模块中使用该结构体的默认成员初始化方法,那么你需要提供一个访问级别为public的默认成员初始化方法。

Protocols(协议)####

如果你想为一个协议明确的申明访问级别,那么有一点需要注意,就是你要确保该协议只在你申明的访问级别作用域中使用。

协议中的每一个必须要实现的函数都具有和该协议相同的访问级别。这样才能确保该协议的使用者可以实现它所提供的函数。

注意:如果你定义了一个public访问级别的协议,那么实现该协议提供的必要函数也会是public的访问级别。这一点不同于其他类型,比如,public访问级别的其他类型,他们成员的访问级别为internal

Protocol Inheritance(协议继承)####

如果定义了一个新的协议,并且该协议继承了一个已知的协议,那么新协议拥有的访问级别最高也只和被继承协议的访问级别相同。比如说,你不能定义一个public的协议而去继承一个internal的协议。

Protocol Conformance(协议一致性)####

类可以采用比自身访问级别低的协议。比如说,你可以定义一个public级别的类,可以让它在其他模块中使用,同时它也可以采用一个internal级别的协议,并且只能在定义了该协议的模块中使用。

采用了协议的类的访问级别遵循它本身和采用协议中最低的访问级别。也就是说如果一个类是public级别,采用的协议是internal级别,那个采用了这个协议后,该类的访问级别也是internal

如果你采用了协议,那么实现了协议必须的方法后,该方法的访问级别遵循协议的访问级别。比如说,一个public级别的类,采用了internal级别的协议,那么该类实现协议的方法至少也得是internal

注意:在Swift中和Objective-C中一样,协议的一致性保证了一个类不可能在同一个程序中用不同的方法采用同一个协议。

Extensions(扩展)####

你可以在条件允许的情况下对类、结构体、枚举进行扩展。扩展成员应该具有和原始类成员一致的访问级别。比如你扩展了一个公共类型,那么你新加的成员应该具有和原始成员一样的默认的internal访问级别。

或者,你可以明确申明扩展的访问级别(比如使用private extension)给该扩展内所有成员指定一个新的默认访问级别。这个新的默认访问级别仍然可以被单独成员所指定的访问级别所覆盖。

Adding Protocol Conformance with an Extension(协议的扩展)####

如果一个扩展采用了某个协议,那么你就不能对该扩展使用访问级别修饰符来申明了。该扩展中实现协议的方法都会遵循该协议的访问级别。

Generics(泛型)####

泛型类型或泛型函数的访问级别遵循泛型类型、函数本身、泛型类型参数三者中访问级别最低的级别。

Type Aliases(类型别名)####

任何被你定义的类型别名都会被视作为不同的类型,这些类型用于访问控制。一个类型别名的访问级别可以低于或等于这个类型的访问级别。比如说,一个private级别的类型别名可以设定给一个open,public,internal,file-private,private的类型,但是一个public级别的类型别名只能设定给一个public级别的类型,不能设定给internal,file-privateprivate的类类型。

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

推荐阅读更多精彩内容