Swift:协议专题

本文详细探讨了Swift中的协议,包括协议的使用规范、构造函数、关联值、标准库中的常见协议、协议扩展和默认实现、泛型约束以及可选协议方法。重点讲解了协议在继承、扩展和类型约束中的应用,旨在帮助开发者更好地理解和运用Swift协议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 协议初体验

强调的几点:

(1)协议里只能用var并且一定要注明是只读还是可读可写

(2)协议可以规定只被类继承,不能规定只被结构体继承

(3)即使协议中是只读的,但是我继承的类或者结构体仍然可以用var来实现,相当于在只读的基础上,多加了一个可写功能,但是并不破坏原有可读的属性,所以是允许的。反之不行,如果原来在协议里就是定义可读可写,则在继承的类或者结构体中不能用let去定义,只能用var

(4)属性可以是协议类型的,并且可以将一个实现过该协议的结构体或者类类型的实例赋值给这个属性,但是这个属性仍要优先遵循协议里面的相关约束

(5)协议也可以有构造函数,继承协议的类的构造函数一定要用required,当然特例是类前面有final

   (6) 协议的属性,继承之后必须赋初值,不管是直接赋初值也好还是以构造函数的方式也好,还是以计算型属性的方式也好。注意,如果要以计算型属性的方式来实现协议中的属性,就要看协议中是只读还是可读可写,对应设置计算型属性的getter和setter方法

具体代码如下

import Foundation

protocol Pet {
    
    var name: String {get set} // 继承他的时候不一定非要是计算型属性
    let birthPlace: String {get} // 只读
    
    func playWith()
    func fed(food: String)
}

struct Dog: Pet {
    var name: String = "Puppy" // 这里不能写let,因为协议中规定是可读可写,你这里相当于不遵守协议了
    var birthPlace = "Wuhan" // 协议里面是let,这里是var,相当于是在可读的基础上我加了一个可写
    
    func playWith() {
        print("Happy!")
    }
    
    func fed(food: String = "bone") {
        print("I love eating \(food)!")
    }
    
    mutating func changeName(newName: String) {
        name = newName
    }
}

var myDog: Dog = Dog()
var aPet: Pet = myDog // 这里和继承那一章很像,Pet类型的aPet可以存放其子类的对象

myDog.birthPlace = "Hankou" // 不报错,因为Dog结构体中是可读可写的
aPet.birthPlace = "Wuchang" // 报错,因为Pet协议中是只读的

2. 协议和构造函数

import Foundation

protocol Pet {
    
    var name: String {get set} // 继承他的时候不一定非要是计算型属性
    var birthPlace: String {get} // 只读
    
    func playWith()
    func fed(food: String)
    
    init(name: String) // 协议也可以有构造函数
}

class Animal {
    var type: String = "mammal"
}

class Dog: Animal, Pet {
    var name: String = "Puppy" // 这里不能写let,因为协议中规定是可读可写,你这里相当于不遵守协议了
    var birthPlace = "Wuhan" // 协议里面是let,这里是var,相当于是在可读的基础上我加了一个可写
    
    required init(name: String) { // 子类也必须继承
        self.name = name
    }
    
    func playWith() {
        print("Happy!")
    }
    
    func fed(food: String = "bone") {
        print("I love eating \(food)!")
    }
    
    func changeName(newName: String) {
        name = newName
    }
}

class Bird: Animal {
    var name: String = "Little bird"
    init(name: String) {
        self.name = name
    }
}

class Parrot: Bird, Pet {
    required override init(name: String) { // 这里的required是协议必须遵守的,要加required,要加override是因为构造函数和父类一样,所以要加override关键字
        super.init(name: name + " " + name)
    }
    
    func playWith() {
        print("gugugu")
    }
    
    func fed(food: String) {
        print("Thanks~")
    }
    
    let birthPlace: String = "Wuhan" // 这里没有name,是因为虽然Pet中规定了必须要有name,但是Parrot的父类已经有name了所以不需要
}

3. 举个?

import UIKit
protocol Pet {
    var name: String {get set}
}

protocol Flyable {
    var flySpeed: Double {get}
    var flyHeight: Double {get}
}

class Animal {
    
}

class Dog: Animal, Pet {
    var name = "puppy"
}

class Cat: Animal, Pet {
    var name = "Kitten"
}

class Bird: Animal, Flyable {
    var flySpeed: Double
    var flyHeight: Double
    
