Swift基础入门知识学习(24)-协议(协定)-讲给你懂

Swift基础入门知识学习(24)-协议(协定)-讲给你懂,第1张

TED演讲的8个秘诀:学习18分钟高效表达-重点笔记

Swift基础入门知识学习(23)-扩展-讲给你懂

目录 协议语法属性的规则方法的规则变异方法的规则 构造器的规则类别中实例协议的构造器可失败构造器的规则协议为一种类型 委托模式为扩展添加协议经由扩展遵循协议 协议类型的集合协议的继承只用于类别的协议 协议合成检查协议可选协议的规则协议扩展为协议扩展添加限制条件


理解难度
★★★★★
实用程度
★★☆☆☆

协议(protocol)是 Swift 一个重要的特性,它会定义出为了完成某项任务或功能所需的方法、属性,协议本身不会实例这些任务跟功能,而仅仅只是表达出该任务或功能的名称。这些功能则都交由遵循协议的类型来实例,枚举、结构体及类别都可以遵循协议,遵循协议表示这个类型必须实例出协议定义的方法、属性或其他功能。

有点像是协议定义出一个To Do List,而所有遵循这个协议的类型都必须照表 *** 课,将需要的功能都实例出来。

协议语法

使用protocol关键字来定义一个协议,格式如下:

protocol 协议名称 {
    协议定义的内容
}

要让自定义的类型遵循协议,写法有点像继承,一样是把协议名称写在冒号:后面,而要遵循多个协议时,则是以逗号 , 分隔每个协议,格式如下:

struct 自定义的结构体名称: 协议, 另一个协议 {
    结构体的内容
}
属性的规则

协议不能定义一个属性是储存属性或计算属性,而只是定义属性的名称及是实例属性或类型属性。此外还可以定义属性是唯读或是可读写的。

协议定义属性是可读写时,则遵循协议的类型定义的属性不能是常量属性或唯读的计算属性。协议定义属性是唯读时,则遵循协议的类型定义的属性可以是唯读或依照需求改定义为可读写。

协议使用 var 关键字来定义变量属性,在类型标注后加上{ get set }来表示是可读写的,唯读则是使用{ get }来表示,如下:

protocol 协议 {
    var 可读写变量: 类型 { get set }
    var 唯读变量: 类型 { get }
}

在协议中定义类型属性时,必须在前面加上static关键字。而当一个类别遵循这个协议时,除了static还可以使用class关键字来定义类别的这个类型属性:

protocol 协议 {
    static var 类型属性: 属性类型 { get set }
}

底下是一个例子:

// 定义一个协议 包含一个唯读的字符串属性
protocol MyFullyNamed {
    var fullName: String { get }
}

// 定义一个类别 遵循协议 MyFullyNamed
struct Person: MyFullyNamed {
    // 因为遵循协议 MyFullyNamed
    // fullName 这个属性一定要定义才行 否则会报错误
    var fullName: String
}

let joe = Person(fullName: "Black")
print("名字为 \(joe.fullName)")
// 打印出:名字为 Black
方法的规则

协议可以定义实例方法或类型方法以供遵循,而这些方法不需要大括号 { } 以及其内的内容(即不需要实例),而实例则是交给遵循协议的类型来做。

协议可以定义含有可变量量参数(variadic parameter)的方法。协议不能为方法的参数提供预设值。

与属性的规则一样,协议中要定义类型方法时,必须在前面加上 static 关键字。而当一个类别遵循这个协议时,除了static还可以使用class关键字来定义类别的这个类型方法。

协议定义方法的格式如下:

protocol OneProtocol {
    // 定义一个类型方法
    static func someTypeMethod()

    // 定义一个实例方法
    func instanceMethod() -> Double

    // 协议定义方法皆不需要大括号 {} 及其内内容
}

底下是一个例子:

protocol OneProtocol {
    // 定义一个实例方法 返回一个整数
    func instanceMethod() -> Int
}

// 定义一个类别 遵循协议 OneProtocol
class MyClass: OneProtocol {
    // 因为遵循协议 OneProtocol
    // instanceMethod() 这个方法一定要定义才行 否则会报错误
    func instanceMethod() -> Int {
        return 300
    }
}
变异方法的规则

