Protocol(协议)

2017-11-22  本文已影响13人  你weixiao的时候很美

// swift的协议很重要
// 协议 protocol

//“协议 定义了一个蓝图,规定了用来实现某一特定任务或者功能的方法、属性,以及其他需要的东西。类、结构体或枚举都可以遵循协议,并为协议定义的这些要求提供具体实现。某个类型能够满足某个协议的要求,就可以说该类型遵循这个协议”
//“除了遵循协议的类型必须实现的要求外,还可以对协议进行扩展,通过扩展来实现一部分要求或者实现一些附加功能,这样遵循协议的类型就能够使用这些功能。”

//1. 协议语法

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

//“要让自定义类型遵循某个协议,在定义类型时,需要在类型名称后加上协议名称,中间以冒号(:)分隔。遵循多个协议时,各协议之间用逗号(,)分隔:”

struct SomeStructure:SomeProtocol{
    //结构体的定义部分
}

// “拥有父类的类在遵循协议时,应该将父类名放在协议名之前,以逗号分隔”

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

//2. 属性要求
//“协议可以要求遵循协议的类型提供特定名称和类型的实例属性或类型属性。协议不指定属性是存储型属性还是计算型属性,它只指定属性的名称和类型。此外,协议还指定属性是可读的还是可读可写的。”
//“如果协议要求属性是可读可写的,那么该属性不能是常量属性或只读的计算型属性。如果协议只要求属性是可读的,那么该属性不仅可以是可读的,如果代码需要的话,还可以是可写的。”
//“协议总是用 var 关键字来声明变量属性,在类型声明后加上 { set get } 来表示属性是可读可写的,可读属性则用 { get } 来表示:”

protocol propertyProtocol{
    var mustBesettable:Int {
        get set
    }
    var doesNotNeedToBeSettable:Int{
        get
    }
}

//“在协议中定义类型属性时,总是使用 static 关键字作为前缀。当类类型遵循协议时,除了 static 关键字,还可以使用 class 关键字来声明类型属性:”

protocol AnotherProtocol{
    static var someTypeProperty:Int{
        get set
    }
}

//示例

protocol FullyNamed{
    var fullName:String {
        get
    }
}
struct Person:FullyNamed {
    var fullName: String
}
let john = Person(fullName:"john Appleseed")

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 ncc1701 = Starship(name:"EnterPrise",prefix:"USS")
ncc1701.fullName //USSEnterPrise

//3.方法要求
//“协议可以要求遵循协议的类型实现某些指定的实例方法或类方法。这些方法作为协议的一部分,像普通方法一样放在协议的定义中,但是不需要大括号和方法体。可以在协议中定义具有可变参数的方法,和普通方法的定义方式相同。但是,不支持为协议中的方法的参数提供默认值”
//“正如属性要求中所述,在协议中定义类方法的时候,总是使用 static 关键字作为前缀。当类类型遵循协议时,除了 static 关键字,还可以使用 class 关键字作为前缀:”

protocol MethodProtocol{
    static func someTypeMethod()
}

//示例

protocol RandomNumberGennerator{
    func random() -> Double
}
class LinearCongruentialGenerator:RandomNumberGennerator{
    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))
        return lastRandom / m
    }
}

//4. Mutating方法要求
// “有时需要在方法中改变方法所属的实例。例如,在值类型(即结构体和枚举)的实例方法中,将 mutating 关键字作为方法的前缀,写在 func 关键字之前,表示可以在该方法中修改它所属的实例以及实例的任意属性的值。”
//“如果你在协议中定义了一个实例方法,该方法会改变遵循该协议的类型的实例,那么在定义协议时需要在方法前加 mutating 关键字。这使得结构体和枚举能够遵循此协议并满足此方法要求。”
//“注意 实现协议中的 mutating 方法时,若是类类型,则不用写 mutating 关键字。而对于结构体和枚举,则必须写 mutating 关键字。”

protocol Togglable{
    mutating func toggle()
}
enum OnOffSwitch:Togglable{
    case Off,On
    mutating func toggle() {
        switch self {
        case .Off:
            self = .On
        case .On:
            self = .Off
        }
    }
}
var ligthSwitch = OnOffSwitch.Off
ligthSwitch.toggle()

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

protocol InitProtocol{
    init(someParameter:Int)
}