    init(flySpeed: Double, flyHeight: Double) {
        self.flySpeed = flySpeed
        self.flyHeight = flyHeight
    }
}

class Parrot: Bird, Pet {
    var name: String

    init(name: String, flySpeed: Double, flyHeight: Double) {
        self.name = name + " " + name
        super.init(flySpeed: flySpeed, flyHeight: flyHeight) // 子类指定的构造函数一定要用init
    }
}

class Sparrow: Bird {
    var color = UIColor.gray
}

class Vehicle {
    
}

class Plane: Vehicle, Flyable {
    var flySpeed: Double
    var flyHeight: Double
    var model: String
    
    init(model: String, flySpeed: Double, flyHeight: Double) {
        self.model = model
        self.flySpeed = flySpeed
        self.flyHeight = flyHeight
    }
}

var dog = Dog()
var cat = Cat()
var parrot = Parrot(name: "shijie", flySpeed: 10, flyHeight: 100)

let pets: [Pet] = [dog, cat, parrot]
for pet in pets { // 多态性体现,这三个都是宠物,因此可以输出他们的名字
    print(pet.name)
}

var sparrow = Sparrow(flySpeed: 15, flyHeight: 200)
var plane = Plane(model: "Boeing 747", flySpeed: 800, flyHeight: 100_000)

let flyers: [Flyable] = [parrot, sparrow, plane] // 多态性的体现
for flyer in flyers {
    print("Fly speed: ", flyer.flySpeed, "Fly height: ", flyer.flyHeight)
}

4. 关联值类型,同一种属性,可能对于不同的继承者来说需要的类型不一样,为了代码的可重用性,可以在协议里用associatedtype来表示typealias

代码如下

import UIKit

// 对Double进行扩展,增加两个计算型属性,以Weight的kg为单位
extension Double {
    typealias Weight = Double // 这句话的目的是为了语义清晰,表示我这里的g是代表中重量,人家看代码的时候也好看
    var g: Weight { // 计算型属性不能有等号 =,不然报错
        return self / 1000.0
    }
    var t: Weight {
        return self * 1000.0
    }
}

// 同理
extension Int {
    typealias Weight = Int
    var g: Weight { // 计算型属性不能有等号 =,不然报错
        return self / 1000
    }
    var t: Weight {
        return self * 1000
    }
}

protocol WeightCalculable {
    associatedtype WeightType // 关联类型,这里的类型就是WeighType,对于不同的继承者来说可能需要的类型也不一样
    var weight: WeightType {get set} // 这里设置是可读可写的,如果继承的类要用计算型属性的话,就要分别设置get方法和set方法
}

class iPhone: WeightCalculable {
    typealias WeightType = Double
    var weight: WeightType { //  协议里面设置的是可读可写的,我这里又想用计算型属性,那么就只好用getter和setter方法咯
        get {
            return 114.0.g
        }
        
        set {
            print(newValue)
        }
    }
}

class Ship: WeightCalculable {
    typealias WeightType = Int
    var weight: WeightType
    init(weight: WeightType) {
        self.weight = weight
    }
}

let ship = Ship(weight: 20_000.t)
ship.weight // 20_000_000

5. 标准库中的常用协议Equatable、Comparable和CustomStringConvertible以及仿C风格的改写

import Foundation

struct Record {
    var wins: Int
    var losses: Int
}

extension Record: Equatable { // 对于遵循Equatable协议的结构体或者类,必须要重载 ==
    
}

//  运算符重载必须放在外面,因为协议里面是放新添的东西,而这个是重载不是新添,因此不能放在里面,而且必须紧跟扩展的协议之后
func == (left: Record, right: Record) -> Bool {
    return left.wins == right.wins && left.losses == right.losses
}

extension Record: Comparable { // 遵循Comparable协议只用重载 < 就行了,加上 ==,系统自动加上剩下的比较运算符的重载
    
}
// 同理,运算符重载应该放在外面
func < (left: Record, right: Record) -> Bool {
    if left.wins != right.wins {
        return left.wins < right.wins
    } else if left.losses != right.losses {
        return left.losses > right.losses
    } else {
        return false
    }
}

extension Record: CustomStringConvertible { // 遵循CustomStringConvertible这个协议,只用重写description这个计算型属性就行了
    // 当直接print(结构体名)的时候,可以自定义输出格式,而不是系统自带的格式
    var description: String {
        return "WINS: \(wins), LOSS: \(losses)"
    }
}