使用 mutating 关键字放在 func 关键字前来定义变异方法(变异方法表示可以在方法中修改它所属的实例以及实例的属性的值)。

遵循一个包含变异方法的协议时,枚举跟结构体定义时必须加上mutating关键字,而类别定义时则不用加上。

底下是一个例子:

// 定义一个包含变异方法的协议
protocol Togglable {
    // 只需标明方法名称 不用实例
    mutating func toggle()
}

// 定义一个开关切换的枚举
enum OnOffSwitch: Togglable {
    case off, on

    // 实例这个遵循协议后需要定义的变异方法
    mutating func toggle() {
        // 会在 On, Off 两者中切换
        switch self {
        case .off:
            self = .on
        case .on:
            self = .off
        }
    }
}

var lightSwitch = OnOffSwitch.off
lightSwitch.toggle()
// lightSwitch 现在切换为 .on
构造器的规则

协议可以定义一个构造器,与定义方法一样不需要写大括号 { } 及其内的内容,格式如下:

protocol OtherProtocol {
    init(someParameter: Int)
}
类别中实例协议的构造器

如果是一个类别遵循一个含有构造器的协议时,无论是指定构造器或便利构造器,都必须为类别的构造器加上 required 修饰符,以确保所有子类也必须定义这个构造器,从而符合协议(如果类别已被加上final,则不需要为其内的构造器加上 required,因为 final 类别不能再被子类继承),如下:

class OtherClass: OtherProtocol {
    required init(someParameter: Int) {
        // 构造器的内容
    }
}

如果一个子类覆写了父类的指定构造器,且此构造器满足了某个协议的要求,则该构造器必须同时加上required和override,如下:

// 定义一个协议
protocol AnontherProtocol {
    init()
}

// 定义一个类别
class AnontherSuperClass {
    init() {
        // 构造器的内容
    }
}

// 定义一个继承 AnontherSuperClass 的类别
// 同时还遵循了协议 AnontherProtocol
class SomeSubClass: AnontherSuperClass, AnontherProtocol {
    // 必须同时加上 required 和 override
    required override init() {
        // 构造器的内容
    }
}
可失败构造器的规则

协议可以定义可失败构造器:

一个协议包含可失败构造器时,遵循这个协议的类型,可以使用可失败构造器( init? )或非可失败构造器( init )来定义这个构造器。
一个协议包含非可失败构造器时,遵循这个协议的类型,可以使用隐式解析可失败构造器( init! )或非可失败构造器( init )来定义这个构造器。

协议为一种类型

虽然协议本身没有实例任何功能,但协议仍可以被当做一种类型来使用。使用情况就如同一般类型一样,如下:

作为函式、方法或构造器的参数类型或返回值类型。作为常量、变量或属性的类型。作为数组、字典或其他集合中的元素类型。协议习惯的命名方式就如同一般类型一样使用大写字母开头的大驼峰式命名法。

底下为一个例子:

// 定义一个协议
protocol SomeOtherProtocol {
    func method() -> Int
}

// 定义一个类别 遵循协议 SomeOtherProtocol
class OneClass: SomeOtherProtocol {
    func method() -> Int {
        return 5566
    }
}

// 定义另一个类别 有一个类型为 SomeOtherProtocol 的常量
class AnotherClass {
    // 常量属性 类型为一个协议:SomeOtherProtocol
    let oneMember: SomeOtherProtocol

    // 构造器有个参数 member 类型为 SomeOtherProtocol
    init(member: SomeOtherProtocol) {
        self.oneMember = member
    }

}

// 先声明一个类别 OneClass 的实例
let oneInstance = OneClass();

// 任何遵循 协议:SomeOtherProtocol 的实例
// 都可以被当做 协议:SomeOtherProtocol 类型
// 所以上面声明的 oneInstance 可以被当做参数传入
let twoInstance = AnotherClass(member: oneInstance)

// 打印出:5566
print(twoInstance.oneMember.method())

由上面的代码可以知道,任何遵循一个协议的实例,都可以被当做这个协议类型的值。

