1.协议的语法
定义协议:
protocol SomeProtocol {
// protocol definition goes here
}
遵守协议:
struct SomeStructure: FirstProtocol, AnotherProtocol {
// structure definition goes here
}
当一个类既有父类,又遵守其他协议时,将父类名写在所遵守协议的前面:
class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
// class definition goes here
}
2.属性的要求
- 在协议中,实例属性总是使用
var
声明为变量属性。可读的与可设置的属性在类型声明后通过写入{get set}表示,可读的属性通过写入{get}表示。
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
- 在协议中,类属性始终使用
static
关键字作为类属性声明的前缀。在类中实现时,可以使用class
或static
关键字作类属性声明前缀。
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
示例:
protocol FullyNamed {
var fullName: String { get }
}
struct Person: FullyNamed {
var fullName: String
}
let john = Person(fullName: "John Appleseed")
// john.fullName is "John Appleseed"
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 is "USS Enterprise"
3.方法的要求
- 在协议中声明的实例方法和类方法没有方法体,允许可变参数,但是不能为协议中的方法参数指定默认值;
- 在协议中,类方法始终使用
static
关键字作为前缀。在类中实现时,可以使用class
或static
关键字作类方法声明前缀。
protocol SomeProtocol {
static func someTypeMethod()
}
示例:
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).truncatingRemainder(dividingBy:m))
return lastRandom / m
}
}
let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
// Prints "Here's a random number: 0.3746499199817101"
print("And another one: \(generator.random())")
// Prints "And another one: 0.729023776863283"
- 在协议中,可变实例方法使用关键字
mutating
作前缀。在类中实现该方法时不需要写mutating
关键字。mutating
关键字仅供结构体和枚举使用。
protocol Togglable {
mutating func toggle()
}
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 is now equal to .on
5.构造器的要求
- 协议可以要求符合的类型来实现特定的构造器。可以将这些构造器作为协议定义的一部分。
protocol SomeProtocol {
init(someParameter: Int)
}
- 在符合构造器协议的类中,可以将协议构造器作为该类的指定构造器或便利构造器。在这两种情况中,必须使用
required
修饰这些构造器。required
修饰符确保在该类的所有子类上提供构造器的显式或继承的实现。
class SomeClass: SomeProtocol {
required init(someParameter: Int) {
// initializer implementation goes here
}
}
注意:
在使用final
关键字修饰的类上,不需要使用required
关键字标记协议构造器的实现。
- 如果子类重写父类的一个指定构造器,同时还从协议中实现相匹配的构造器,则该构造器需要使用
required
和override
关键字修饰。
protocol SomeProtocol {
init()
}
class SomeSuperClass {
init() {
// initializer implementation goes here
}
}
class SomeSubClass: SomeSuperClass, SomeProtocol {
// "required" from SomeProtocol conformance; "override" from SomeSuperClass
required override init() {
// initializer implementation goes here
}
}
6.协议是一种类型
协议是一种类型:
- 作为函数、方法或构造器中的参数类型或返回值类型;
- 作为常量、变量或属性的类型
- 作为数组、字典或其他容器中的元素类型
示例:
定义一个新类Dice
,它表示用于棋盘游戏的n面骰子。Dice
实例有一个sides
的整数属性,它表示有多少边;还有一个类型为RandomNumberGenerator
的属性generator
,它提供了一个随机数生成器,可以从中生成骰子掷骰值,可以将其设置为遵守RandomNumberGenerator
协议的任何类型的实例。
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
}
}
以下是如何使用Dice
类创建一个带有 LinearCongruentialGenerator
实例作为其随机数生成器的六面骰子:
var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
for _ in 1...5 {
print("Random dice roll is \(d6.roll())")
}
// Random dice roll is 3
// Random dice roll is 5
// Random dice roll is 4
// Random dice roll is 5
// Random dice roll is 4
注意:
因为协议是一种类型,所以以大写字母开头,以匹配Swift中其他类型的名称(如Int、String和Double)。
7.委托
委托是一种设计模式,它允许类或结构体将其部分职责传递(或委托)给另一种类型的实例。这种设计模式是通过定义一个协议来实现的,该协议封装了委托者的职责,这样就保证了一致性类型(称为委托)能够提供已经委托的功能。委托可以用来响应特定的操作,或者从外部源检索数据,而不需要知道该源的底层类型。
示例:
DiceGame
规定了涉及骰子的游戏所采用的协议,使用DiceGameDelegate
协议可以追踪DiceGame
的进度:
protocol DiceGame {
var dice: Dice { get }
func play()
}
protocol DiceGameDelegate: AnyObject {
func gameDidStart(_ game: DiceGame)
func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
func gameDidEnd(_ game: DiceGame)
}
定义一个类SnakesAndLadders
遵守DiceGame协议,并通知DiceGameDelegate
其进度:
class SnakesAndLadders: DiceGame {
let finalSquare = 25
let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
var square = 0
var board: [Int]
init() {
board = Array(repeating: 0, count: finalSquare + 1)
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
}
weak 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)
}
}
下面示例显示了一个名为DiceGameTracker
的类,它遵守DiceGameDelegate
协议:
class DiceGameTracker: DiceGameDelegate {
var numberOfTurns = 0
func gameDidStart(_ game: DiceGame) {
numberOfTurns = 0
if game is SnakesAndLadders {
print("Started a new game of Snakes and Ladders")
}
print("The game is using a \(game.dice.sides)-sided dice")
}
func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
numberOfTurns += 1
print("Rolled a \(diceRoll)")
}
func gameDidEnd(_ game: DiceGame) {
print("The game lasted for \(numberOfTurns) turns")
}
}
以下是DiceGameTracker
的实际应用:
let tracker = DiceGameTracker()
let game = SnakesAndLadders()
game.delegate = tracker
game.play()
// Started a new game of Snakes and Ladders
// The game is using a 6-sided dice
// Rolled a 3
// Rolled a 5
// Rolled a 4
// Rolled a 5
// The game lasted for 4 turns
8.使用扩展添加协议一致性
- 当在扩展中将一致性添加到实例的类型时,类型的现有实例自动采用并遵守协议。
示例:
这个被称为TextRepresentable的协议,可以由任何能够表示为文本的类型实现。这可能是对其自身的描述,或其当前状态的文本版本:
protocol TextRepresentable {
var textualDescription: String { get }
}
上面的Dice
类可以扩展为采用并遵守TextRepresentable
协议:
extension Dice: TextRepresentable {
var textualDescription: String {
return "A \(sides)-sided dice"
}
}
任何Dice
类的实例现在可以被视为TextRepresentable
:
let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
print(d12.textualDescription)
// Prints "A 12-sided dice"
同样,SnakesAndLadders
类也可以扩展为采用和遵守TextRepresentable
协议:
extension SnakesAndLadders: TextRepresentable {
var textualDescription: String {
return "A game of Snakes and Ladders with \(finalSquare) squares"
}
}
print(game.textualDescription)
// Prints "A game of Snakes and Ladders with 25 squares"
- 泛型类型只能在特定条件下才能满足协议的要求,比如类型的泛型参数符合协议。在扩展类型时,可以通过列出约束条件使泛型类型在特定条件下符合协议。通过编写泛型where语句,在采用的协议名称之后添加这些约束。
示例:
下面的扩展使数组实例在存储符合TextRepresentable
类型的元素时符合TextRepresentable
协议。
extension Array: TextRepresentable where Element: TextRepresentable {
var textualDescription: String {
let itemsAsText = self.map { $0.textualDescription }
return "[" + itemsAsText.joined(separator: ", ") + "]"
}
}
let myDice = [d6, d12]
print(myDice.textualDescription)
// Prints "[A 6-sided dice, A 12-sided dice]"
- 如果一个类型已经符合协议的所有要求,但是还未声明采用了该协议,则可以让它采用一个空扩展的协议:
struct Hamster {
var name: String
var textualDescription: String {
return "A hamster named \(name)"
}
}
extension Hamster: TextRepresentable {}
现在,只要TextRepresentable
是所需类型,就可以使用Hamster
实例:
let simonTheHamster = Hamster(name: "Simon")
let somethingTextRepresentable: TextRepresentable = simonTheHamster
print(somethingTextRepresentable.textualDescription)
// Prints "A hamster named Simon"
注意:
类型不会仅通过满足其要求而自动采用协议。 它们必须始终明确地声明它们采用了协议。
9.协议类型的集合
- 协议可以用作存储在数组或字典等集合中的类型。
示例:
这个例子创建了一个元素类型为TextRepresentable
的数组:
let things: [TextRepresentable] = [game, d12, simonTheHamster]
for thing in things {
print(thing.textualDescription)
}
// A game of Snakes and Ladders with 25 squares
// A 12-sided dice
// A hamster named Simon
10.协议的继承
- 协议可以继承一个或多个其他协议,并且可以在它继承的需求之上添加更多的需求。协议继承的语法类似于类继承的语法,但是可以列出多个继承的协议,用逗号分隔:
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
// protocol definition goes here
}
下面是一个继承TextRepresentable
协议的例子:
protocol PrettyTextRepresentable: TextRepresentable {
var prettyTextualDescription: String { get }
}
SnakesAndLadders
类通过扩展来采用和遵守PrettyTextRepresentable
协议:
extension SnakesAndLadders: PrettyTextRepresentable {
var prettyTextualDescription: String {
var output = textualDescription + ":\n"
for index in 1...finalSquare {
switch board[index] {
case let ladder where ladder > 0:
output += "▲ "
case let snake where snake < 0:
output += "▼ "
default:
output += "○ "
}
}
return output
}
}
现在可以使用prettyTextualDescription
属性打印任何SnakesAndLadders
实例的漂亮文本描述:
print(game.prettyTextualDescription)
// A game of Snakes and Ladders with 25 squares:
// ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○
11.仅支持类的协议
- 通过将AnyObject协议添加到协议的继承列表中,来限制该协议只适用于
class
类型,而不适用于枚举和结构体。
protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
// class-only protocol definition goes here
}
12.协议组合
- 当需要一个类型同时符合多个协议时,可以使用协议组合,将多个协议组合到单个需求中。
- 协议组合不是定义新的协议类型;
- 协议组合还可以包含一个类类型,可以使用它来指定所需的超类;
- 协议组合使用
&
符连接多个协议;
示例:
函数中的参数celebrator
要求传入的参数类型必须同时遵守Named
和Aged
协议:
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
struct Person: Named, Aged {
var name: String
var age: Int
}
func wishHappyBirthday(to celebrator: Named & Aged) {
print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(to: birthdayPerson)
// Prints "Happy birthday, Malcolm, you're 21!"
下面的示例,它将前面示例中的Named
协议与Location
类组合起来:
class Location {
var latitude: Double
var longitude: Double
init(latitude: Double, longitude: Double) {
self.latitude = latitude
self.longitude = longitude
}
}
class City: Location, Named {
var name: String
init(name: String, latitude: Double, longitude: Double) {
self.name = name
super.init(latitude: latitude, longitude: longitude)
}
}
func beginConcert(in location: Location & Named) {
print("Hello, \(location.name)!")
}
let seattle = City(name: "Seattle", latitude: 47.6, longitude: -122.3)
beginConcert(in: seattle)
// Prints "Hello, Seattle!"
13. 检查协议一致性
- 可以使用
is
和as
操作符来检查协议的一致性,并强制转换到特定的协议。 - 如果一个实例符合协议,则
is
操作符返回true;否则返回false。 -
as?
操作符返回协议类型的可选值,如果实例不符合该协议,则该值为nil。 -
as!
操作符将强制转换为协议类型,并在转换失败时触发运行时错误。
示例:
protocol HasArea {
var area: Double { get }
}
class Circle: HasArea {
let pi = 3.1415927
var radius: Double
var area: Double { return pi * radius * radius }
init(radius: Double) { self.radius = radius }
}
class Country: HasArea {
var area: Double
init(area: Double) { self.area = area }
}
class Animal {
var legs: Int
init(legs: Int) { self.legs = legs }
}
let objects: [AnyObject] = [
Circle(radius: 2.0),
Country(area: 243_610),
Animal(legs: 4)
]
for object in objects {
if let objectWithArea = object as? HasArea {
print("Area is \(objectWithArea.area)")
} else {
print("Something that doesn't have an area")
}
}
// Area is 12.5663708
// Area is 243610.0
// Something that doesn't have an area
14.可选协议
- 当协议中某些方法或属性不需要遵守协议的类型实现时,使用关键字
optional
来指明; - 协议和可选需求都必须用
@objc
属性标记; -
@objc
协议只能由继承自Objective-C类或其他@objc
类的类使用,结构体或枚举不能使用。
示例:
@objc protocol CounterDataSource {
@objc optional func increment(forCount count: Int) -> Int
@objc optional var fixedIncrement: Int { get }
}
class Counter {
var count = 0
var dataSource: CounterDataSource?
func increment() {
if let amount = dataSource?.increment?(forCount: count) {
count += amount
} else if let amount = dataSource?.fixedIncrement {
count += amount
}
}
}
class ThreeSource: NSObject, CounterDataSource {
let fixedIncrement = 3
}
var counter = Counter()
counter.dataSource = ThreeSource()
for _ in 1...4 {
counter.increment()
print(counter.count)
}
// 3
// 6
// 9
// 12
class TowardsZeroSource: NSObject, CounterDataSource {
func increment(forCount count: Int) -> Int {
if count == 0 {
return 0
} else if count < 0 {
return 1
} else {
return -1
}
}
}
counter.count = -4
counter.dataSource = TowardsZeroSource()
for _ in 1...5 {
counter.increment()
print(counter.count)
}
// -3
// -2
// -1
// 0
// 0
15.协议扩展
- 可以对协议进行扩展,为遵守协议的类型提供方法、构造器、下标和计算属性的实现。
- 协议扩展可以为遵守协议的类型添加实现,但它不能使协议从另一个协议扩展或继承。协议继承始终在协议声明本身指定。
示例:
RandomNumberGenerator
协议通过扩展提供一个randomBool()
方法,该方法使用random()
的结果返回一个Bool
值。
extension RandomNumberGenerator {
func randomBool() -> Bool {
return random() > 0.5
}
}
通过在协议中创建扩展,所有遵守该协议的类型将自动获得该方法的实现,而不需要添加额外的修改。
let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
// Prints "Here's a random number: 0.3746499199817101"
print("And here's a random Boolean: \(generator.randomBool())")
// Prints "And here's a random Boolean: true"
- 如果协议扩展为该协议的任何方法或计算属性提供了默认实现,同时遵守协议的类型也提供了所需方法或属性的自身实现,则将使用类型本身的实现而不是协议扩展提供的实现。
示例:
extension PrettyTextRepresentable {
var prettyTextualDescription: String {
return textualDescription
}
}
- 在定义协议扩展时,可以指定符合类型必须满足的约束条件,然后才能使用扩展的方法和属性。通过编写泛型
where
语句,在要扩展的协议名称添加这些约束。
示例:
extension Collection where Element: Equatable {
func allEqual() -> Bool {
for element in self {
if element != self.first {
return false
}
}
return true
}
}
let equalNumbers = [100, 100, 100, 100, 100]
let differentNumbers = [100, 100, 200, 100, 200]
print(equalNumbers.allEqual())
// Prints "true"
print(differentNumbers.allEqual())
// Prints "false"
网友评论