// 可以通过下面的扩展来模仿C语言中if的写法
extension Int {
    public var boolValue: Bool {
        return self != 0
    }
}

let record1 = Record(wins: 10, losses: 5)
let record2 = Record(wins: 8, losses: 2)

record1 == record2
record1 >= record2
print(record1)

if record1.wins.boolValue { // 仿C风格
    print("record1起码赢了一场")
}

 

6. 扩展协议和默认实现

想强调的几点,根据个人经验总结,仅供参考,如有错误请各位大神指出,感激不尽!小弟这里也思考了很久!

(1)扩展里面的计算型属性和函数必须要有具体实现

(2)可以对一个协议进行扩展,这里扩展的内容我分为三种情况

    <1> 如果该协议(称作协议A)还继承了其他协议(称作协议B),我此时可以扩展这个协议B里面的的计算型属性和方法,即相当于实现了默认实现,那么一旦有结构体或者类继承了协议A,则可以不用显式地再实现一遍刚刚扩展里面实现过的计算型属性或者方法,但是也可以再实现一遍,介时会覆盖原有默认实现。因此,如果想在继承协议A的类或者结构体里面重写协议B的属性或者方法,那么干脆可以不在extension里面进行默认实现,反正就算实现到时候也会被覆盖,何必呢是吧。

    <2> 如果扩展的是协议里原有的方法或者属性,则属于多此一举,因为就算你扩展了,到时候在继承协议A的类或者结构体里面还是要在实现一遍,不然会报错,因此没有必要去扩展协议里面本来就存在的东西,切记!

    <3> 我们还可以扩展该协议A和协议B所没有的一些计算型属性和方法 ,这也是一种默认实现,这样和<1>一样,我们可以选择不再继承协议A的结构体或者方法中重写刚刚说的计算型属性或者方法,如果偏要实现一遍,那么会覆盖原有extension里面的默认实现。

Ps:对于情况<2>,可以这么改,既然你想默认实现,那么就没有必要在原有的protocol中进行声明,直接按情况<3>来就好了,也就是说,你不应该把你想进行默认实现的函数或者计算型属性放在原有协议里,应该直接放在扩展里。

代码如下

import Foundation

protocol Record: CustomStringConvertible {
    var wins: Int {get}
    var losses: Int {get}
    
    func winningPercent() -> Double
}

extension Record {
    
    //  情况<1>,我们也可以选择不在扩展里面进行默认实现,在继承的类或者结构体里面进行
    var description: String {
        return "WINS: \(wins), LOSSES: \(losses)"
    }
    
    // 情况<2> 多此一举,必须删掉
    func winningPerfect() -> Double {
        return Double(wins) / Double(gamePlayed)
    }
    
    // 情况<3>
    func showWins() {
        print("WE WIN \(wins) TIMES!")
    }
    
    // 情况<3>
    var gamePlayed: Int {
        return wins + losses
    }
    
    // 情况<3>
    func hhh() -> () {
        print("hhh")
    }
}

struct BaseballRecord: Record {
    
    var wins: Int
    var losses: Int

    func winningPercent() -> Double { // 不能删,就算extension里面实现了也不能删,删了就报错
        return Double(wins) / Double(gamePlayed)
    }
    
//    var description: String {
//        return "WWWWWINS: \(wins), LOSSES: \(losses)"
//    }
    
    
//    func hhh() -> () {
//        print("sss")
//    }
    
}

let base = BaseballRecord(wins: 2, losses: 1)
print(base) // 默认实现
base.hhh() // 默认实现

当然,我们还可以对已有的系统协议进行扩展

extension CustomStringConvertible {
    var descriptionWithDate: String {
        return Date().description + " " + description
    }
}
print(base.descriptionWithDate)

7. 协议与where初体验

import Foundation

protocol Record: CustomStringConvertible {
    var wins: Int {get}
    var losses: Int {get}
}

extension Record {
    var description: String {
        return "WINS: \(wins), LOSSES: \(losses)"
    }
    
    func winningPerfect() -> Double {
        return Double(wins) / Double(gamePlayed)
    }
    
    var gamePlayed: Int {
        return wins + losses
    }

}

// 有些比赛允许平局
protocol Tieable {
    var ties: Int {get}
}

// 如果某个类它即继承了Record,又遵循了平局的协议,则应该重写winningPerfect()和gamePlayed以覆盖原有的方法和计算型属性
extension Record where Self: Tieable {
    func winningPerfect() -> Double {
        return Double(wins) / Double(gamePlayed)
    }
    