委托模式

委托(delegation)是一种设计模式,它允许类别或结构体将一些需要它们负责的功能委托给其他类型的实例。

委托模式的实例就是定义协议来封装那些需要被委托的功能,而遵循这个协议的类型就能提供这些功能。委托模式可以用来回应特定的动作或是接收外部资料,而不需要知道外部资料的类型。

以下是一个例子:

// 定义一个协议 遵循这个协议的类别都要实例 attack() 方法
protocol GameCharacterProtocol {
    func attack()
}

// 定义一个委托协议 将一些其他功能委托给别的实例实例
protocol GameCharacterProtocolDelegate {
    // 这边是定义一个在攻击后需要做的整理工作
    func didAttackDelegate()
}

// 定义一个类别 表示一个游戏角色 
class GameCharacter: GameCharacterProtocol {
    // 首先定义一个变量属性 delegate
    // 类型为 GameCharacterProtocolDelegate
    // 定义为可选类型 会先初始化为 nil 之后
    // 再将其设置为负责其他动作的另一个类型的实例
    var delegate: GameCharacterProtocolDelegate?

    // 因为遵循协议:GameCharacterProtocol
    // 所以需要实例 attack() 这个方法
    func attack() {
        print("攻击!")

        // 最后将其他动作委托给另一个类型的实例实例
        delegate?.didAttackDelegate()
    }
}

// 定义一个类别 遵循协议:GameCharacterProtocolDelegate
// 这个类别生成的实例会被委托其他动作
class GameCharacterDelegate: GameCharacterProtocolDelegate {
    // 必须实例这个方法
    func didAttackDelegate() {
        print("攻击后的后续工作")
    }
}

// 首先生成一个游戏角色的实例
let oneChar = GameCharacter()

// 接着生成一个委托类别的实例 要负责其他的动作
let charDelegate = GameCharacterDelegate()

// 将游戏角色的 delegate 属性设为委托的实例
oneChar.delegate = charDelegate

// 接着呼叫攻击方法
oneChar.attack()
// 会依序打印出:
// 攻击!
// 攻击后的后续工作
为扩展添加协议

你也可以让扩展遵循协议,这样就可以在不修改原始代码码的情况下,让已存在的类型经由扩展来遵循一个协议。当已存在类型经由扩展遵循协议时,这个类型的所有实例也会随之获得协议中定义的功能。

// 定义另一个协议 增加一个防御方法 defend
protocol GameCharacterDefend {
    func defend()
}

// 定义一个扩展 会遵循新定义的协议 GameCharacterDefend
extension GameCharacter: GameCharacterDefend {
    // 必须实例这个方法
    func defend() {
        print("防御!")
    }
}

// 使用前面生成的实例 oneChar
// 这样这个被扩展的类别生成的实例 也随即可以使用这个方法
oneChar.defend()
// 打印出:防御!
经由扩展遵循协议

当一个类型已经符合某个协议的所有要求,但却没有在类型的定义中声明时,可以经由一个空的扩展来遵循这个协议,以下是个例子:

// 定义一个协议
protocol NewProtocol {
    var name: String { get set }
}

// 定义一个类别 满足了[协议 NewProtocol]的要求 但尚未遵循它
class NewClass {
    var name = "good day"
}

// 这时可以使用扩展来遵循
extension NewClass: NewProtocol {}

即使满足了协议的所有要求,类型也不会自动遵循协议,必须显式地为它加上遵循协议才行。

协议类型的集合

协议类型也可以作为数组或字典内成员的类型,以下是个例子:

// 生成另外两个实例
let twoChar = GameCharacter()
let threeChar = GameCharacter()

// 声明一个类型为 [GameCharacterProtocol] 的数组
let team:[GameCharacterProtocol]=[oneChar,twoChar,threeChar]

// 因为都遵循这个协议 所以这个 attack() 方法一定存在可以呼叫
for member in team {
    member.attack()
}
协议的继承

协议也可以像类别一样继承另外一个或多个协议,可以在继承的协议的基础上增加新的功能,继承的多个协议一样是使用逗号,隔开,如下:

