Swift5.1—协议

简介: Swift5.1—协议

协议 定义了一个蓝图,规定了用来实现某一特定任务或者功能的方法、属性,以及其他需要的东西。类、结构体或枚举都可以遵循协议,并为协议定义的这些要求提供具体实现。某个类型能够满足某个协议的要求,就可以说该类型遵循这个协议。


除了遵循协议的类型必须实现的要求外,还可以对协议进行扩展,通过扩展来实现一部分要求或者实现一些附加功能,这样遵循协议的类型就能够使用这些功能。


协议语法



协议的定义方式与类、结构体和枚举的定义非常相似:

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


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

struct SomeStructure: FirstProtocol, AnotherProtocol {
    // 这里是结构体的定义部分
}


若是一个类拥有父类,应该将父类名放在遵循的协议名之前,以逗号分隔:

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


属性要求



协议可以要求遵循协议的类型提供特定名称和类型的实例属性或类型属性。协议不指定属性是存储属性还是计算属性,它只指定属性的名称和类型。此外,协议还指定属性是可读的还是可读可写的。


如果协议要求属性是可读可写的,那么该属性不能是常量属性或只读的计算型属性。如果协议只要求属性是可读的,那么该属性不仅可以是可读的,如果代码需要的话,还可以是可写的。


协议总是用 var 关键字来声明变量属性,在类型声明后加上 { set get }来表示属性是可读可写的,可读属性则用 { get } 来表示:

protocol SomeProtocol {
    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 }
}


FullyNamed 协议除了要求遵循协议的类型提供 fullName 属性外,并没有其他特别的要求。这个协议表示,任何遵循 FullyNamed 的类型,都必须有一个可读的 String 类型的实例属性 fullName


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

struct Person: FullyNamed {
    var fullName: String
}
let john = Person(fullName: "John Appleseed")
// john.fullName 为 "John Appleseed"


这个例子中定义了一个叫做 Person 的结构体,用来表示一个具有名字的人。从第一行代码可以看出,它遵循了 FullyNamed 协议。


Person 结构体的每一个实例都有一个 String 类型的存储型属性 fullName。这正好满足了 FullyNamed 协议的要求,也就意味着 Person 结构体正确地遵循了协议。(如果协议要求未被完全满足,在编译时会报错。)


下面是一个更为复杂的类,它采纳并遵循了 FullyNamed 协议:

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 为 "USS Enterprise"


Starship 类把 fullName 作为只读的计算属性来实现。每一个 Starship 类的实例都有一个名为 name 的非可选属性和一个名为 prefix 的可选属性。 当 prefix 存在时,计算属性 fullName 会将 prefix 插入到 name 之前,从而得到一个带有 prefixfullName


方法要求



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


正如属性要求中所述,在协议中定义类方法的时候,总是使用 static 关键字作为前缀。即使在类实现时,类方法要求使用 classstatic 作为关键字前缀,前面的规则仍然适用:

protocol SomeProtocol {
    static func someTypeMethod()
}


下面的例子定义了一个只含有一个实例方法的协议:

protocol RandomNumberGenerator {
    func random() -> Double
}


RandomNumberGenerator 协议要求遵循协议的类型必须拥有一个名为 random, 返回值类型为 Double 的实例方法。尽管这里并未指明,但是我们假设返回值是从 0.0 到(但不包括)1.0。


RandomNumberGenerator 协议并不关心每一个随机数是怎样生成的,它只要求必须提供一个随机数生成器。


如下所示,下边是一个遵循并符合 RandomNumberGenerator 协议的类。该类实现了一个叫做线性同余生成器(linear congruential generator) 的伪随机数算法。

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).truncatingRemainder(dividingBy:m))
        return lastRandom / m
    }
}
let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
// 打印 “Here's a random number: 0.37464991998171”
print("And another one: \(generator.random())")
// 打印 “And another one: 0.729023776863283”


异变方法要求



有时需要在方法中改变(或异变)方法所属的实例。例如,在值类型(即结构体和枚举)的实例方法中,将 mutating 关键字作为方法的前缀,写在 func 关键字之前,表示可以在该方法中修改它所属的实例以及实例的任意属性的值。这一过程在 在实例方法中修改值类型 章节中有详细描述。


如果你在协议中定义了一个实例方法,该方法会改变遵循该协议的类型的实例,那么在定义协议时需要在方法前加 mutating 关键字。这使得结构体和枚举能够遵循此协议并满足此方法要求。


注意

实现协议中的 mutating 方法时,若是类类型,则不用写 mutating 关键字。而对于结构体和枚举,则必须写 mutating 关键字。


如下所示,Togglable 协议只定义了一个名为 toggle 的实例方法。顾名思义,toggle() 方法将改变实例属性,从而切换遵循该协议类型的实例的状态。


toggle() 方法在定义的时候,使用 mutating 关键字标记,这表明当它被调用时,该方法将会改变遵循协议的类型的实例:

protocol Togglable {
    mutating func toggle()
}