//“你可以在遵循协议的类中实现构造器,无论是作为指定构造器,还是作为便利构造器。无论哪种情况,你都必须为构造器实现标上 required 修饰符”

class InitClass:InitProtocol{
    required init(someParameter: Int) {
        //构造器的实现
    }
}

//“使用 required 修饰符可以确保所有子类也必须提供此构造器实现,从而也能符合协议。”
//“注意 如果类已经被标记为 final,那么不需要在协议构造器的实现中使用 required 修饰符,因为 final 类不能有子类。”
//“如果一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么该构造器的实现需要同时标注 required 和 override 修饰符:

protocol DoubleProtocol{
    init()
}

class SuperClass{
    init() {
    }
}
class subClass: SuperClass,DoubleProtocol {
    //因为遵循协议,需要加上required,
   //因为继承自父类,需要加上 override
    required override init() {
        //这里是构造器实现
    }
}

//5.协议作为类型
//“尽管协议本身并未实现任何功能,但是协议可以被当做一个成熟的类型来使用。”
/*
“作为函数、方法或构造器中的 参数类型或返回值类型
作为常量、变量或属性的类型
作为数组、字典或其他容器中的 元素类型”
*/

class Dice{
    let sides : Int
    let generator : RandomNumberGennerator
    init(sides:Int,generator:RandomNumberGennerator) {
        self.sides = sides
        self.generator = generator
    }
    func roll() -> Int {
        return Int(generator.random() * Double(sides))+1
    }
}

//“上例中定义了一个 Dice 类,用来代表桌游中拥有 N 个面的骰子。Dice 的实例含有 sides 和 generator 两个属性,前者是整型,用来表示骰子有几“个面,后者为骰子提供一个随机数生成器,从而生成随机点数。
//“generator 属性的类型为 RandomNumberGenerator,因此任何遵循了 RandomNumberGenerator 协议的类型的实例都可以赋值给 generator,除此之外并无其他要求。
//“Dice 类还有一个构造器,用来设置初始状态。构造器有一个名为 generator,类型为 RandomNumberGenerator 的形参。在调用构造方法创建 Dice 的实例时,可以传入任何遵循 RandomNumberGenerator 协议的实例给 generator

var d6 = Dice(sides:6,generator:LinearCongruentialGenerator())
for _ in 1...5{
    print("Random dice roll is \(d6.roll())")
}

//6.委托代理模式
// “委托是一种设计模式,它允许类或结构体将一些需要它们负责的功能委托给其他类型的实例,委托模式的实现很简单:定义协议来封装那些需要被委托的功能,这样就能确保遵循协议的类型能提供这些功能。委托模式可以用来响应特定的动作,或者接收外部数据源提供的数据,而无需关心外部数据源的类型。

protocol DiceGame{
    var dice : Dice{get}
    func play()
}
protocol DiceGameDelegate {
    func gameDidStart(_ game:DiceGame)
    func game(_ game: DiceGame,didStartNewTurnWithDiceRoll diceRoll:Int)
    func gameDidEnd(_ game:DiceGame)
}

//“DiceGame 协议可以被任意涉及骰子的游戏遵循。 DiceGameDelegate 协议可以被任意类型遵循,用来追踪 DiceGame 的游戏过程。”

class SnakesAndLadders:DiceGame{
    let finalSquare = 25
    let dice = Dice(sides:6,generator:LinearCongruentialGenerator())
    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)
    }
}

//“这个版本的游戏封装到了 SnakesAndLadders 类中,该类遵循了 DiceGame 协议,并且提供了相应的可读的 dice 属性和 play() 方法。( dice 属性在构造之后就不再改变,且协议只要求 dice 为可读的,因此将 dice 声明为常量属性”
//“游戏使用 SnakesAndLadders 类的 init() 构造器来初始化游戏。所有的游戏逻辑被转移到了协议中的 play() 方法,play() 方法使用协议要求的 dice 属性提供骰子摇出的值。
//“注意,delegate 并不是游戏的必备条件,因此 delegate 被定义为 DiceGameDelegate 类型的可选属性。因为 delegate 是可选值,因此会被自动赋予初始值 nil。随后,可以在游戏中为 delegate 设置适当的值”
//“因为 delegate 是一个 DiceGameDelegate 类型的可选属性,因此在 play() 方法中通过可选链式调用来调用它的方法。若 delegate 属性为 nil,则调用方法会优雅地失败,并不会产生错误。若 delegate 不为 nil,则方法能够被调用,并传递 SnakesAndLadders 实例作为参数。”