protocol 新协议: 继承的协议, 继承的另一个协议 {
    协议新增加的功能
}
只用于类别的协议

你可以在协议的继承列表中,增加关键字 class 来限制这个协议只能被类别类型遵循,而枚举跟结构体不能遵循这个协议。class 必须摆在继承列表的第一个,在其他要继承的协议之前,如下:

protocol 只用于类别的协议: class, 其他要遵循的协议 {
    只用于类别的协议的功能
}
协议合成

如果你需要一个类型同时遵循多个协议,你可以将多个协议使用 OneProtocol & AnotherProtocol 这种格式组合起来,这种方式称为协议合成( protocol composition )。你可以填入多个需要遵循的协议,并以符号 & 隔开,如下:

// 定义一个协议 有一个 name 属性
protocol Named {
    var name: String { get }
}

// 定义另一个协议 有一个 age 属性
protocol Aged {
    var age: Int { get }
}

// 定义一个结构体 遵循上面两个定义的协议
struct OnePerson: Named, Aged {
    var name: String
    var age: Int
}

// 定义一个函式 有一个参数 定义为遵循这两个协议的类型
// 所以写成 : Named & Aged 格式
func wishHappyBirthday(to celebrator: Named & Aged) {
    print("谢谢确认。 \(celebrator.name)")
    print("目前年龄\(celebrator.age) 岁!")
}

let birthdayPerson = OnePerson(name: "Brian", age: 25)
wishHappyBirthday(to: birthdayPerson)
// 打印出:谢谢确认。 Brian
// 目前年龄 25 岁啰!
检查协议

你可以使用前面章节提过的 is 与 as 来检查是否符合某协议或是转换到指定的协议类型,使用方式与类型检查与转换一样:

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

// 定义一个协议 有一个 area 属性 表示面积
protocol HasArea {
    var area: Double { get }
}

// 定义一个圆的类别 遵循协议:HasArea 所以会有 area 属性
class Circle: HasArea {
    var area: Double
    init(radius: Double) { self.area = 3.14 * radius * radius }
}

// 定义一个国家的类别 遵循协议:HasArea 所以会有 area 属性
class Country: HasArea {
    var area: Double
    init(area: Double) { self.area = area }
}

// 定义一个动物的类别 没有面积
class Animal {
    var legs: Int
    init(legs: Int) { self.legs = legs }
}

// 以上三个类别的实例都可以作为 [AnyObject] 数组的成员
let objects: [AnyObject] = [
    Country(area: 243610),
    Circle(radius: 2.0),
    Animal(legs: 4)
]

// 遍历这个数组
for object in objects {
    // 使用可选绑定来将成员绑定为 HasArea 的实例
    if let objectWithArea = object as? HasArea {
        // 符合协议 就会绑定成功 也就可以取得 area 属性
        print("面积为 \(objectWithArea.area)")
    } else {
        // 不符合协议 则是返回 nil
        print("没有面积!")
    }
}

// 依序打印出:
// 面积为 243610.0
// 面积为 12.56
// 没有面积!
可选协议的规则

要在协议里定义一个可选的功能(像是属性或方法),必须在这个功能前面加上optional关键字。

如果将功能变为可选后,它们的类型会自动变成可选的。像是一个类型为 (Int) -> String 的方法会变成((Int) -> String)? ,是方法的类型为可选,不是方法返回值的类型。

而这些定义为可选的功能,可以使用可选链来呼叫。

你必须在protocol前面标记@objc特性,才能让协议可以定义它的功能(像是属性或方法)为可选,且功能前面也必须标记@objc。

标记@objc特性的协议只能被继承自 Objective-C 类别的类别或@objc 类别遵循。

以下是一个例子:

// 要加上 @objc 必须引入 Foundation
import Foundation
// 这边不详细说明 因为可选协议与 Objective-C 代码语言有关系
// 而 Objective-C 大量使用到 Foundation 的功能 所以需要引入

// 定义一个可选协议 用于计数 分别有两种不同的增量值
@objc protocol CounterDataSource {
    // 定义一个可选方法 可以传入一个要增加的整数
    @objc optional func increment(forCount: Int) -> Int