    var gamePlayed: Int {
        return wins + losses + ties
    }
}

class BaseballRecord: Record {
    
    var wins: Int
    var losses: Int
    
    init(wins: Int, losses: Int) {
        self.wins = wins
        self.losses = losses
    }
}

class FootballRecord: Record, Tieable {
    var wins: Int
    var losses: Int
    var ties: Int
    
    init(wins: Int, losses: Int, ties: Int) {
        self.wins = wins
        self.losses = losses
        self.ties = ties
    }
}

let baseballRecord = BaseballRecord(wins: 5, losses: 10)
let footballRecord = FootballRecord(wins: 1, losses: 1, ties: 1)

baseballRecord.winningPerfect() // 0.3333333
footballRecord.winningPerfect() // 0.3333333

8. 协议聚合(可以理解为一种约束,表示某个变量只能继承某些协议,这样才生效)

用 & 来分隔协议

import Foundation

protocol Record: CustomStringConvertible {
    var wins: Int {get}
    var losses: Int {get}
}

extension Record {
    var description: String {
        return "WINS: \(wins), LOSSES: \(losses)"
    }
    
    func winningPerfect() -> Double {
        return Double(wins) / Double(gamePlayed)
    }
    
    var gamePlayed: Int {
        return wins + losses
    }

}

// 有些比赛允许平局
protocol Tieable {
    var ties: Int {get}
}

// 如果某个类它即继承了Record,又遵循了平局的协议,则应该重写winningPerfect()和gamePlayed以覆盖原有的方法和计算型属性
extension Record where Self: Tieable {
    func winningPerfect() -> Double {
        return Double(wins) / Double(gamePlayed)
    }
    
    var gamePlayed: Int {
        return wins + losses + ties
    }
}

protocol Prizeable {
    func isPrizeable() -> Bool
}

class BaseballRecord: Record, Prizeable {
    
    var wins: Int
    var losses: Int
    
    init(wins: Int, losses: Int) {
        self.wins = wins
        self.losses = losses
    }
    
    func isPrizeable() -> Bool {
        return winningPerfect() > 0.4
    }
    
}

class FootballRecord: Record, Tieable, Prizeable {
    var wins: Int
    var losses: Int
    var ties: Int
    
    init(wins: Int, losses: Int, ties: Int) {
        self.wins = wins
        self.losses = losses
        self.ties = ties
    }
    
    func isPrizeable() -> Bool {
        return winningPerfect() > 0.1 && wins > 2
    }
}

struct Student: Prizeable, CustomStringConvertible {
    var name: String
    var score: Int
    var description: String {
        return name
    }
    func isPrizeable() -> Bool {
        return score > 90
    }
    init(name: String, score: Int) {
        self.name = name
        self.score = score
    }
}

// 协议聚合,这里表示one必须要继承Prizeable和CustomStringConvertible两个协议才能生效
func award(_ one: Prizeable & CustomStringConvertible) {
    if one.isPrizeable() {
        print(one.description)
        print("You are very good!")
    } else {
        print(one.description)
        print("you are so bad!")
    }
}

let baseballRecord = BaseballRecord(wins: 5, losses: 10)
let footballRecord = FootballRecord(wins: 1, losses: 1, ties: 1)
let student = Student(name: "shijie", score: 100)

award(baseballRecord)
award(footballRecord)
award(student)

9. 泛型约束

小tips:虽然我们说协议可以当做是一种类型,但是也有特例,下面的协议就不能当做一种类型。

<1> Comparable这个协议不能被当做一种类型,因为Comparable里面的左右两边是Self这种类型,而我们知道Self是一种类型,这相当于自己调用自己,Swift不允许这样。

<2> 如果一个协议里面有关联值,即含有关键字asscoiatedtype,那么也不能当做是一种类型,因为属性的返回值类型都不确定。

那么怎么解决呢?只能通过泛型约束来解决。

?如下

import Foundation

protocol Prizable {
    func isPrizable() -> Bool
}

struct Student: Prizable, CustomStringConvertible, Equatable, Comparable {
    var name: String
    var score: Int
    var description: String {
        return name
    }
    func isPrizable() -> Bool {
        return score > 90
    }
    init(name: String, score: Int) {
        self.name = name
        self.score = score
    }
}

func == (left: Student, right: Student) -> Bool {
    return left.score == right.score
}