class DiceGameTracker:DiceGameDelegate{
    var numberOfTurns = 0
    func gameDidStart(_ game: DiceGame) {
        numberOfTurns = 0
        if game is SnakesAndLadders{
            print("Started a new game of Snakes and Ladders")
        }
        print("the game is using a \(game.dice.sides) sides dice")
    }
    func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
        numberOfTurns += 1
        print("rolled a \(diceRoll)")
    }
    func gameDidEnd(_ game: DiceGame) {
        print("the game lasted for \(numberOfTurns) turns")
    }
}

//“DiceGameTracker 实现了 DiceGameDelegate 协议要求的三个方法,用来记录游戏已经进行的轮数。当游戏开始时,numberOfTurns 属性被赋值为 0,然后在每新一轮中递增,游戏结束后,打印游戏的总轮数。”

//运行该游戏

let tracker = DiceGameTracker()
let game = SnakesAndLadders()
game.delegate = tracker
game.play()
//打印
//Started a new game of Snakes and Ladders
//the game is using a 6 sides dice
//rolled a 3
//rolled a 5
//rolled a 4
//rolled a 5
//the game lasted for 4 turns

//6.通过扩展添加协议一致性
//“即便无法修改源代码,依然可以通过扩展令已有类型遵循并符合协议。扩展可以为已有类型添加属性、方法、下标以及构造器,因此可以符合协议中的相应要求
//注意 通过扩展令已有类型遵循并符合协议时,该类型的所有实例也会随之获得协议中定义的各项功能。

protocol TextReprresentable{
    var textualDescription : String {get}
}
extension Dice :TextReprresentable{
    var textualDescription: String{
        return "A \(sides) side dice"
    }
}

//“通过扩展遵循并符合协议,和在原始定义中遵循并符合协议的效果完全相同。协议名称写在类型名之后,以冒号隔开,然后在扩展的大括号内实现协议要求的内容。”

let d12 = Dice(sides:12,generator:LinearCongruentialGenerator())
print(d12.textualDescription)
//打印 A 12 side dice

extension SnakesAndLadders:TextReprresentable{
    var textualDescription: String{
        return "A game of Snakes and ladders with \(finalSquare) squares"
    }
}
print(game.textualDescription)
// 打印 A game of Snakes and ladders with 25 squares

//7. 通过扩展遵循协议
//“当一个类型已经符合了某个协议中的所有要求,却还没有声明遵循该协议时,可以通过空扩展体的扩展来遵循该协议:”

struct Hamster{
    var name : String
    var textualDescription:String {
        return "A hamster named \(name)"
    }
}
extension Hamster:TextReprresentable{}
let simonTheHamster = Hamster(name:"simon")
print(simonTheHamster.textualDescription)
//打印A hamster named simon

//“注意 即使满足了协议的所有要求,类型也不会自动遵循协议,必须显式地遵循协议。

//8.协议类型的集合
//“协议类型可以在数组或者字典这样的集合中使用”

let things:[TextReprresentable] = [game,d12,simonTheHamster]
for thing in things {
    print(thing.textualDescription)
}
//打印 A game of Snakes and ladders with 25 squares
//A 12 side dice
//A hamster named simon

//“thing 是 TextRepresentable 类型而不是 Dice,DiceGame,Hamster 等类型,即使实例在幕后确实是这些类型中的一种。由于 thing 是 TextRepresentable 类型,任何 TextRepresentable 的实例都有一个 textualDescription 属性,所以在每次循环中可以安全地访问 thing.textualDescription。”

//9. 协议的继承
//“协议能够继承一个或多个其他协议,可以在继承的协议的基础上增加新的要求。协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔:”

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

protocol PrettyTextRepresentable:TextReprresentable{
    var prettyTextualDescrition : String {get}
}

//“例子中定义了一个新的协议 PrettyTextRepresentable,它继承自 TextRepresentable 协议。任何遵循 PrettyTextRepresentable 协议的类型在满足该协议的要求时,也必须满足 TextRepresentable 协议的要求。”

extension SnakesAndLadders:PrettyTextRepresentable{
    var prettyTextualDescrition: 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
    }
}