    // 定义一个可选属性 为一个固定增加的整数
    @objc optional var fixedIncrement: Int { get }
}

// 定义一个遵循可选协议的类别 计数用
class CounterSource: CounterDataSource {
    // 一个经由遵循协议而拥有的可选属性 设值为 3
    // 前面必须加上 @objc
    @objc let fixedIncrement = 3

    // 不过因为是可选的 所以另一个可选方法可以不用实例 这边将其注解起来
    /*
    @objc func increment(forCount: Int) -> Int {
        return count
    }
    */

}

// 用来计数的变量
var count = 0

// 生成一个类型为可选协议:CounterDataSource 的实例
// 因为类别 CounterSource 有遵循这个协议 所以可以指派为这个类别的实例
var dataSource: CounterDataSource = CounterSource()

// 回圈跑 4 次
for _ in 1...4 {
    // 使用可选绑定
    // 首先呼叫 increment(forCount:) 方法
    // 但因为这是个可选方法 所以需要加上 ?
    // 而目前这个 increment 没有实例这个方法
    // 所以会返回 nil 也就不会执行 if 内的代码
    if let amount = dataSource.increment?(forCount: count){
        count += amount
    }
    // 接着依旧使用可选绑定 取得属性 fixedIncrement
    // 因为有设置这个属性 所以会有值 流程则会进入此 else if 内的代码
    else if let amount = dataSource.fixedIncrement{
        count += amount
    }
}

// 因为回圈跑了 4 次,每次都是加上 3 ,所以最后计为 12
// 打印出:最后计数为 12
print("最后计数为 \(count)")

上面的例子中,协议CounterDataSource的属性与方法都为可选的,因此遵循协议的类别可以都不实例这些功能,虽然逻辑上这样是可行,但最好不要写成这样都是可选功能的协议。

协议扩展

你也可以扩展一个协议来为遵循这个协议的类型新增属性、方法或下标,而不需要在每个遵循这个协议的类型内实例一样的功能。以下是个例子:

// 扩展前面定义的协议 GameCharacterProtocol
// 此协议原本只有定义一个 attack() 方法
// 这边增加一个新的方法
extension GameCharacterProtocol {
    func superAttack() {
        print("额外的攻击!")
        attack()
    }
}

// 生成一个游戏角色的实例
let member = GameCharacter()
member.delegate = GameCharacterDelegate()

// 可以直接呼叫扩展协议后新增的方法
member.superAttack()
// 依序打印出:
// 额外的攻击!
// 攻击!
// 攻击后的后续工作

由上面的代码可知,经由扩展一个协议,可以直接为属性、方法及下标建立预设的实例功能,而这些遵循协议的类型如果自己又另外实例的话,则这些自定义的实例会替代扩展中的预设实例功能。

为协议扩展添加限制条件

在扩展一个协议时,可以指定一些限制条件,当遵循协议的类型满足这些限制条件时,才能获得这个扩展的协议提供的预设实例。使用方式为在协议名称后面加上 where 语句并接着限制条件。例子如下:

// 先为协议:GameCharacterProtocol 经由扩展增加一个新的属性
extension GameCharacterProtocol {
    var description: String {
        return "成员"
    }
}

// 接着扩展 集合类型的协议:Collection 
// 且其内成员必须遵循 协议:GameCharacterProtocol
extension Collection where 
  Iterator.Element: GameCharacterProtocol {
    var allDescription: String {
        let itemsAsText = self.map { $0.description }
        return "[" + itemsAsText.joined(separator: ", ") + "]"
    }
}

// 生成三个实例并放入一个数组中
let oneMember = GameCharacter()
let twoMember = GameCharacter()
let threeMember = GameCharacter()
let myTeam = [oneMember, twoMember, threeMember]

// 因为数组定义时有遵循 协议:CollectionType
// 且其内成员都遵循 协议:GameCharacterProtocol
// 所以这个 allDescription 属性会自动获得
// 打印出:[成员, 成员, 成员]
print(myTeam.allDescription)

Swift基础入门知识学习(25)-泛型-讲给你懂

高效阅读-事半功倍读书法-重点笔记-不长,都是干货

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/996886.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存