隐私性(也叫作访问控制)指的是对正常的作用域规则的显式修改。第1章曾介绍过下面这个示例:
class Dog { var name = /"/" private var whatADogSays = /"woof/" func bark { print(self.whatADogSays) }}
这里的意图是限制其他对象访问Dog属性whatADogSays的方式。它是个私有属性,主要供Dog类自身内部使用:Dog可以使用self.whatADogSays,不过其他对象甚至都意识不到它的存在。
Swift提供了3级私有性:
internal
默认规则为声明都是内部的,这意味着它们对所在模块中的所有文件中的所有代码可见。这正是相同模块中的Swift文件能够自动看到彼此顶层内容的原因所在,你无须为此做任何额外的处理工作(这与C和Objective-C不同,在这两种语言中,除非显式通过include或import语句将一个文件展示给别的文件,否则它们之间是看不到彼此的)。
private(比internal范围要小)
声明为private的内容只在包含它的文件中可见。这个规则可能与你想象的不太一样。在某些语言中,private表示对对象声明是私有的。在Swift中,private并不是这个意思;如果一个文件包含了两个类,这两个类都声明为了private,但它们还是可以看到彼此。因此,我们可以将代码划分到多个文件中,遵循一个文件一个类这一通用规则。
public(比internal范围要大)
声明为public的内容可在模块外可见。另一个模块要先导入这个模块才能看到其中的内容。不过,一旦另一个模块导入了这个模块,那么它还是看不到这个模块中没有显式声明为public的内容。如果没有编写过任何模块,那么你可能并不需要将任何东西声明为公开的。如果编写过模块,那就需要将某些东西声明为公开的,否则模块将会毫无作用。
5.3.1 Private声明
通过将对象成员声明为private,你也就间接指定了该对象会有哪些公开API。如下示例来自于我所编写的代码:
class CancelableTimer: NSObject { private var q = dispatch_queue_create(/"timer/",nil) private var timer : dispatch_source_t! private var firsttime = true private var once : Bool private var handler : -> init(once:Bool, handler:->) { // ... } func startWithInterval(interval:Double) { // ... } func cancel { // ... }}
初始化器init(once:handler:)、startWithInterval:与cancel方法没有被标记为private,它们都是这个类的公开API。也就是说,你可以随意调用它们。不过,属性都是私有的;其他代码(即该文件外面的代码)都看不到它们,也无法获取其值和设置其值。它们纯粹都是用于该类方法的内部。它们维护着状态,不过这些状态是外部代码所不知道的。
值得注意的是,隐私性只限于当前文件的级别。比如:
class Cat { private var secretName : String?}class Dog { func nameCat(cat:Cat) { cat.secretName = /"Lazybones/" }}
为何说上述代码是合法的呢?Cat的secretName是私有的,那为什么Dog可以修改它呢?这是因为,私有性并不是单个对象类型级别的;它是文件级别的。我在同一个文件中定义了Cat与Dog,因此它们可以看到彼此的私有成员。
在实际开发中,我们常常会将每个类定义在自己的文件中。事实上,文件名常常是在里面的类名定义好之后才确定下来的;包含了ViewController类声明的文件常常会命名为ViewController.swift。不过,这仅仅是个约定而已,并不强求。在Swift中,文件名并没有什么意义,同一个模块中的Swift文件能够自动看到其他文件中的内容,不必指定其他文件的名字。文件名只不过是为了程序员的方便而已:在Xcode中,我会看到程序文件的列表;因此,当我想要寻找ViewController类声明时,通过文件名会很方便,我可以查找ViewController.swift文件。
将代码划分到不同文件中的真实原因在于确保私有性能够起作用。比如,我有两个文件,Cat.swift与Dog.swift:
// Cat.swift:class Cat { private var secretName : String?}// Dog.swift:class Dog { func nameCat(cat:Cat) { cat.secretName = /"Lazybones/" // compile error }}
上述代码将无法编译通过:编译器会报错“Cat does not have a member named secretName.”。现在,代码被放到了不同的文件中,因此私有性起作用了。
有些时候,你想将设置变量和读取变量时的私有性区分开。为了实现这种差别,请将单词set放在私有性声明后面的圆括号中。这样,private(set)var myVar就表示对该变量的设置局限在了同一个文件的代码中。它并没有对变量的获取作任何限制,因此取默认值。与之类似,你可以写成public private(set)var myVar来使得变量读取变成公开的,同时保持变量的设置为私有的(可以对subscript函数使用相同的语法)。
5.3.2 Public声明
如果编写模块,那就至少需要将一些对象声明为公开的,否则导入你的模块的代码就无法看到它们。未声明为公开的其他声明是内部的,这意味着它们对于模块来说是私有的。因此,请合理使用public声明来配置模块的公开API。
比如,在我编写的Zotz应用(一个纸牌游戏)中,用于发牌和描述牌的对象类型与将纸牌形成一副牌的对象被绑定到了名为ZotzDeck的框架中。其中很多类型(如Card和Deck)都被声明为公开的。不过,很多辅助对象类型则不是;ZotzDeck模块中的类可以看到并使用它们,不过模块外的代码就完全不需要知道它们的存在。
公开的对象类型中的成员本身不会自动变成公开的。如果希望让某个方法是公开的,你需要将其声明为公开的。这是个非常棒的默认行为,因为这意味着除非你想这么做,否则这些成员不会在模块外被共享(正如Apple所做的,你必须“显式发布”对象成员)。
比如,在ZotzDeck模块中,Card类被声明为了公开的,但其初始化器却不是这样。为什么呢?因为没必要。获得牌的方式是通过初始化Deck来实现的(这意味着要导入ZotzDeck模块);Deck的初始化器被声明为了公开的,因此你可以这么做。没有任何理由让牌脱离Deck单独存在,这都归功于隐私性规则,你做不到这一点。
5.3.3 隐私性规则
在语言发布早期,Apple花了不少时间向Swift添加访问控制,很大程度上是因为编译器需要知道非常多的规则才能确保相关内容的隐私级别是一致的。比如:
·如果类型是私有的,那么变量就不能是公开的,因为其他代码无法使用这种变量。
·如果父类不是公开的,那么子类也不能是公开的。
·子类可以改变重写成员的访问级别,但它看不到父类的私有成员,除非它们声明在同一个文件中。
诸如此类。我可以列出所有的规则,但不会这么做,因为没必要。Swift手册对此有详尽的介绍,如果需要可以参考。一般来说,你可能用不上;这本身都是很直观的,如果违背了规则,编译器会通过有用的错误消息提示你。