//“上述扩展令 SnakesAndLadders 遵循了 PrettyTextRepresentable 协议,并提供了协议要求的 prettyTextualDescription 属性。每个 PrettyTextRepresentable 类型同时也是 TextRepresentable 类型,所以在 prettyTextualDescription 的实现中,可以访问 textualDescription 属性。然后,拼接上了冒号和换行符。”
print(game.prettyTextualDescrition)
//打印 〇〇↑〇〇↑〇〇↑↑〇〇〇↓〇〇〇〇↓〇〇↓〇↓〇

//10.类类型专属协议
//“你可以在协议的继承列表中,通过添加 class 关键字来限制协议只能被类类型遵循,而结构体或枚举不能遵循该协议。class 关键字必须第一个出现在协议的继承列表中,在其他继承的协议之前”

protocol SomeClassOnlyProtocol:class,inheritingProtocol{
    //类类型专属协议
}

//注意:当协议定义的要求需要遵循协议的类型必须是引用语义而非值语义时,应该采用类类型专属协议”

//11.协议合成
//“有时候需要同时遵循多个协议,你可以将多个协议采用 SomeProtocol & AnotherProtocol 这样的格式进行组合,称为 协议合成(protocol composition)。你可以罗列任意多个你想要遵循的协议,以与符号(&)分隔”

protocol Named{
    var name : String{get}
}
protocol Aged {
    var age : Int {get}
}
struct People:Named,Aged{
    var name : String
    var age : Int
    init(_ name:String,_ age:Int) {
        self.name = name
        self.age = age
    }
}
func wishHappyBirthday(to celebrator:Named&Aged){
    print("happy birthday, \(celebrator.name),you are \(celebrator.age)")
}
let birthdayPerson = People("jack",12)
wishHappyBirthday(to: birthdayPerson)
//打印 happy birthday, jack,you are 12

//“Named 协议包含 String 类型的 name 属性。Aged 协议包含 Int 类型的 age 属性。Person 结构体遵循了这两个协议。wishHappyBirthday(to:) 函数的参数 celebrator 的类型为 Named & Aged。这意味着它不关心参数的具体类型,只要参数符合这两个协议即可。”
//“注意 协议合成并不会生成新的、永久的协议类型,而是将多个协议中的要求合成到一个只在局部作用域有效的临时协议中。

//12.检查协议的一致性
//“你可以使用类型转换中描述的 is 和 as 操作符来检查协议一致性,即是否符合某协议,并且可以转换到指定的协议类型。检查和转换到某个协议类型在语法上和类型的检查和转换完全相同:”
//“is 用来检查实例是否符合某个协议,若符合则返回 true,否则返回 false。”
//“as? 返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回 nil。”
//“as! 将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误。”

protocol HasArea{
    var area:Double {get}
}
class Circle:HasArea{
    let pi = 3.1415926
    var radius : Double
    var area: Double{
        return pi*radius*radius
    }
    init(radius:Double) {
        self.radius = radius
    }
}
class Country: HasArea {
    var area: Double
    init(area:Double) {
        self.area = area
    }
}
class Animal {
    var legs : Int
    init(legs:Int) {
        self.legs = legs
    }
}
let objects : [AnyObject] = [
    Circle(radius:2.0),
    Country(area:243_610),
    Animal(legs:4),
]
for object in objects {
    if let objectWithArea = object as? HasArea {
        print("Area is \(objectWithArea.area)")
    }else{
        print("Something that doesn't habe an area")
    }
}
//打印 Area is 12.5663704
//Area is 243610.0
//Something that doesn't habe an area

//“当迭代出的元素符合 HasArea 协议时,将 as? 操作符返回的可选值通过可选绑定,绑定到 objectWithArea 常量上。objectWithArea 是 HasArea 协议类型的实例,因此 area 属性可以被访问和打印。”
//“objects 数组中的元素的类型并不会因为强转而丢失类型信息,它们仍然是 Circle,Country,Animal 类型。然而,当它们被赋值给 objectWithArea 常量时,只被视为 HasArea 类型,因此只有 area 属性能够被访问。”

