扩展
扩展就是向一个已有的类、结构体、枚举类型或者协议类型添加新功能。这包括在没有权限获取原始源代码的情况下扩展类型的能力。扩展和Objective-c中的分类(category)类似。(不过与Objective-c不同的是,Swift的扩展没有名字)
note:扩展可以对一个类型添加新的功能,但是不能重写已有的功能。
扩展语法
声明一个扩展使用关键字 extension :
extension someType {
//加到SomeType的新功能
}
一个扩展可以扩展一个已有类型,使其能够适配一个或多个协议。当这种情况发生时,协议的名字应该玩去按照类或结构体的名字的方式进行书写:
extension SomeType : SomeProtocol , AnotherProtocol {
//协议实现写在这里
}
note:如果我们定义了一个扩展向一个已有类型添加新功能,那么这个新功能对该类型的所有已有实例中都是有用的,即使它们在你的这个扩展前面定义的。
计算型属性
扩展可以向已有类型添加计算实例属性和计算型类型属性。
extension Double {
var km:Double {
return self * 1000.0
}
var m:Double {
return self
}
var cm:Double {
return self / 100.0
}
var mm:Double {
return self / 1000.0
}
}
var height = 170.0.cm
print("the height is \(height)")
height = 1700.0.mm
print("the height is \(height)")
note:扩展可以添加新的计算属性,但是不可以添加存储属性,也不可以向已有属性添加属性观测器。
构造器
扩展可以向已有类型添加新的构造器。这可以让我们扩展其它类型,将我们自己定制类型作为构造器参数,或者提供该类型的原始实现中没有包含的额外初始化选项。
扩展能向类中添加新的便利构造器,但是它们不能向类中添加新的指定构造器或析构器。指定构造器和析构器必须总是由原始类实现来提供。
注意:如果我们使用扩展向一个值类型添加一个构造器,在该值类型已经向所有的存储属性提供默认值,而且没有定义任何定制构造器时,我们可以在值类型的扩展构造器中调用默认构造器和逐一成员构造器。
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
}
extension Rect{
init(center:Point,size:Size){
let originx = center.x - (size.width / 2.0)
let originy = center.y - (size.width / 2.0)
self.init(origin:Point(x: originx, y: originy),size: size)
}
}
note:如果我们使用扩展提供了一个新的构造器,我们依旧由责任保证构造过程能够让所有实例完全初始化。
方法
扩展可以向已有类型添加新的实例方法和类方法。
extension Int {
func loop(task:()->Void){
for _ in 0..<self {
task()
}
}
}
3.loop({
print("xxxxx")
})
2.loop({
() -> Void in
print("hello world")
})
下标
扩展可以向一个已有类型添加新下标。
extension Int {
subscript(let index: Int) -> Int {
let str = "\(self)"
var arr = [String]()
for item in str.characters {
arr.append(String(item))
}
print("hello:\(arr)")
if index > arr.count {
return 0
}else{
let tmp = arr[index]
return Int(tmp)!
}
}
}
1234567[0]
1234567[1]
1234567[2]
嵌套类型
扩展可以向已有的类、结构体和枚举添加新的嵌套类型:
extension Int {
enum Kind {
case Negative, Zero, Positive
}
var kind: Kind {
switch self {
case 0:
return .Zero
case let x where x > 0:
return .Positive
default:
return .Negative
}
}
}
20.kind
(-2).kind
0.kind
这个例子向Int添加了新的嵌套枚举,这个名为Kind的枚举表示特定整数的类型。具体来说,就是表示整数时正数,零或者负数。
这个例子还向Int添加了一个新的计算实例属性,即kind,用来返回合适的Kind枚举成员。
协议
协议定义了一个蓝图,规定了用来实现某一特定工作或者功能所必需的方法和属性。类,结构体或枚举类型都可以遵循协议,并提供具体实现来完成协议定义的方法和功能。任意能够满足协议要求的类型被称为 遵循 这个协议。
除了遵循协议的类型必须实现那些指定的规定以外,还可以对协议进行扩展,实现一些特殊的规定或者一些附加的功能,使得遵循的类型能过收益。
协议的语法
协议的定义方式与类,结构体,枚举的定义非常相似。
protocol SomeProtocol {
}
要使类遵循某个协议,需要在类型名称后加上协议名称,中间以冒号 : 分隔,作为类型定义的一部分。作为类型定义的一部分。遵循多个协议时,各协议之间用逗号 , 分隔。
struct SomeStructure : FirstProtocol , AnotherProtocol {
//结构体内容
}
如果类在遵循协议的同时拥有父类,应该将父类名放在协议名之前,以逗号分隔。
class SomeClass : SomeSuperClass , FirstProtocol ,SecondProtocol {
//类定义
}
对属性的规定
协议可以规定其 遵循者 提供特定名称和类型的 实例属性 或 类属性,而不用指定是 存储属性还是计算型属性。此外还必须指明是只读的还是可读可写的。
如果协议规定属性是可读可写的,那么这个属性不能是常量或只读的计算属性。如果协议只要求属性是只读的,那个属性不仅可以是只读的,如果代码需要的话,也可以是可写的。
协议中的通常用var来声明变量属性,在类型声明后加上{ set get}来表示属性是可读可写的,只读属性则用{get}来表示。
protocol SomeProtocol {
var mustBeSettable : Int {get set}
var doesNotNeedToBeSetted: Int {get}
}
在协议中定义类属性时,总是使用static关键字作为前缀。当协议的遵循者是类时,可以使用class或static关键字来声明类属性:
protocol AnotherProtocol {
static var someTypeProperty : Int {get set}
}
eg:
protocol FullyNamed {
var fullName : String { get }
}
struct Person : FullyNamed {
var fullName: String
}
let joh = Person(fullName: "Jhon")
class Starship : FullyNamed {
var prefix : String?
var name : String
init(name:String , prefix : String? = nil){
self.name = name
self.prefix = prefix
}
var fullName:String {
return (prefix != nil ? prefix! + " " : "") + name
}
}
var xxx = Starship(name: "jhon", prefix: "conna")
print(xxx.fullName)
对方法的规定
协议可以摇起其遵循者实现某些指定的实例方法或类方法。这些方法作为协议的一部分,像普通的方法一样放在协议的定义中,但是不需要大括号和方法体。可以在协议中定义具有可变参数的方法,和普通方法的定义式相同。但是在协议的方法定义中,不支持参数默认值。
正如对属性的规定中所说的,在协议中定义类方法的时候,总是使用static关键字作为前缀。当协议的遵循者是类的时候,我们可以在类的实现中使用class或者static来实现类方法。
protocol SomeProtocol {
static func someTypeMethod()
}
eg:
protocol RandomNumberProtocol {
func random() -> Int
}
class Test : RandomNumberProtocol {
var x : Int = 0
func random() -> Int {
return Int(arc4random()) % 10 + 1
}
func testPrint() -> Void {
x = random()
print("x number is \(x)")
}
}
let x = Test()
x.testPrint()
对Mutating方法的规定
有时需要在方法中改变它的实例。例如,值类型(结构体,枚举)的实例方法中,将mutating关键字作为函数的前缀,写在func之前,表示可以在该方法中修改它所属的实例及其实例属性的值。
如果我们在协议中定义了一个方法旨在改变遵循该协议的实例,那么在协议定义时需要在方法前加mutating关键字。这使得结构和枚举遵循协议并满足此方法要求。
note:用类实现协议中的mutating方法时,不用写mutating关键字;用结构体,枚举实现协议中的mutating方法时,必须写mutating关键字。
protocol Toggleable {
mutating func toggle()
}
enum OnOffSwitch : Toggleable {
case Off, On
mutating func toggle() {
switch self {
case .Off:
self = .On
case .On:
self = .Off
}
}
}
var lightSwitch = OnOffSwitch.Off
lightSwitch.toggle()
对构造器的规定
协议可以要求它的遵循者实现指定的构造器。我们可以像书写普通的构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体:
protocol SomeProtocol {
init(SomeParameter: Int)
}
协议构造器规定在类中的实现,我们可以在遵循该协议的类中实现构造器,并指定其为类的指定构造器或者便利构造器。这两种情况下,我们都必须给构造器实现标上 required 修饰符:
class SomeClass: SomeProtocol {
required init(someParameter: int){
//构造器实现
}
}
使用required修饰符可以保证: 所有的遵循该协议的子类,同样能为构造器规定提供一个显式的实现或继承实现。
note:如果类已经被标记为final,那么不需要在协议构造器的实现中使用required修饰符。因为final类不能有子类。
如果一个子类重写了父类的指定构造器,并且该构造器遵循了某个协议的规定,那么该构造器的实现需要被同时标示required和override修饰符:
protocol SomeProtocol {
init()
}
class SomeSuperClass {
init(){
//构造器实现
}
}
class SomeClass: SomeSuperClass , SomeProtocol{
required override init(){
//构造器实现
}
}
可失败构造器的规定
可以通过给协议protocols中添加可失败构造器来使遵循该协议的类型必须实现该可失败构造器。
如果在协议中定义一个可失败构造器,则在遵循该协议的类型中必须添加同名同参数的可失败构造器或非可失败构造器。如果在协议中定义一个非可失败构造器,则在遵循该协议的类型中必须添加同名同参数的非可失败构造器或隐式解析类型的可失败构造器(init!)。
协议类型
尽管协议本身并不实现任何功能,但是协议可以被当作类型来使用。
协议可以像其他普通类型一样使用,使用场景:
- 作为函数、方法或构造器中的参数类型或返回值类型
- 作为常量、变量或属性的类型
- 作为数组、字典或其他容器中的元素类型
note:协议是一种类型,因此协议类型的名称应与其他类型(Int,Double,String)的写法相同,使用大写字母开头的驼峰式写法。
protocol NameProtocol {
var name:String {get set}
}
class Person : NameProtocol {
var name: String
init(name:String){
self.name = name
}
}
func myPrint(nameObj:NameProtocol){
print("the name is \(nameObj.name)")
}
myPrint(Person(name: "hello"))
委托(代理)模式
委托是一种设计模式,它允许 类 或 结构体 将一些需要他们负责的功能 交由委托 给其他的类型的实例。委托模式实现很简单: 定义协议来封装那些需要被委托的函数和方法,使其 遵循者 拥有这些被委托的 函数和方法。 委托模式 可以用来响应特定的动作或接收外部数据源提供的数据,而无需知道外部数据源的类型信息。
在扩展中添加协议成员
即便无法修改源代码,依然可以通过扩展来扩充已存在的类型(类,结构体,枚举等)。扩展可以为已存在的类型添加属性,方法,下标脚本,协议成员等。
note:通过扩展为已存在的类型遵循协议时,该类型的所有实例也会随之添加协议中的方法
eg:
protocol TextRepresentable {
var textDescription: String{get}
}
extension Test: TextRepresentable {
var textDescription: String {
return "hello world"
}
}
通过扩展补充协议声明
当一个类型已经实现了协议中的所有要求,却没有声明为遵循协议时,可以通过扩展(空的扩展体)来补充协议声明:
struct Test2 {
var name:String
var textDescription: String {
return "hi"
}
}
extension Test2: TextRepresentable {}
note:即使满足了协议的所有要求,类型也不会自动转变,因此我们必须为它做成显式的协议声明。
###协议类型集合
协议类型可以在数组或者字典这样的集合中使用。
let things :[TextRepresentable] = [game,d12,simonTheHamster]
for thing in things {
print(thing.textDescription)
}
###协议的继承
协议能够继承一个或多个其他协议,可以在继承的协议基础上添加新的内容要求。协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔
protocol InheritingProtocol : SomeProtocol , AnotherProtocol {
//协议定义
}
如下,eg:
protocol NameProtocol {
var name:String {get set}
}
protocol InfoProtocol : NameProtocol {
var sex:String {get set}
var address:String {set get}
}
###类专属协议
我们可以在协议的继承列表中,通过添加class关键字,限制协议只能适配到类(class)类型。(结构体或枚举不能遵循该协议)。该class关键字必须时第一个出现在协议的继承列表中,其后才是其他✈️协议。
```
protocol SomeClassOnlyProtocol : class , SomeProtocolo {
//协议定义
}
note:当协议想要定义的行为,要求(或假设)它的遵循类型必须是引用类型而非值类型时,应该采用类专属协议。
协议合成
有时候需要同时遵循多个协议,我们可以将多个协议采用protocol<SomeProtocol,AnotherProtocol> 这样的格式进行组合,称为 协议合成 。我们可以在<>中罗列任意多个我们想要遵循的协议,以逗号分隔。
protocol Named {
var name: String {get}
}
protocol Aged {
var age: Int {get}
}
struct Person: Named, Aged {
var name:String
var age: Int
}
func wishHappyBirthday(person: protocol<Named,Aged>){
print("Happy birthday \(person.name) - you are \(person.age)")
}
let person = Person(name: "tom", age: 12)
wishHappyBirthday(person)
note:协议合成 并不会生成一个新协议类型,而是将多个协议合成为一个临时的协议,超出范围后立即失效。
检验协议的一致性
我们可以使用 is 和 as 操作符来检查是否遵循某一协议或强制转化为某一类型。检查和转化的语法和之前相同。
- is 操作符用来检查实例是否遵循了某个协议。
- as?返回一个可选值,当实例 遵循 协议时,返回该协议类;否则返回nil。
- as 用以强制向下转型,如果强转失败,会引起运行时错误。
protocol Named {
var name: String {get}
}
struct Dog: Named {
var name:String
var age:Int
}
struct Person: Named {
var name:String
var age: Int
var address:String
}
let dog1 = Dog(name: "dog1", age: 1)
let dog2 = Dog(name: "dog2", age: 2)
let person1 = Person(name: "tom", age: 20, address: "nanjing")
let person2 = Person(name: "Jhon", age: 20, address: "shanghai")
let person3 = Person(name: "sam", age: 13, address: "changcun")
let objs : [Named] = [dog1,person1,dog2,person3,person2]
for item in objs {
if let value = item as? Dog {
print("\(value.name) is dog")
}else if let value = item as? Person {
print("\(value.name) is preson")
}
}
对可选协议的规定
协议可以含有可选成员,其 遵循者 可以选择是否实现这些成员。在协议中使用 optional 关键字作为前缀来定义可选成员。当需要使用可选规定的方法或者属性时,他的类型自动会变成可选的。
可选协议在调用时使用 可选链 ,因为协议的遵循者可能没有实现可选内容。
note:可选协议只能在含有 @objc 前缀的协议中生效。这个前缀表示协议将暴露给Object-c代码,即使我们不打算和Objective-C有什么交互,如果我们想要指明协议包含可选属性,哪么还是要加上@objc前缀。还需要注意的是,@objc的协议只能由继承自Objective-c类的类或者其他的@objc类来遵循。他也不能被结构体和枚举遵循。
@objc protocol CounterDataSource {
optional func incrementForCount(count: Int) -> Int
optional var fixedIncrement: Int {get}
}
class Counter {
var count = 0
var dataSource: CounterDataSource?
func increment() {
if let amount = dataSource?.incrementForCount?(count){
count += amount
}else if let amount = dataSource?.fixedIncrement{
count += amount
}
}
}
class ThreeSource : CounterDataSource {
@objc let fixedIncrement: Int = 3
}
var counter = Counter()
counter.dataSource = ThreeSource()
for _ in 1...4 {
counter.increment()
print(counter.count)
}
协议扩展
使用扩展协议的方式可以为遵循者提供方法或属性的实现。通过这种方式,可以让我们无需在每个遵循者都实现一次,无需使用全局函数,我们可以通过扩展协议的方式进行定义。
例如,可以扩展 RandomNumberGenerator协议,让其提供randomBool()方法。该方法使用random()方法返回一个随机的Bool值:
extension RandomNumberGEnerator {
func randomBool() -> Bool {
return random() > 0.5
}
}
通过扩展协议,所有协议的遵循者,在不用任何修改的情况下,都自动得到了这个扩展所增加的方法。
提供默认实现
可以通过协议扩展的方式为协议规定的属性和方法提供默认的实现。如果协议的遵循者对规定的属性和方法提供了自己的实现,那么遵循者提供的实现将被使用。
note:通过扩展协议提供的协议实现和可选协议规定有区别。虽然协议遵循者无需自己实现,通过扩展提供的默认实现,可以不是用可选链调用。
例如,
@objc protocol CounterDataSource {
optional func incrementForCount(count: Int) -> Int
optional var fixedIncrement: Int {get}
}
class Counter {
var count = 0
var dataSource: CounterDataSource?
func increment() {
if let amount = dataSource?.incrementForCount?(count){
count += amount
}else if let amount = dataSource?.fixedIncrement{
count += amount
}else if let amount = dataSource?.incrementForCount2(count){
count += amount
}
}
}
class ThreeSource : CounterDataSource {
}
extension CounterDataSource{
func incrementForCount2(count: Int) -> Int{
return count + 5
}
}
var counter = Counter()
counter.dataSource = ThreeSource()
for _ in 1...4 {
counter.increment()
print(counter.count)
}
为协议扩展添加限制条件
在扩展协议的时候,可以指定一些限制,只有满足这些限制的协议遵循者,才能获得协议扩展提供的属性和方法。这些限制卸载协议名之后,使用where关键字来描述限制情况。
extension CollectionType where Generator.Element : CounterDataSource {
func sayHello(){
print("hello")
}
}
let arr = [ThreeSource(),ThreeSource(),ThreeSource()]
arr.sayHello()
note:如果有多个协议扩展,而一个协议的遵循者又同时满足它们的限制,那么将会使用所满足限制最多的那个扩展。
泛型
泛型函数
泛型函数 可以工作于任何类型。
func swapTwoValues<T>(inout a: T,inout _ b:T){
}
类型参数
在上面的swapTwoValues例子中,占位类型 T 是一种类型参数的式例。类型参数指定并命名为一个占位类型,并且紧随在函数名后面,适用一对尖括号括起来,(如<T>)。
一旦一个类型参数被指定,那么其可以被使用来定义一个函数的参数类型,或作为一个函数返回类型,或用作函数体中的注释类型。在这种情况下,被类型参数所代表的占位类型不管函数任何时候被调用,都会被实际类型所替换。
我们可以支持多个类型参数,命名在尖括号中,用逗号分开。
命名类型参数
在简单的情况下,泛型函数或泛型类型需要指定一个占位类型,通常用一单个字母T来命名类型参数。不过,我们可以使用任何有效的标识符来作为类型参数名。
如果你使用多个参数定规更复杂的泛型函数或泛型类型,那么使用更多的描述类型参数是非常有用的,例如,Swift中字典(Dictionary)类型有两个类型参数,一个是键,另外一个是值。如果我们自己刺蛾字典,我们或许会定义这两个类型参数为 key 和 Value ,用来记住它们在我们的泛型代码中的作用。
note: 请始终使用大写字母开头的驼峰命名法来给类型参数命名,以表明它们是类型占位符,而非类型值。
泛型类型
通常在泛型类型中,Swift运行你定义你自己的泛型类型。这些自定义类、结构体和枚举作用于任何类型。
struct Stack<T> {
var items = [T]()
mutating func push(item: T) {
items.append(item)
}
mutating func pop() -> T {
return items.removeLast()
}
}
扩展一个泛型类型
当我们扩展一个泛型类型的时候,我们并不需要在扩展定义中提供类型参数列表。更加方便的是,原始类型定义中的类型参数列表在扩展中是可以使用的,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。
extension Stack {
var topItem: T? {
return items.isEmpty ? nil : items[items.count - 1]
}
}
类型约束
类型约束指定了一个必须继承自指定类的类型参数,或者遵循一个特定的协议或协议构成。
1.类型约束语法
我们可以写一个在一个类型参数名后面的类型约束,通过冒号分割,来作为类型参数链的一部分。这种作用于泛型函数的类型约束的基本语法如下:
func someFunction<T:SomeClass,U:SomeProtocol>(someT:T,someU:U){
}
上面这个假定函数有两个类型参数。第一个类型参数T,有一个需要T必须是SomeClass子类的类型约束;第二个类型参数U,有一个需要U必须遵循SomeProtocol协议的类型约束。
2.类型约束实例
func findIndex<T: Equatable>(array: [T], _ valueToFind: T) -> Int? {
for (index, value) in array.enumerate() {
if value == valueToFind {
return index
}
}
return nil
}
Swift标准库中定义了一个Equatable协议,该协议要求任何遵循的类型实现等式符(==)和不等符(!=)对任何两个该类型进行比较。所有的Swift标准类型自动支持Equatable协议。
关联类型
当定义一个协议时,有时候声明一个或多个关联类型作为协议定义的一部分是非常有用的。一个关联类型作为协议的一部分,给定义类型的一个占位名(或别名)。作用于关联类型上实际类型在协议被实现前是不需要指定的。关联类型被指定为 typealias 关键字。
1.关联类型实例
protocol Container {
typealias ItemType
mutating func append(item: ItemType)
var count:Int {get}
subscript(i: Int) -> ItemType {get}
}
struct Stack<T>: Container {
// original Stack<T> implementation
var items = [T]()
mutating func push(item: T) {
items.append(item)
}
mutating func pop() -> T {
return items.removeLast()
}
// conformance to the Container protocol
mutating func append(item: T) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> T {
return items[i]
}
}
2.扩展一个存在的类型为指定关联类型
在Swift中Array已经提供append(_:)方法,一个count属性和通过下标来查找一个自己的元素。这三个功能都达到Container协议的要求。也就意味着你可以扩展Array去遵循Container协议,只要通过简单声明Array适用于该协议而已。
extension Array: Countainer {}
3.where语句
类型约束 能够确保类型符合泛型函数或类的定义约束。
对关联类型定义约束是非常有用的。我们可以在参数列表中通过where语句定义参数的约束。一个where语句能够使一个关联类型遵循一个特定的协议,以及(或)那个特定的类型参数和关联类型可以是相同的。我们可以写一个where语句,紧跟在类型参数列表后面,where语句后跟一个或多个针对关联的约束,以及(或)一个或多个类型和关联类型间的等价关系。
func allItemsMatch<
C1: Container, C2: Container
where C1.ItemType == C2.ItemType, C1.ItemType: Equatable>
(someContainer: C1, anotherContainer: C2) -> Bool {
return false
}
访问控制
访问控制可以限定其他源文件或模块中代码对你代码的访问级别。这个特性可以让我们隐藏功能实现一些细节,并且可以明确的申明我们提供给其他人的接口中哪些部分是他们可以访问和使用的。
我们可以明确地给单个类型(类、结构体、枚举)设置访问级别,也可以给这些类的属性、函数、初始化方法、基本类型、下标索引等设置访问级别。协议也可以被限定在一定范围内使用,包括协议里的全局常量、变量和函数。
在提供了不同访问级别的同时,Swift还为某些经典场景提供了默认的访问级别,这样就不需要我们在每段代码中都申明显示访问级别。其实,如果只是开发一个单目标应用程序,我们完全不用申明代码的显示访问级别。
note:简单起见,代码中可以设置访问级别的特性(属性、基本类型、函数等),在下面的章节中我们会以“实体”代替。
模块和源文件
Swift中的访问控制模型基于模块和源文件这两个概念。
模块指的是以单独单元构建和发布的Framework或application。在Swift中的一个模块可以使用import关键字引入另外一个模块。
在Swift中,Xcode的每个构建目标(比如 FrameWork或app bundle)都被当作模块处理。如果我们是为了实现某个通用的功能,或者是为了封装一些常用方法二将代码打包成独立的Framework,这个Framework在Swift中就被称为模块。当它被引入到某个app工程或者另外一个framwwork时,它里面的一切(属性、函数)等仍然属于这个独立的模块。
源文件指的是Swift中的Swift File,就是编写Swift源代码的文件,它通常属于一个模块,尽管一般我们将不同的 类 分别定义在不同的源文件中,但是同一个源文件也可以包含多个类和函数的定义。
访问级别
swift为代码中的实体提供了三种不同的访问级别。这些访问级别不仅与源文件中定义的实体相关,同时也与源文件所属的模块相关。
- public : 可以访问自己模块中源文件里的任何实体,别人也可以通过引入该模块来访问源文件中所有实体。通常情况下,framework 中的某个接口是可以被任何人使用时,我们也可以将其设置为public级别。
- internal : 可以访问自己模块中源文件里的任何实体,但是别人不能访问该模块中源文件里的实体。通常情况下,某个接口或framwork作为内部结构使用时,我们可以将其设置为internal级别。
- private :只能在当前源代码文件中使用的实体,称为私有实体。使用private级别,可以用作隐藏某些功能的实现细节。
public为最高访问级别,private为最低访问级别。
访问级别的使用原则
swift中的访问级别遵循一个使用原则:访问级别统一性。比如:
- 一个public访问级别的变量,不能将它的类型定义为internal 和private。因为变量可以被任何人访问,但是定义它的类型不可以,所以这样就会出现错误。
- 函数的访问级别不能高于它的参数、返回类型的访问级别。因为如果函数定义为了public而参数或返回类型定位为internal或private,就会出现函数可以被别人访问,但是它的参数和返回类型确不可以。同样会出现错误。
默认访问级别
如果你不为代码中的所有实体定义显式访问级别,那么它们默认为internal级别。在大多数情况下,我们不需要设置实体的显示访问级别。因为我们一般都是在开发一个app bundle。
单目标应用程序的访问级别
当你编写一个单目标应用程序时,该应用的所有功能都是为该应用服务,不需要提供给其他应用或者模块使用,所以我们不需要明确设置访问级别,使用默认的访问级别 internal即可。但是如果你愿意,我们也可以使用private级别,用于隐藏一些功能的实现细节。
Framework的访问级别
当你开发Framework时,就需要把一些对外的接口定义为public级别,以便其他人导入该framework后可以正常的使用其功能。这些你定义为public的接口,就是这个Framework的API。
note:Framwork 的内部实现细节依然可以使用默认的internal级别,或者也可以定义为private级别。只有当你想把它作为API的一部分的时候,才将其定义为public级别。
单元测试目标的访问级别
当你的app有单元测试目标时,为乐方便测试,测试模块需要能访问到你app中的代码。默认情况下只有public级别的实体才可以被其他模块访问。然而,如果在引入一个生产模块时使用@testable 注解,然后用带测试的方式编译这个生产模块,单元测试目标就是可以访问所有internal级别的实体。
访问控制语法
通过修饰符 public、internal、private来声明实体的访问级别:
public class SomePublicClass {}
internal class SomeInternalClass{}
private class SomePrivateClass{}
public var somePublicVariable = 0
internal let someInternalConstant = 0
private func somePrivateFunction(){}
除非有特殊的说明,否则实体都使用默认的访问级别 inernal 。这意味着不再使用修饰符声明显示访问级别的情况下,SomeInternalClass和someInternalConstant仍然拥有隐式的访问级别 internal。
class SomeInternalClass {} //隐式访问级别 internal
var someInternalVariable = 0 //隐式访问级别 internal
自定义类型
如果想为一个自定义类型中申明显示访问级别,那么要明确一点。那就是你要确保新类型的访问级别和它实际的作用域相匹配。比如说,如果你定义了一个private类,那么这个类就只能在定义它的源文件中当作属性类型、函数参数或者返回类型使用。
类的访问级别也可以影响到类成员(属性、函数、初始化方法等)的默认访问级别。如果你将类申明为private类,那么该类的所有成员的默认访问级别也会成为private。如果将类申明为public或者internal类(或者不明确的申明访问级别,即使用默认internal访问级别),那么该类的所有成员的访问级别式internal。
note:上面提到,一个public类的所有成员的访问级别默认为internal级别,而不是public级别。如果你想将某个成员申明为public级别,那么你必须使用修饰符明确的声明该成员。这样做的好处是,在你定义公共接口API的时候,可以明确的选择那些属性或方法是需要公开的,哪些是内部使用的,可以避免将内部使用的属性方法公开成公共API的错误。
//显式的public类
public class SomePublicClass {
//显式的public类成员
public var somePublicProperty = 0
//隐式的internal类成员
var someInternalPeroperty = 0
//显式的private 方法
private func somePrivateMethod(){
}
}
//隐式的internal类
class SomeInternalClass {
//隐式的internal 成员
var someInternalProperty = 0
//显式的private 方法
private func somePrivateMethod(){
}
}
//显式的private类
private class SomePrivateClass {
//隐式的private类成员
var somePrivatePropery = 0
//隐式的private方法
func somePrivateMethod(){
}
}
元组类型
元组的访问级别使用时所有类型的访问级别使用中最为严谨。比如说,如果你构建一个包含两种不同类型元素的元组,其中一个元素类型的访问级别为internal,另外一个为private级别,哪么这个元组的访问级别为private。也就是说元组的访问级别与元组中访问级别最低的类型一致。
note:元组不同于类、结构体、枚举、函数那样有单独的定义。元组的访问级别是在它使用时自动推导出的,而不是明确的申明。
函数类型
函数的访问级别需要根据该函数的参数类型和返回类型的访问级别得出。如果根据参数类型和返回类型得出的函数访问级别不符合默认上下文,哪么就需要明确地声明该函数的访问级别。
eg:
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// function implementation goes here
}
因为(SomeInternalClass,SomePrivateClass)这个元组访问级别是private的,所以函数需要显式的设置为private的。而不能使用默认修饰符internal。
枚举类型
枚举中成员的访问级别继承自该枚举,你不能为枚举中的成员单独申明不同的访问级别。
比如下面的例子,枚举 CompassPoint 被明确的申明为public级别,那么它的成员 North,South,East,West的访问级别同样也是public:
public enum CompassPoint {
case North
case South
case East
case West
}
枚举定义中的任何原始值或关联值的类型都必须有一个访问级别,这个级别至少要不低于枚举的访问级别。比如说,你不能在一个internal访问级别的枚举中定义private的原始值类型。
嵌套类型
如果在private级别的类型中定义嵌套类型,那么该嵌套类型就自动拥有private访问级别。如果在public或者internal级别的类型中定义嵌套类型,那么该嵌套类型自动拥有internal访问级别。如果想让嵌套类型拥有public访问级别,那么需要明确地申明该嵌套类型的访问级别。
子类
子类的访问级别不得高于父类的访问级别。比如说,父类的访问级别是internal,子类的访问级别就不能声明为public。
此外,在满足子类不高于父类访问级别以及遵循各方位级别作用域(即模块或源文件)的前提下,你可以重写任意类成员(方法,属性,初始化方法,下标索引等等)。
如果我们无法直接访问某个类中的属性或函数等,那么可以继承该类,从而可以更容易的访问到该类的类成员。
public class A {
private func someMethod() {}
}
internal class B: A {
override internal func someMethod() {
}
常量、变量、属性、下标
常量、变量、属性不能拥有比它们的类型更高的访问级别。比如说,你定义一个pbulic级别的属性,但是它的类型是private的级别,这是编译器所不允许的。同样,下标也不能拥有比索引类型或返回类型更高的访问级别。
初始化
我们可以给自己定义的初始化方法申明访问级别,但是要不高于它所属类的访问级别。
如同函数或方法参数,初始化方法参数的访问级别也不能低于初始化方法的访问级别。
默认初始化方法
swift为结构体、类都提供了一个默认的无参初始化方法,用于给它们的所有属性提供赋值操作,但不会给出具体的值。默认初始化方法的访问级别所属类型的访问级别相同。
note:如果一个类型被申明为public级别,哪么默认的初始化方法的访问级别为internal。如果你想让无参的初始化方法在其他模块中可以被使用,哪么你必须提供一个具有public访问级别的无参初始化方法。
结构体的默认成员初始化方法
如果结构体中的任一存储属性的访问级别为private,那么它的默认成员初始化方法访问级别就是private。尽管如此,结构体的初始化方法的访问级别依然是internal。
如果你想在其他模块中使用该结构体的默认成员初始化方法,那么你需要提供一个访问级别为public的默认成员初始化方法。
协议
如果想为一个协议明确的申明访问级别,哪么需要注意一点,就是你要确保该协议旨在你申明的访问级别作用域中使用。
协议中的每一个必须要实现的函数都具有和该协议相同的访问级别。这样才能确保该协议的使用者可以实现它所提供的函数。
note:如果你定义了一个public访问级别的协议,哪么实现该协议提供的必要函数也会是public的访问级别。这一点不同于其他类型,比如,public访问级别的其他类型,他们的成员的访问级别为internal。
协议继承
如果定义了一个新的协议,并且该协议继承了一个已知的协议,那么新的协议拥有的访问级别最高也只和被继承的协议的访问级别相同。比如说,你不能定义一个public的协议而去继承一个internal的协议。
协议一致性
类可以采用比自身访问级别低的协议。比如说,你可以定义一个public级别的类,可以让它的其他模块中使用,同时它也可以采用一个internal级别的协议,并且只能定义了该协议的模块中使用。
采用了协议的类的访问级别取它本身和所采用协议中最低的访问级别,也就是说如果一个类是public级别,采用的协议是internal级别,那么采用了这个协议后,该类的访问级别也是internal。
如果你采用了协议,那么实现了协议所必须的方法后,该方法的访问级别遵循协议的访问级别。比如说,一个public级别的类,采用了internal级别的协议,那么该类实现协议的方法至少也得食internal。
note:swift和Objective-C一样,协议的一致性保证了一个类不可能在同一个程序中用不同的方法采用同一个协议。
扩展
你可以在条件允许的情况下对类、结构体、枚举进行扩展。扩展成员应该具有和原始类成员一致的访问级别。比如你扩展了一个公共类型,那么你新加的成员应该具有和原始成员一样默认的internal的访问级别。
或者你可以明确申明访问级别(比如使用 private extesion)给该扩展内所有成员申明一个新的默认访问级别。这个新的访问级别仍然可以被单独成员所申明的访问级别所覆盖。
协议的扩展
如果一个扩展采用了某个协议,那么你就不能对该扩展使用该访问级别修饰符来申明了。该扩展中实现协议的方法都会遵循该协议的访问级别。
泛型
泛型类型或泛型函数的访问级别取泛型类型、函数本身、泛型类型参数三者中的最低访问级别。
类型别名
任何你定义的类型别名都被当作不同的类型,以便于进行访问控制。一个类型别名的访问级别不可高于原类型的访问级别。比如说,一个private级别的类型别名可以设定给一个public、internal、private的类型,但是一个public级别的类型别名只能设定给一个public级别的类型,不能设定给internal或private级别的类型。
note:这条规定也适用于为满足协议一致性而给相关类型命名别名的情况。