Swift知识点25 - 访问控制

可以给单个类型如类、结构体、枚举设置访问级别,也可以给这些类型的属性、方法、下标、构造器等设置访问级别。也包括协议里的全局常量、变量、方法都可以设置访问级别。

如果只是开发一个单一 target 的应用程序,我们完全可以不用显式声明代码的访问级别。

模块和源文件

模块:独立的代码单元。框架或应用程序会作为一个独立的模块来构建和发布。
在Swift中,一个模块可以使用import来导入。
Swift中Xcode的每个target会被当做独立的模块处理。

源文件:Swift中的源代码文件。

访问级别

Open、Public、Internal、File-private、Private

Open和Public:Open 和 Public 级别可以让实体被同一模块源文件中的所有实体访问,在模块外也可以通过导入该模块来访问源文件里的所有实体。通常情况下,你会使用 Open 或 Public 级别来指定框架的外部接口。

即:模块内部和被import的模块,可以访问。

Internal:Internal 级别让实体被同一模块源文件中的任何实体访问,但是不能被模块外的实体访问。通常情况下,如果某个接口只在应用程序或框架内部使用,就可以将其设置为 Internal 级别。

即:模块内部,可以访问。

File-private:File-private 限制实体只能在其定义的文件内部访问。如果功能的部分细节只需要在文件内使用时,可以使用 File-private 来将其隐藏。

即:当前源文件内部,可以访问。

Private:Private 限制实体只能在其定义的作用域,以及同一文件内的 extension 访问。如果功能的部分细节只需要在当前作用域内使用时,可以使用 Private 来将其隐藏。

即:定义的作用域以及extension,可以访问。

Open和Public区别:

Open只能作用于类和类的成员。

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

总结:Public以及更严格级别的,类和类成员,只能在定义的模块内被继承和子类重写。
Open类和类成员,可以在定义的模块中,也可以在import的模块中被继承和子类重写。

基本原则:
不可以在一个实体中访问级别更严格的实体。

// 可以正常访问
internal class People {
    public var name: String?
}

// private 比 internal 严格,所以访问不带legs
internal class Dog {
    private var legs: Int = 0
}

默认访问级别为internal。
单target应用程序,默认也是internal。
框架默认也是internal,只是需要把对外的设置为open或者public即可。
单元测试target访问级别,默认只有open和public的实体才可以访问到。但是在导入应用程序的模块之前,使用@testable特性,单元测试目标就可以访问整个应用程序的实体了。

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

一个 public 类型的所有成员的访问级别默认为 internal 级别,而不是 public 级别。如果你想将某个成员指定为 public 级别,那么你必须显式指定。这样做的好处是,在你定义公共接口的时候,可以明确地选择哪些接口是需要公开的,哪些是内部使用的,避免不小心将内部使用的接口公开。

元组

元组的访问级别将由元组中访问级别最严格的类型来决定。
元组不同于类、结构体、枚举、函数那样有单独的定义。元组的访问级别是在它被使用时自动推断出的,而无法明确指定。

函数

函数的访问级别根据访问级别最严格的参数类型或返回类型的访问级别来决定。但是,如果这种访问级别不符合函数定义所在环境的默认访问级别,那么就需要明确地指定该函数的访问级别。

枚举

枚举成员的访问级别和该枚举类型相同,你不能为枚举成员单独指定不同的访问级别。

嵌套类型

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

子类

子类可以严格于父类。

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

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

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

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

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

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

常量、变量、属性、下标

可以往访问级别低(严格)了去,不能往访问级别高了去。

Getter 和 Setter

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

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

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

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

numberOfEdits只有在结构体内部才可以改变,对外只读。

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

numberOfEdits的getter设置为public级别,而setter被设置为private级别。

构造器

自定义构造器的访问级别可以低于或等于其所属类型的访问级别。唯一的例外是必要构造器它的访问级别必须和所属类型的访问级别相同。

默认构造器

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

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

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

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

协议

如果想为一个协议类型明确地指定访问级别,在定义协议时指定即可。这将限制该协议只能在适当的访问级别范围内被采纳。

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

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

协议继承

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

协议的一致性

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

采纳了协议的类型的访问级别取它本身和所采纳协议两者间最低的访问级别。也就是说如果一个类型是 public 级别,采纳的协议是 internal 级别,那么采纳了这个协议后,该类型作为符合协议的类型时,其访问级别也是 internal。

如果你采纳了协议,那么实现了协议的所有要求后,你必须确保这些实现的访问级别不能低于协议的访问级别。例如,一个 public 级别的类型,采纳了 internal 级别的协议,那么协议的实现至少也得是 internal 级别。

Extension

Extension 可以在访问级别允许的情况下对类、结构体、枚举进行扩展。Extension 的成员具有和原始类型成员一致的访问级别。例如,你使用 extension 扩展了一个 public 或者 internal 类型,extension 中的成员就默认使用 internal 访问级别,和原始类型中的成员一致。如果你使用 extension 扩展了一个 private 类型,则 extension 的成员默认使用 private 访问级别。

或者,你可以明确指定 extension 的访问级别(例如,private extension),从而给该 extension 中的所有成员指定一个新的默认访问级别。这个新的默认访问级别仍然可以被单独指定的访问级别所覆盖。

如果你使用 extension 来遵循协议的话,就不能显式地声明 extension 的访问级别。extension 每个 protocol 要求的实现都默认使用 protocol 的访问级别。

Extension的私有成员

扩展同一文件内的类,结构体或者枚举,extension 里的代码会表现得跟声明在原类型里的一模一样。也就是说你可以这样:

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

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

类型别名

你定义的任何类型别名都会被当作不同的类型,以便于进行访问控制。类型别名的访问级别不可高于其表示的类型的访问级别。例如,private 级别的类型别名可以作为 private、file-private、internal、public 或者 open 类型的别名,但是 public 级别的类型别名只能作为 public 类型的别名,不能作为 internal、file-private 或 private 类型的别名。

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

推荐阅读更多精彩内容