• 学习Swift -- 协议(上)


    协议(上)

    协议是Swift非常重要的部分,协议规定了用来实现某一特定工作或者功能所必需的方法和属性。类,结构体或枚举类型都可以遵循协议,并提供具体实现来完成协议定义的方法和功能。任意能够满足协议要求的类型被称为遵循这个协议。

    protocol SomeProtocol {
        // 协议内容
    }
    
    struct SomeStruct: SomeProtocol {
        // 结构体和枚举都可以遵循协议,写法以 ": 协议名"为准 多个协议名以逗号分隔
    }
    
    class SomeClass {
        
    }
    
    class SubClass: SomeClass, SomeProtocol {
        // 如果一个类要继承一个父类 并且遵循一个协议 写法以 ": 父类名, 协议",注意 父类名在前,协议在后
    }
    

    对属性的规定

    协议可以规定其遵循者提供特定名称和类型的实例属性类属性,而不指定是存储型属性还是计算型属性。此外还必须指明是只读的还是可读可写的。

    如果协议规定属性是可读可写的,那么这个属性不能是常量或只读的计算属性。如果协议只要求属性是只读的,那个属性不仅可以是只读的,如果你代码需要的话,也可以是可写的。

    协议中的通常用var来声明属性,在类型声明后加上{ set get }来表示属性是可读可写的,只读属性则用{ get }来表示。

    在协议中定义类属性时,总是使用static关键字作为前缀。当协议的遵循者是类时,可以使用classstatic关键字来声明类属性,但是在协议的定义中,仍然要使用static关键字。

    protocol SomeProtocol {
        var mustBeSettable: Int { get set }         // 可读可写的属性
        var doesNotNeedToBeSettable: Int { get }    // 只读属性
    }
    
    protocol AnotherProtocol {
        static var someTypeProperty: Int { get set }    // 类型属性
    }
    

    下面是一个遵循FullyNamed协议的简单结构体。

    protocol FullyNamed {
        var fullname: String { get }
    }
    
    struct Person: FullyNamed {     // 遵循了FullyNamed协议
        var fullname: String        // 遵循了协议,必须声明协议规定的属性 fullname
    }
    
    let Alex = Person(fullname: "Alex")
    

    下面是一个比较复杂的类

    protocol FullyNamed {
        var fullname: String { get }
    }
    
    class Starship: FullyNamed {
        var fullname: String {      // 把FullyNamed的属性声明为了只读计算属性
            return (prefix != nil ? prefix! + " " : "") + name
        }
        
        var prefix: String?
        var name: String
        init(name: String, prefix: String? = nil) {
            self.name = name
            self.prefix = prefix
        }
    }
    
    let ncc1701 = Starship(name: "Enterprise", prefix: "USS")
    ncc1701.fullname    // USS Enterprise
    

    对方法的规定

    协议可以要求其遵循者实现某些指定的实例方法或类方法。这些方法作为协议的一部分,像普通的方法一样放在协议的定义中,但是不需要大括号和方法体。可以在协议中定义具有可变参数的方法,和普通方法的定义方式相同。但是在协议的方法定义中,不支持参数默认值。

    正如对属性的规定中所说的,在协议中定义类方法的时候,总是使用static关键字作为前缀。当协议的遵循者是类的时候,虽然你可以在类的实现中使用class或者static来实现类方法,但是在协议中声明类方法,仍然要使用static关键字。

    protocol SomeProtocol {
        static func someTpyeMethod()
    }
    
    protocol RandomNumberGenerator {
        func random() -> Double
    }
    

    例子:

    protocol RandomNumberGenerator {
        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) % m
            return lastRandom / m
        }
    }
    
    let generator = LinearCongruentialGenerator()
    print("Here's a random number :(generator.random())")
    // 打印出 : "Here's a random number :0.37464991998171"
    print("Another one: (generator.random())")
    // 打印出 : "Another one: 0.729023776863283"
    

    对Mutating方法的规定

    有时需要在方法中改变它的实例。例如,值类型(结构体,枚举)的实例方法中,将mutating关键字作为函数的前缀,写在func之前,表示可以在该方法中修改它所属的实例及其实例属性的值。

    注意:用类实现协议中的mutating方法时,不用写mutating关键字;用结构体,枚举实现协议中的mutating方法时,必须写mutating关键字。

    protocol SomeProtocol {
        mutating func mutatingFunc()
    }
    
    struct SomeStruct: SomeProtocol {
        var number = 2
        mutating func mutatingFunc() {
            number = 100
        }
    }
    
    class SomeClass: SomeProtocol {
        var number = 1
        func mutatingFunc() {
            number = 100
        }
    }
    

    例子:

    protocol Togglable {
        mutating func toggle()
    }
    
    enum OnOffSwitch: Togglable {
        case Off, On
        mutating func toggle() {
            switch self {
            case On:
                self = .Off
            case Off:
                self = .On
            }
        }
    }
    
    var lightSwitch = OnOffSwitch.On
    lightSwitch.toggle()            // lightSwitch 现在为Off
    

    对构造器的规定

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

    protocol SomeProtocol{
        init(someParameter: Int)
    }
    

    协议构造器在类中的实现

    你可以在遵循该协议的类中实现构造器,并指定其为类的指定构造器或者便利构造器。在这两种情况下,你都必须给构造器实现标上"required"修饰符。

    注意:如果类已经被标记为final,那么不需要在协议构造器的实现中使用required修饰符。因为final类不能有子类

    class SomeClass: SomeProtocol {
        required init(someParameter: Int) {     // 必须加required关键字
            // 构造过程
        }
    }
    
    final class FinalClass: SomeProtocol {
        init(someParameter: Int) {              // 因为被标记为final,所以可以不用加required关键字
            // 构造过程
        }
    }
    
    protocol SomeProtocol{
        init()
    }
    
    class SomeSuperClass{
        init() {
            
        }
    }
    
    class SomeSubClass: SomeSuperClass, SomeProtocol {
        // 因为遵循协议,需要加上"required", 因为继承自父类,需要加上"override"
        required override init() {
            
        }
    }
    

    可失败构造器的规定

    可以通过给协议Protocols中添加可失败构造器来使遵循该协议的类型必须实现该可失败构造器。

    如果在协议中定义一个可失败构造器,则在遵顼该协议的类型中必须添加同名同参数的可失败构造器或非可失败构造器。如果在协议中定义一个非可失败构造器,则在遵循该协议的类型中必须添加同名同参数的非可失败构造器或隐式解析类型的可失败构造器(init!)。

    protocol SomeProtocol{
        init?()
        init(someParameter: Int)
    }
    
    class SomeClass: SomeProtocol{
        required init() {   // 或 init?()
            
        }
    
        required init!(someParameter: Int) {    // 或init(someParameter: Int)
            
        }
    }
    

    协议类型

    尽管协议本身并不实现任何功能,但是协议可以被当做类型来使用。

    协议可以像其他普通类型一样使用,使用场景:

    • 作为函数、方法或构造器中的参数类型或返回值类型
    • 作为常量、变量或属性的类型
    • 作为数组、字典或其他容器中的元素类型

    注意:协议是一种类型,因此协议类型的名称应与其他类型(Int,Double,String)的写法相同,使用大写字母开头的驼峰式写法。

    protocol RandomNumberGenerator {
        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) % m)
            return lastRandom / m
        }
    }
    
    class Dice {
        let sides: Int
        let generator: RandomNumberGenerator     // 属性类型可以用协议类型
        init(sides: Int, generator: RandomNumberGenerator){    // 参数的类型也可以是协议类型
            self.sides = sides
            self.generator = generator
        }
        func roll() -> Int {
            return Int(generator.random() * Double(sides)) + 1
        }
    }
    
    var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
    d6.roll()   // 3
    d6.roll()   // 5
    d6.roll()   // 4
    

    委托(代理)模式

    委托是一种设计模式,它允许结构体将一些需要它们负责的功能交由(委托)给其他的类型的实例。委托模式的实现很简单: 定义协议来封装那些需要被委托的函数和方法, 使其遵循者拥有这些被委托的函数和方法。委托模式可以用来响应特定的动作或接收外部数据源提供的数据,而无需要知道外部数据源的类型信息。

    protocol RandomNumberGenerator {
        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) % m)
            return lastRandom / m
        }
    }
    
    class Dice {
        let sides: Int
        let generator: RandomNumberGenerator
        init(sides: Int, generator: RandomNumberGenerator){
            self.sides = sides
            self.generator = generator
        }
        func roll() -> Int {
            return Int(generator.random() * Double(sides)) + 1
        }
    }
    
    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)
    }
    
    class SnakesAndLadders: DiceGame {
        // 协议只要求dice为只读的,因此将dice声明为常量属性。
        let dice: Dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
        let finalSquare = 25
        var square = 0
        var board: [Int]
        init() {
            board = Array(count: finalSquare, repeatedValue: 0)
            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: DiceGame) {
            // 游戏开始 做一些想做的事情
        }
        func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
            // 游戏进行中
            numberOfTurns += 1
            print("Rolled a (diceRoll)")
        }
        func gameDidEnd(game: DiceGame) {
            // 游戏结束
            print("The game lasted for (numberOfTurns) turns")
        }
    }
    
    let tracker = DiceGameTracker()
    let game = SnakesAndLadders()
    game.delegate = tracker
    game.play()
    //Rolled a 3
    //Rolled a 5
    //Rolled a 4
    //Rolled a 5
    //"The game lasted for 4 turns"
    
  • 相关阅读:
    一道编程题: 在1~n之间选择若干个数,使其和为m
    关于raft算法
    程序员算法基础——动态规划
    c++中两个类互相引用的问题
    c++ 之模板进阶
    jmeter分布式操作-远程启动功能探索
    linux下安装不同版本的jdk
    Jmeter插件监控服务器性能
    测试开发面试-技术持续累积
    python:Jpype安装和使用
  • 原文地址:https://www.cnblogs.com/Alex-sk/p/5540457.html
Copyright © 2020-2023  润新知