当使用枚举或结构体来实现 Togglable 协议时,需要提供一个带有 mutating 前缀的 toggle() 方法。


下面定义了一个名为 OnOffSwitch 的枚举。这个枚举在两种状态之间进行切换,用枚举成员 OnOff 表示。枚举的 toggle() 方法被标记为 mutating,以满足 Togglable 协议的要求:

enum OnOffSwitch: Togglable {
    case off, on
    mutating func toggle() {
        switch self {
        case .off:
            self = .on
        case .on:
            self = .off
        }
    }
}
var lightSwitch = OnOffSwitch.off
lightSwitch.toggle()
// lightSwitch 现在的值为 .on


构造器要求



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

protocol SomeProtocol {
    init(someParameter: Int)
}


协议构造器要求的类实现


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

class SomeClass: SomeProtocol {
    required init(someParameter: Int) {
        // 这里是构造器的实现部分
    }
}


使用 required 修饰符可以确保所有子类也必须提供此构造器实现,从而也能遵循协议。


关于 required 构造器的更多内容,请参考 必要构造器


注意

如果类已经被标记为 final,那么不需要在协议构造器的实现中使用 required 修饰符,因为 final 类不能有子类。关于 final 修饰符的更多内容,请参见 防止重写


如果一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么该构造器的实现需要同时标注 requiredoverride 修饰符:

protocol SomeProtocol {
    init()
}
class SomeSuperClass {
    init() {
        // 这里是构造器的实现部分
    }
}
class SomeSubClass: SomeSuperClass, SomeProtocol {
    // 因为遵循协议,需要加上 required
    // 因为继承自父类,需要加上 override
    required override init() {
        // 这里是构造器的实现部分
    }
}


可失败构造器要求


协议还可以为遵循协议的类型定义可失败构造器要求,详见 可失败构造器

遵循协议的类型可以通过可失败构造器(init?)或非可失败构造器(init)来满足协议中定义的可失败构造器要求。协议中定义的非可失败构造器要求可以通过非可失败构造器(init)或隐式解包可失败构造器(init!)来满足。


注:


1.protocal 相当于“接口”,定义一组规范,再由具体的类、结构体、枚举型变量实现。

2.协议的方法中没有具体实现,不能有默认参数,默认参数相当于一种实现,同时不允许有函数体body。

3.协议的属性的读取类型为{get set}可读写或{get}只读,协议属性定义必须为var。

4.协议不是类型,不能像类一样初始化,而要创建一个类型来遵守协议。

5.协议,本身可以被当作一个类型来看待,但是和遵守协议实现的类等有所区别。

6.关键字class,定义只能被类实现。

7.实现协议必须实现协议中的所有内容。

目录
相关文章
|
6月前
|
Swift iOS开发
Swift 语言: 什么是协议(Protocol)?如何实现和使用协议?
Swift 语言: 什么是协议(Protocol)?如何实现和使用协议?
195 2
|
算法 Swift C++
34 Swift为了协议 关联类型
Swift为了协议 关联类型
74 0
|
存储 Swift iOS开发
31 Swift 继续聊聊协议扩展
Swift 继续聊聊协议扩展
109 0
|
17天前
|
存储 Swift 开发者
Swift 协议
Swift 协议
23 0
|
6月前
|
设计模式 Swift iOS开发
【Swift开发专栏】Swift中的协议与委托模式
【4月更文挑战第30天】Swift编程语言强调协议与委托模式。协议定义了类型需实现的方法和属性,如`SomeProtocol`示例。遵循协议的类、结构体或枚举需实现协议要求。协议可继承,也可作为类型使用。委托模式让对象间通信更灵活,通过协议实现,如`DataSourceDelegate`示例。实战案例展示了在`UITableView`和自定义下载器中使用委托模式。
122 0
|
4月前
|
存储 安全 Swift
Swift高级特性:泛型与协议
【7月更文挑战第10天】Swift高级特性:泛型与协议增强代码复用与类型安全。泛型允许编写通用代码,如`swap`函数和泛型`Stack`结构体,支持类型约束如`Comparable`。协议定义行为蓝图,类型遵循协议需实现其要求。通过两者结合,构建高效灵活的代码结构。
|
Swift C++ Ruby
32 Swift面向协议编程初探
32 Swift面向协议编程初探
71 0
|
存储 前端开发 Swift
Swift实用小册20: Protocol协议的使用
在本章中,你将学会Protocol协议的使用方法。
272 0
Swift实用小册20: Protocol协议的使用
|
存储 安全 Java
Swift5.0 - day5-继承、初始化、可选链、协议(上)
Swift5.0 - day5-继承、初始化、可选链、协议(上)
129 0
Swift5.0 - day5-继承、初始化、可选链、协议(上)
|
Swift C++
Swift-进阶 10:可选类型Optional & Equatable+Comparable协议
Swift-进阶 10:可选类型Optional & Equatable+Comparable协议
349 0
Swift-进阶 10:可选类型Optional & Equatable+Comparable协议