//协议:规定了用来实现某一特定任务或者功能的方法、属性,以及其他需要的东西。
//类、结构体、枚举都可以遵循协议,并为协议定义的这些要求提供具体实现。
//某个类型能满足某个协议的要求,就可以说该类型遵循这个协议
//除了遵循协议的类型必须实现的要求外,还可以对协议进行扩展,通过扩展来实现一部分要求或者实现一些附加功能,这样遵循协议的类型就能够使用这些功能
/**1.协议语法
protocol SomeProtocol {
//这里是协议的定义部分
}
要让自定义类型遵循某个协议,在定义类型时,需要在类型名称后加上协议名称,中间以冒号(:)分割,遵循多个协议时,各协议直接用逗号(,)分隔
struct SomeStructure: FirstProtocol, AnotherProtocol {
//这里是结构体的定义部分
}
拥有父类的类在遵循协议时,应该讲父类名放在协议名之前,以都厚分隔:
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
//这里是类的定义部分
}
*/
//2.属性要求
//协议可以要求遵循协议的类型提供d特定名称和类型的实例属性或类型属性;协议还指定属性是可读的还是可读写的;但不指定x属性是存储型属性还是计算型属性
protocol SomeProtocolMust{//协议用var关键字来声明变量属性,在类型声明后加上{set get}来表示属性是可读可写的,可读属性用{ get }来表示
var mustBeSettable: Int { get set }
var justGetter: Int { get }
//在协议中定义类型属性是,总是使用 static 关键字 作为前缀。当类类型遵循协议时,除了 static关键字,还可以使用 class 关键字来声明类型属性
static var someTypeProperty: Int { get set }
}
//示例:
protocol FullyNamed {//该协议除了要求遵循协议的类型提供 fullName属性外,并没有其他特别的要求。
var fullName: String { get } // 这个协议表示,任何遵循 fullyNamed的类型,都必须有一个可读的 String 类型的实例属性 fullName
}
struct PersonM: FullyNamed { //该结构体遵循了FullyNamed协议
var fullName: String //如果不满足FullyNamed协议,在编译时会报错
}
let johnM = PersonM(fullName: "斯威夫特")
print("johnM的名字是:\(johnM.fullName)")
class StarshipM: FullyNamed {
var prefix: String?
var name: String
init(name:String, prefix: String? = nil) {
self.name = name
self.prefix = prefix
}
var fullName: String { //将 fullName 属性实现为只读的计算型属性
return (prefix != nil ? prefix! + " " : "") + name // 返回 prefix+name,prefix为空就返回name
}
}
var ncc1701 = StarshipM(name: "企业号", prefix: "联合舰队")
print("ncc1701星舰的名字是:\(ncc1701.fullName)")
//3.方法要求
//协议可以要求遵循协议的类型实现某些指定的实例方法或类方法。可以在协议中定义具有可变参数的方法,但是不能为其参数提供默认值
//在协议中定义类方法的时候,需要使用static关键字作为前缀,当类类型遵循协议时,除了static关键字,还可以使用class关键字作为前缀
protocol SomeProtocolFunc {
static func SomeTypeMethod()
}
//示例:定义了一个只含有一个实例方法的协议
protocol RandomNumberGenerator { //该协议要求遵循协议的类型必须拥有一个名为random,返回值类型为Double实例方法。
func random() -> Double
}
class LinearCongruentialGenerator: RandomNumberGenerator {//同余数生成程序
var lastRandom = 42.0
let m = 139968.0
let a = 3877.0
let c = 29573.0
func random() -> Double {
lastRandom = ((lastRandom * a + c).truncatingRemainder(dividingBy: m)) //使用 truncatingRemainder 方法进行浮点数取余: (lastRandom * a + c)% m 取余数
return lastRandom / m
}
}
let generatorLine = LinearCongruentialGenerator()
print("第一个随机数为:\(generatorLine.random())")
print("第二个随机数为:\(generatorLine.random())")
//4.Mutating 方法要求
//将mutating关键字作为方法的前缀写在func关键字之前,表示可以在该方法中修改它所属的实例以及实例的任意属性的值。
//实现协议中的mutating方法时,若是类类型则不用谢mutating关键字。对于结构体和枚举,则必须写mutating关键字。
protocol Togglable {
mutating func toggle() // toggle()方法在定义时使用mutating标记,表明当它被调用时,将会改变遵循协议的类型的实例
}
enum OnOffSwitch: Togglable {
case off,on
mutating func toggle() { //当枚举或结构体实现toggle()方法时需要在前面加上mutating前缀
switch self {
case .off:
self = .on
case .on:
self = .off
}
}
}
var lightSwitch = OnOffSwitch.off
lightSwitch.toggle()
print("开关的值为:\(lightSwitch)")
//5.构造器要求
//协议可以要求遵循协议的类型实现指定的构造器。你可以想编写普通构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体:
protocol SomeProtocolInit{
init(someParameter: Int)
}
//可以在遵循协议的类中实现构造器,无论是作为指定构造器,还是作为便利构造器,都必须为构造器实现标上required修饰符:
class SomeClassInit: SomeProtocolInit {
required init(someParameter: Int) { //使用required 修饰符可以确保所有子类也必须提供此构造器实现,从而也能符合协议。如果类已经被标记为 final 那么不需要在协议构造器的实现中使用required修饰符,因为final类不能有子类。
//这里是构造器的实现部分
}
}
//如果一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么该构造器的实现需要同时标注required 和 override修饰符
protocol SomeProtocolSup {
init()
}
class SomeSuperClassSup {
init() {
//这里是构造器的实现部分
}
}
class SomeSubClassSup: SomeSuperClassSup,SomeProtocolSup {
//因为遵循了协议,所以要加上 required; 因为继承自父类,所以需要加上 override
required override init() {
//这里是构造器的实现部分
}
}
//协议还可以为遵循协议的类型定义可失败构造器要求。遵循协议的类型可以通过可失败构造器(init?)或非可失败构造器(init)来满足协议中定义的可失败构造器要求。协议中定义的非可失败构造器要求可以通过非可失败构造器(init)或隐式解包可失败构造器(init!)来满足。
//6.协议作为类型
//协议开一被昂做一个成熟的类型来使用,使用场景有:作为函数、方法或构造器中的参数类型或返回值类型;作为常量、变量或属性的类型、作为数组、字典或其他容器中的元素类型。
class DiceP {//骰子
let sides: Int //表示 骰子有几个面
let gennerator: RandomNumberGenerator // 为骰子提供一个随机数生成器
init(sides: Int, gennerator: RandomNumberGenerator) {
self.sides = sides
self.gennerator = gennerator
}
func roll() -> Int { //骰出来的数字
return Int(gennerator.random() * Double(sides)) + 1
}
}
var d6 = DiceP(sides: 6, gennerator: LinearCongruentialGenerator())
for _ in 1...5 {
print("随机的骰子数为:\(d6.roll())")
}
//7.委托代理模式
//委托是一种设计模式,它允许类或结构体将一些需要它们负责的功能委托给其他类型的实例。委托模式的实现很简单:定义协议来封装那些需要被委托的功能,这样就能确保遵循协议的类型能提供这些功能。委托模式可以用来响应特定的动作,或者接收外部数据源提供的数据,而无需关心外部数据源的类型。
protocol DiceGameP { //可以被任意涉及骰子的游戏遵循。
var dice: DiceP { get }
func play()
}
protocol DiceGameDelegate {//可以被任意类型遵循,用来追踪DiceGame的游戏过程
func gameDidStart(_ game: DiceGameP)
func game(_ game: DiceGameP, didStartNewTurnWithDiceRoll diceRoll: Int)
func gameDidEnd(_ game: DiceGameP)
}
class SnakesAndLadders: DiceGameP { // 遵循DiceGameP协议
let finalSquare = 25
let dice = DiceP(sides: 6, gennerator: LinearCongruentialGenerator())//只要求dice为可读
var square = 0
var board: [Int]
init() {
board = [Int](repeating: 0, count: finalSquare + 1)
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
}
var delegate: DiceGameDelegate?
func play() {
square = 0//当前位置
delegate?.gameDidStart(self)//开始游戏
gameLoop: while square != finalSquare {//循环 当前位置 != 终点位置
let diceRoll = dice.roll()//骰出来的数字
delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)//游戏中,
switch square + diceRoll {//当前位置 + 骰出来的数字
case finalSquare: // 终点位置
break gameLoop // 结束游戏
case let newSquare where newSquare > finalSquare: // 超过终点位置
continue gameLoop//开始下一轮循环
default:
square += diceRoll
square += board[square]
}
}
delegate?.gameDidEnd(self) // 游戏结束
}
}
class DiceGameTracker: DiceGameDelegate {//记录游戏的过程
var numberOfTurns = 0
func gameDidStart(_ game: DiceGameP) {// 游戏开始
numberOfTurns = 0
if game is SnakesAndLadders {
print("开始了蛇和梯子的新游戏")
}
print("游戏使用了 \(game.dice.sides)角骰子")
}
func game(_ game: DiceGameP, didStartNewTurnWithDiceRoll diceRoll: Int) { //游戏中
numberOfTurns += 1
print("骰子骰出来的数字是\(diceRoll)")
}
func gameDidEnd(_ game: DiceGameP) { // 游戏结束
print("比赛持续了\(numberOfTurns)圈")
}
}
let trackerP = DiceGameTracker()
let gameP = SnakesAndLadders()
gameP.delegate = trackerP
gameP.play()
//8.通过扩展添加协议一致性
//可以通过扩展 让已有类型 遵循并符合协议;扩展可以为已有类型添加属性、方法、下标以及构造器,因此可以符合协议中的相应要求。
//注意:通过扩展令已有类型遵循并符合协议时,该类型的所有实例也会随之获得协议中定义的各项功能。
protocol TextRepresentable {//该协议只具有一个文本描述的功能
var textualDescription: String { get }
}
extension DiceP: TextRepresentable { // 为 DiceP 类添加扩展,另其遵循 TextRepresentable 协议
var textualDescription: String { // 实现协议方法
return "这是一个\(sides)面骰子"
}
}
//现在 所有的 DiceP 的实例 都可以看做TextRepresentable类型
let d12 = DiceP(sides: 12, gennerator: LinearCongruentialGenerator())
print(d12.textualDescription)
extension SnakesAndLadders: TextRepresentable {
var textualDescription: String {
return "这是一个有\(finalSquare)个格子的蛇与梯子游戏"
}
}
print(gameP.textualDescription)
//9.通过扩展遵循协议
//当一个类型已经符合了某个协议中的所有要求,却还没有声明遵循该协议时,可以通过空扩展体的扩展来遵循该协议:
struct Hamster {
var name: String
var textualDescription: String {
return "小老鼠的名字叫\(name)"
}
}
extension Hamster: TextRepresentable{}
//此时,Hamster的实例可以作为TextRepresentable类型使用:
let simonTheHamster = Hamster(name: "硕鼠")
let somethingTextRepresentable: TextRepresentable = simonTheHamster
print(somethingTextRepresentable.textualDescription)
//10.协议类型的集合
//协议类型可以在数组或者字典这样的集合中使用。
let thingsP: [TextRepresentable] = [gameP, d12, simonTheHamster]
for thing in thingsP {
print("遍历thingsP 数组: \(thing.textualDescription)")
}
//11.协议的继承
//协议能够继承一个或多个其他协议,可以在继承的协议的基础上增加新的要求,协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔
protocol InHeritingProtocol: SomeProtocolSup, SomeProtocolFunc {
//这里是协议的定义部分
}
protocol PrettyTextRepresentablePR: TextRepresentable { //PrettyTextRepresentablePR 协议继承了 TextRepresentable 协议;任何遵循PrettyTextRepresentablePR协议的类型在满足该协议的要求时, 也必须满足 TextRepresentable 协议的要求。
var prettyTextualDescription: String { get }
}
//扩展SnakesAndLadders 类 使其遵循 PrettyTextRepresentablePR 协议
extension SnakesAndLadders: PrettyTextRepresentablePR {
var prettyTextualDescription: String {
var output = textualDescription + ":\n"
for index in 1...finalSquare {
switch board[index] {
case let ladder where ladder > 0:
output += "▲ "
case let snake where snake < 0:
output += "▼ "
default:
output += "○ "
}
}
return output
}
}
print(gameP.prettyTextualDescription)
//12.类类型专属协议
//可以在协议的继承列表中,通过添加class关键字来限制协议只能被类类型遵循,而结构体或枚举不能遵循该协议.
//class关键字必须第一个出现在协议的继承列表中,在其他继承的协议之前:
protocol SomeClassOnlyProtocol: class, SomeProtocolFunc {
//这里是类类型专属协议的定义部分
}
//注意:当协议定义的要求需要遵循协议的类型必须是引用语义而非值语义时,应该采用类类型专属协议。
//13.协议合成
//有时候需要同时遵循多个协议,可以将多个协议采用 SomeProtocol & AnothreProtocol这样的格式进行组合,称为协议合成
protocol NamedP {
var name: String { get }
}
protocol AgedP {
var age: Int { get }
}
struct PersonP: NamedP, AgedP {
var name: String
var age: Int
}
func wishHappyBirthday(to celebrator: NamedP & AgedP){
print("生日快乐,\(celebrator.name), 你今年\(celebrator.age)岁了")
}
let birthdayPerson = PersonP(name: "豆豆", age: 3)
wishHappyBirthday(to: birthdayPerson)
//将Location类和前面的Named协议进行组合:
class LocationP {
var latitude: Double
var longitude: Double
init(latitude: Double, longitude: Double) {
self.latitude = latitude
self.longitude = longitude
}
}
class CityP: LocationP, NamedP {
var name: String
init(name: String, latitude: Double, longitude: Double) {
self.name = name
super.init(latitude: latitude, longitude: longitude)
}
}
func beginConcert(in location: LocationP & NamedP) { //接受任何Location的子类,并且遵循Named协议
print("Hello, \(location.name)!")
}
let seattle = CityP(name: "狗狗", latitude: 42.3, longitude: -120.4)
beginConcert(in: seattle)
//14.检查协议一致性
//可以使用 is 和 as 操作符来检查是否符合协议,并且可以转换到指定的协议类型。
//is 用来检查实例是否符合某个协议,若符合则返回true,否则返回false
//as? 返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回nil
//as! 将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误
protocol HasAreaJ {
var area: Double { get }
}
class CircleJ: HasAreaJ {
let pi = 3.1415927
var radius: Double
var area: Double { return pi * radius * radius}
init(radius: Double) { self.radius = radius }
}
class CountryJ: HasAreaJ {
var area: Double
init(area: Double) { self.area = area }
}
class AnimalJ {
var legs: Int
init(legs: Int) { self.legs = legs }
}
let objects: [AnyObject] = [
CircleJ(radius: 2.0),
CountryJ(area: 234610),
AnimalJ(legs: 4)
]
for object in objects {
if let objectWithArea = object as? HasAreaJ {
print("遵循了HasAreaJ协议:\(objectWithArea.area)")
} else {
print("没有遵循HasAreaJ协议")
}
}
//15.可选的协议要求
//协议可以定义可选要求,遵循协议的类型可以选择是否实现这些要求。
//在协议中使用 optional 关键字 作为前缀来定义可选要求;在需要和OC代码打交道中,协议 和 可选要求 都必须带上@objc属性;
//标记@objc特性的协议只能被继承自OC类的类 或者 @objc类遵循,其他类以及结构体和枚举均不能遵循这种协议。
//使用可选要求时,他们的类型会自动变成可选的,比如:一个类型为(Int) -> String 的方法 会变成 ((Int) -> String)? 需要注意的是整个函数类型是可选的,而不是函数的返回值。
@objc protocol CounterDataSource {
@objc optional func incrementForCount(count: Int) -> Int
@objc optional var fixedIncrement: Int { get }
}
//严格上来讲,上面这个协议中的方法和属性都是可选的,遵循该协议的类可以不实现这些要求,但不推荐这么做。
class Counter {
var count = 0
var dataSource: CounterDataSource?
func increment() {
if let amount = dataSource?.incrementForCount?(count: count){
count += amount
} else if let amount = dataSource?.fixedIncrement {
count += amount
}
}
}
class ThreeSource: NSObject, CounterDataSource {
let fixedIncrement = 3
}
var counterF = Counter()
counterF.dataSource = ThreeSource()
for _ in 1...4 {
counterF.increment()
print("累加的值为:\(counterF.count)")
}
//16.协议扩展
//协议可以通过扩展来为遵循协议的类型提供属性、方法以及下标的实现。
//通过这种方式,你可以基于协议本身来实现这些功能,而无需再每个遵循协议的类型中都重复同样的实现,也无需使用全局函数。
extension RandomNumberGenerator {
func randomBool() -> Bool {
return random() > 0.5
}
}
let genneratorR = LinearCongruentialGenerator()
print("随机数为:\(genneratorR.random())")
print("随机的布尔值为:\(genneratorR.randomBool())")
//提供默认实现
//如果遵循协议的类型为这些要求提供了自己的实现,那么这些自定义实现将会替代扩展中的默认实现被使用。
//通过协议扩展为协议要求提供的默认实现和可选的协议要求不同。虽然在这两种情况下,遵循协议的类型都无需自己实现这些要求,但是通过扩展提供的默认实现可以直接调用,而无需使用可选链式调用。
extension PrettyTextRepresentablePR { //PrettyTextRepresentablePR 协议 继承自 TextRepresentable协议,可以为其提供一个 prettyTextualDescription 属性,只是简单地返回textualDescription 属性的值
var prettyTextualDescription: String {
return textualDescription
}
}
//为协议扩展添加限制条件
//在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件是,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用where子句来描述。
//示例:扩展Collection 协议,但是只适用于集合中的元素遵循了 TextRepresentable 协议的情况
extension Collection where Iterator.Element: TextRepresentable {
var textualDescription: String { //返回整个集合的文本描述
let itemsAsText = self.map { $0.textualDescription }
return "[" + itemsAsText.joined(separator: ", ") + "]"
}
}
let gougouTheHamster = Hamster(name: "狗狗")
let maomaoTheHamster = Hamster(name: "猫猫")
let laoshuTheHamster = Hamster(name: "就叫老鼠")
let hamsters = [gougouTheHamster,maomaoTheHamster,laoshuTheHamster]
//hamsters 数组符合 Collection协议,而数组中的元素又符合TextRepresentable协议,所以数组可以使用 textualDescription 属性得到数组内容的文本表示
print("为协议扩展添加限制:\(hamsters.textualDescription)")
//注意: 若多个协议扩展都为了同一个协议要求 提供了默认实现,而遵循协议的类型又同时瞒住这些协议扩展的限制条件,那么将会使用限制条件最多的那个协议扩展提供的默认实现。
网友评论