func < (left: Student, right: Student) -> Bool {
    return left.score < right.score
}

func getTopOne<T: Comparable>(seq: [T]) -> T { // 返回值也是一个可比较的类型
    assert(!seq.isEmpty)
    return seq.reduce(seq[0]) {
        (a: T, b: T) -> T in
        return max(a, b)
    }
}


// 这个人不仅分数第一,还得是可以奖励的
func getPrizableOne<T: Comparable & Prizable> (seq:[T]) -> T?{
    return seq.reduce(nil) {
        (tempTop: T?, contender: T) -> T? in // reduce可以细细品一下
        guard contender.isPrizable() else {
            return tempTop
        }

        guard let tempTop = tempTop else {
            return contender
        }

        return max(tempTop, contender)
    }
}

let student1 = Student(name: "shijie", score: 100)
let student2 = Student(name: "Mike", score: 20)
let student3 = Student(name: "July", score: 80)
let student4 = Student(name: "Ann", score: 0)

let studentArr = [student1, student2, student3, student4]

getTopOne(seq: studentArr) // shijie
getPrizableOne(seq: studentArr) // shijie

今天学到了reduce函数式编程的一些用法,这里把reduce拿出来单独说一下

func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, T) 
throws -> Result) rethrows -> Result

闭包里面的第一个参数和返回值得和reduce整体返回的类型一样,第二个参数可以随意,但是要是可操作的类型。

10. 可选的协议方法

如果协议中含有可选的协议方法,则应在协议前加上@objc,同时对应的函数前加上@objc optional

import UIKit

// 如果协议中含有可选的协议方法,则应在协议前加上@objc,同时对应的函数前加上@objc optional
@objc protocol TurnBasedGameDelegate {
    func gameStart()
    func playerMove()
    func gameEnd()

    func gameOver() -> Bool

    @objc optional func turnStart() // 可选的方法,可以不必实现

    @objc optional func turnEnd() // 同上
}

protocol TurnBasedGame {
    var turn: Int {get set}
    func play()
}

class SinglePlayerTurnBasedGame: TurnBasedGame {
    var delegate: TurnBasedGameDelegate!
    var turn = 0

    func play() {
        delegate.gameStart()
        while !delegate.gameOver() {
            turn += 1
            delegate.turnStart?()
            print("ROUND", turn, ":")
            delegate.playerMove()
            delegate.turnEnd?()
        }
        delegate.gameEnd()
    }
}


class RockPaperSscissors: SinglePlayerTurnBasedGame, TurnBasedGameDelegate {
    enum Shape: Int, CustomStringConvertible {
        case Rock // 0
        case Scissors // 1
        case Paper // 2

        // 枚举里面也可以有函数
        func beat(shape: Shape) -> Bool { // 看自己能否打败传进来的参数
            return (self.rawValue + 1) % 3 == shape.rawValue // 这个可以体会一下
        }

        var description: String { // 输出该变量的时候应该是这种格式
            switch self {
            case .Paper:
                return "Paper"
            case .Rock:
                return "Rock"
            case .Scissors:
                return "Scissors"
            }
        }
    }

    var wins = 0
    var otherWins = 0

    override init() {
        super.init()
        delegate = self
    }

    static func go() -> Shape {
        return Shape(rawValue: Int(arc4random()) % 3)!
    }

    func gameStart() {
        wins = 0
        otherWins = 0
        print("== Rock Paper Scissor ==")
    }

    func gameOver() -> Bool {
        return wins == 2 || otherWins == 2
    }

    func gameEnd() {
        if wins == 2 {
            print("AFTER \(turn) ROUNDS, YOU WIN!")
        } else {
            print("AFTER \(turn) ROUNDS, YOU LOSE...")
        }
    }

    func playerMove() {
        let yourShape = RockPaperSscissors.go()
        let otherShape = RockPaperSscissors.go()
        print("Your: \(yourShape)")
        print("Other: \(otherShape)")

        if yourShape.beat(shape: otherShape) {
            print("You win this round")
            wins += 1
        } else if otherShape.beat(shape: yourShape) {
            print("You lose this round")
            otherWins += 1
        } else {
            print("Tie in this round")
        }
    }

    func turnStart() {
        print("++++++++++++++")
    }

    func turnEnd() {
        print("--------------")
        print("")
    }
}

let rockPaperScissors = RockPaperSscissors()
rockPaperScissors.play()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值