Swift文档Chapter 21 协议


协议规定了一个蓝图,规定了用来实现某一特定任务或者功能的方法、属性,以及其他需要的东西。

协议语法

protocol SomeProtocol {
    // 这里是协议的定义部分
}

自定义类型如果需要遵循协议,需要用:分隔,遵循多个协议使用,分隔。

struct SomeStructure: FirstProtocol, AnotherProtocol {
    // 这里是结构体的定义部分
}

如果这个类型拥有一个父类,那么父类名放在协议名之前:

class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
    // 这里是类的定义部分
}

属性要求

协议可以要求提供特定名称的实例属性或者类型属性,不指定是计算属性还是存储属性。同时,也可以定义属性是可读的还是可读可写的。
如果协议只要求可读,实现中可以是可读可写的。协议用var来声明变量属性,在声明类型后加上{set get}表示属性可读可写,加上{get}表明属性可读。
使用static前缀可以声明类型属性,在类实例实现时可以使用class关键字。

方法要求

协议中方法的定义和其他类型中完全相同使用func关键字,只不过没有花括号的函数体。在协议中定义类方法的时候,总是使用static关键字作为前缀。即使在类实现时,类方法要求使用classstatic作为关键字前缀。

异变方法要求

有时需要在方法中改变(或异变)方法所属的实例。例如,在值类型(即结构体和枚举)的实例方法中,将mutating关键字作为方法的前缀,写在func关键字之前,表示可以在该方法中修改它所属的实例以及实例的任意属性的值。如果是类类型实现mutating函数可以不加关键字,但是枚举类型和结构体类型需要。

构造器要求

协议可以要求遵循协议的类型实现指定的构造器。你可以像编写普通构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体:

protocol SomeProtocol {
    init(someParameter: Int)
}

协议构造器要求的类实现

类实现协议的构造器,无论是指定构造器,还是作为便利构造器,都需要加上required修饰符。
required修饰符可以确保所有子类也必须提供此构造器实现,如果是final类,那么不需要required修饰符。
如果一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么该构造器的实现需要同时标注requiredoverride修饰符。

可失败构造器要求

协议还可以为遵循协议的类型定义可失败构造器要求。遵循协议的类型可以通过可失败构造器(init?)或非可失败构造器(init)来满足协议中定义的可失败构造器要求。

协议作为类型

协议本身并未实现任何功能,但是协议可以作为一个类型来使用。协议作为类型使用通常被称为存在类型
协议可以像普通类型一样使用:

  • 作为函数,方法以及构造器里的参数类型或者返回值类型;
  • 作为常量,变量或者属性的类型;
  • 作为数组,字典等其他容器的元素类型。

协议是一种类型,因此命名也要使用大写字母开头的驼峰命名法。

当我们在类型中定义协议参数的时候,我们只需要传入一个遵循了该协议的类型即可。我们只能使用协议内定义的方法和类型,不可以使用底层的类型提供的方法和属性。

委托

委托是一种设计模式,允许类或者结构体把一部分功能委托给其他类型的实例。委托模式需要定义协议封装需要实现的功能,能确保遵循协议的类型能提供这些功能。

在扩展里添加协议遵循

在无法修改源代码的情况下,我们可以扩展令已有类型遵循并符合协议。通过扩展可以给类型增加新的协议并使其遵循这个协议。和原始协议中定义并遵循协议是一样的。需要使用extension关键字。

有条件地遵循协议

如果我们在泛型中使用扩展添加协议,可能只符合某一种类型。因此我们使用where扩展类型时列出限制让泛型类型有条件地遵循某协议。

在扩展里声明采纳协议

如果说某个类型已经实现了协议,但是并没有遵循该协议。那么使用空的扩展让它来遵循协议。

struct Hamster {
    var name: String
       var textualDescription: String {
        return "A hamster named \(name)"
    }
}
extension Hamster: TextRepresentable {}

即使类型已经满足了协议要求,也不会自动去遵循协议。必须显式地遵循协议。

协议类型的集合

协议类型可以在数组或者字典这样的集合中使用。

协议的继承

协议也可以继承一个或多个协议并在基础上增加新的要求。协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔:

protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
    // 这里是协议的定义部分
}

类的专属协议

添加AnyObject到协议的继承列表,那么这个协议只可以用在类类型,不能用在结构体或者枚举类型。

协议合成

如果说一个类型需要遵循多个协议,那么可以使用协议合成复合成同一个协议,使用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(to celebrator: Named & Aged) {
    print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(to: birthdayPerson)
// 打印 “Happy birthday Malcolm - you're 21!”

检查协议一致性

可以使用is或者as进行协议类型检查,检查是否符合某个协议。

  • is检查实例是否符合某个协议,符合返回true,否则返回false
  • as?返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回nil
  • as!将实例强制向下转换到某个协议类型,如果强转失败,将触发运行时错误。

可选的协议要求

协议可以定义可选的要求,遵循协议的类型可以选择是否实现这些要求。在协议中可选的要求使用optional关键字作为前缀。可选要求如果用在和Object-C打交道的代码中,协议和可选要求都必须加上@obj属性。记@objc特性的协议只能被继承自Objective-C类的类或者@objc类遵循,其他类以及结构体和枚举均不能遵循这种协议。
可选要求的属性可会变成可选的,即使是函数也会变成可选类型,而不是返回值可选。协议中的可选要求可通过可选链式调用来使用,因为遵循协议的类型可能没有实现这些可选要求。

协议扩展

协议可以通过扩展来为遵循协议的类型提供属性、方法以及下标的实现。这样就不需要在所有遵循该协议的类型进行重复的实现,也不用使用全局函数。

提供默认实现

可以通过协议扩展来为协议要求的属性、方法以及下标提供默认的实现。如果遵循协议的类型为这些要求提供了自己的实现,那么这些自定义实现将会替代扩展中的默认实现被使用

为协议扩展添加限制条件

在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用where子句来描述。