// 可选的协议要求
//“协议可以定义可选要求,遵循协议的类型可以选择是否实现这些要求。在协议中使用 optional 关键字作为前缀来定义可选要求。可选要求用在你需要和 Objective-C 打交道的代码中。协议和可选要求都必须带上@objc属性。标记 @objc 特性的协议只能被继承自 Objective-C 类的类或者 @objc 类遵循,其他类以及结构体和枚举均不能遵循这种协议”
//“使用可选要求时(例如,可选的方法或者属性),它们的类型会自动变成可选的。比如,一个类型为 (Int) -> String 的方法会变成 ((Int) -> String)?。需要注意的是整个函数类型是可选的,而不是函数的返回值”
//“协议中的可选要求可通过可选链式调用来使用,因为遵循协议的类型可能没有实现这些可选要求。类似 someOptionalMethod?(someArgument) 这样,你可以在可选方法名称后加上 ? 来调用可选方法”

@objc protocol CounterDataSource{
    @objc optional func incrementForCount(count:Int)->Int
    @objc optional var fixedIncrement:Int {get}
}
class Counter {
    var count = 0
    var dataSourece : CounterDataSource?
    func increment(){
        if let amount = dataSourece?.incrementForCount?(count: count) {
            count += amount
        }else if let amount = dataSourece?.fixedIncrement{
            count += amount
        }
    }
}

//“这里使用了两层可选链式调用。首先,由于 dataSource 可能为 nil,因此在 dataSource 后边加上了 ?,以此表明只在 dataSource 非空时才去调用 increment(forCount:) 方法。其次,即使 dataSource 存在,也无法保证其是否实现了 increment(forCount:) 方法,因为这个方法是可选的。因此,increment(forCount:) 方法同样使用可选链式调用进行调用,只有在该方法被实现的情况下才能调用它,所以在 increment(forCount:) 方法后边也加上了 ?。”

class ThreeSource: NSObject,CounterDataSource{
    let fixedIncrement = 3
}
var counter = Counter()
counter.dataSourece=ThreeSource()
for _ in 1...4{
    counter.increment()
    print(counter.count)
}
//打印 3
//6
//9
//12
@objc class TowardsZeroSource:NSObject,CounterDataSource{
    func incrementForCount(count: Int) -> Int {
        if count == 0 {
            return 0
        }else if count < 0 {
            return 1
        }else {
            return -1
        }
    }
}
counter.count = -4
counter.dataSourece = TowardsZeroSource()
for _ in 1...5{
    counter.increment()
    print(counter.count)
}
//打印-3
//-2
//-1
//0
//0

//13.协议扩展
//“协议可以通过扩展来为遵循协议的类型提供属性、方法以及下标的实现。通过这种方式,你可以基于协议本身来实现这些功能,而无需在每个遵循协议的类型中都重复同样的实现,也无需使用全局函数。”

extension RandomNumberGennerator {
    func randomBool() -> Bool {
        return random() > 0.5
    }
}

//“通过协议扩展,所有遵循协议的类型,都能自动获得这个扩展所增加的方法实现,无需任何额外修改:”

let generator = LinearCongruentialGenerator()
print("here si a random Number: \(generator.random())")
//打印 here si a random Number: 0.37464991998171
print("and here is a random boolean:\(generator.randomBool())")
//打印 and here is a random boolean:true

//13.1 提供默认实现
// “可以通过协议扩展来为协议要求的属性、方法以及下标提供 默认的实现 。如果遵循协议的类型为这些要求提供了自己的实现,那么这些自定义实现将会替代扩展中的默认实现被使用。”
//“注意 通过协议扩展为协议要求提供的默认实现和可选的协议要求不同。虽然在这两种情况下,遵循协议的类型都无需自己实现这些要求,但是通过扩展提供的默认实现可以直接调用,而无需使用可选链式调用。

extension PrettyTextRepresentable{
    var prettyTextualDescrition : String{
        return textualDescription
    }
}

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

//“例如,你可以扩展 CollectionType 协议,但是只适用于集合中的元素遵循了 TextRepresentable 协议的情况”

//extension Collection  where Generator.Element : TextReprresentable{
//    var textualDescription : String{
//        let itemsAsText = self.map{$0.textualDescription}
//        return "[" + itemsAsText.joinWithSeparator(",") + "]"
//    }
//}
//Array
//let murrayTheHamster = Hamster(name:"Murray")
//let morgenTheHamster = Hamster(name:"Morgan")
//let hamsters = [murrayTheHamster,morgenTheHamster]
//print(hamsters.textualDescription)
//应该打印“[A hamster named Murray, A hamster named Morgan]” 但是这里一直走不通
上一篇 下一篇

猜你喜欢

热点阅读