学习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"
时间: 2024-11-04 07:31:15

学习Swift -- 协议(上)的相关文章

TPC协议学习总结(上)

在计算机领域,数据的本质无非0和1,创造0和1的固然伟大,但真正百花齐放的还是基于0和1之上的各种层次之间的组合(数据结构)所带给我们人类各种各样的可能性.例如TCP协议,我们的生活无不无时无刻的站在TCP协议这个"巨人"的肩膀上,最简单的一个打开手机的动作.所以对TCP的认识和理解,可谓越来越常识化. TCP/IP五层协议 虽然TCP是一种计算机网络协议,但本质还是人与人之间的一种约定,只不过由计算机去执行而已,把协议的细节与作用解耦,让我们人类只需专注于基于它的应用呈现之上即可.协

Swift 协议(protocol)和扩展(extension)

协议 Swift 使用protocol定义协议: 1 protocol ExampleProtocol { 2 var simpleDescription: String { get } 3 mutating func adjust () 4 } 类型.枚举和结构都可以实现(adopt)协议: 1 class SimpleClass: ExampleProtocol { 2 var simpleDescription: String = "A very simple class." v

关于学习Swift的一些感受

最近准备开始学习Swift.之前计划的是一个月内要能写出一个简单的APP出来.毕竟我只有晚上下班了有点时间. 因为从网上看的或者听别人说的,都说如果会OC,Swift学习也就一两个星期的事情.然后我就当真了.. 然后,开始看了一个星期的官方文档,照着文档敲例子还行,敲玩就忘了..我以为是因为看的英文的原因. 然后,又开始拿中文翻译文档,准备从头开始看一遍,结果还是一样的,看了一半文档,发现前面的又忘了.. 不得不说我的记忆力确实太渣了. 今天周末,下定决心不看文档了,直接从github上找了几个

如何学习Swift可空链式调用

今天我们一起来学习Swift可空链式调用.可空链式调用是一种可以请求和调用属性.方法以及下标的过程,它的可空体现在请求或调用的目标当前可能为nil.如果可空的目标有值,即调用就会成功:如果选择的目标为nil,即调用将返回nil.多个连续的调用可以被链接在一起形成一个调用链,如果其中任何一个节点为nil将导致整个链调用失败.    一.使用可空链式调用来强制展开    可空链的定义,即是在要调用非空的属性.方法.下标的可空值后面添加一个问号即可.特别的,可空链式调用的返回结果与原本的返回结果具有相

Swift学习——Swift基础详解(一)

注:由于基础部分在Swift Tour 中已经大体的说明了,所以在详解中不会达到100%的原文释义 Constants and Variables  常量和变量 常量和变量都需要声明名称和类型(作为程序员,这些基础也就不说了),常量一次赋值不能改变,变量的值可以改变 Declaring Constants and Variables   声明常量和变量 常量和变量在使用之前必须要声明,使用let关键字定义常量,var关键字定义变量 下面的例子可以用来定义用户登录的时候最大的尝试次数: let m

Swift学习——Swift基础详解(二)

上节说了没有营养的变量和常量,这玩意,都差不多,自己稍微看下就好了 Integers    整型 整数就是整数了,没有小数,整数有符号(+,-,0)或者无符号(0,+) Swift提供了8,16,32,64位的有符号和无符号的整数,命名使用C的方式,比如,8位无符号的整型UInt8,32位有符号的整型就是Int32 Integer Bounds    整型范围 可以使用min 和 max获取整数类型的最大值和最小值 let minValue = UInt8.min // minValue is

【802.3ad学习】LAG上的协商

选择一个LAG 每个端口都会被LAG上的membership选择,每个membership是由设备上唯一的LAG ID进行标识的 43.3.7 Selecting a Link Aggregation Group Each port is selected for membership in the Link Aggregation Group uniquely identified by the LAG ID (composed of operational information, both

Swift学习——Swift基础详解(八)

Assertions    断言 可选可以让你判断值是否存在,你可以在代码中优雅地处理值缺失的情况.然而,在某些情况下,如果值缺失或者值并不满足特定的条件,你的代码可能并不需要继续执行.这时,你可以在你的代码中触发一个断言(assertion)来结束代码运行并通过调试来找到值缺失的原因. Debugging with Assertions    使用断言进行调试 断言会在运行时判断一个逻辑条件是否为true.从字面意思来说,断言"断言"一个条件是否为真.你可以使用断言来保证在运行其他代

Swift学习——Swift基础详解(六)

Optionals    选配 选配(好像不是这么翻译的)适用于那些值可能为空的情况,一个选配有两种情况:存在值并且等于x,或者值不存在. 选配的概念在OC和C里面并没有,在OC中最接近的概念就是:OC中的返回值为对象的函数,当对象不存在的时候,可以返回nil,但是nil只能代表对象,不可以代表基础类型或者结构体和枚举类型.OC中使用NSNotFound表示值不存在.在Swift中,不需要使用其他的类型表示不存在的值. 举个例子: 在Swift中,String类型有一个方法叫做toInt,但是并