第二十一章 协议

8. Adding Protocol Conformance with an Extension (通过扩展添加协议一致性)

可以通过扩展已有的类型来采用和遵循一个新的协议,即使我们对该已有类型的源代码没有访问权限时。通过扩展可以给已有的类型添加一些新的属性,方法或下标。 所以可以通过扩展协议也可以为已有的类型添加新的属性等,更多扩展相关内容在扩展章节中有详细介绍。

下面这个协议TextRepresentable,任何表达文本的任何类型都可以实现这个协议,可以是文本本身也可以是当前文本的状态。

protocol TextRepresentable {
    var textualDescription: String { get }
}

上面Dice这个类,同样也可以通过扩展来采用和遵循这个协议TextRepresentable。这个扩展采用了一个新的协议和在Dice里面实现某个要求的功能是一样的。也就是说可以通过扩展给Dice添加某些要求,也可以在Dice的实现中添加某些要求都是一样的为实现某个功能。

extension Dice: TextRepresentable {
    var textualDescription: String {
    	// 同样也可以在Dice的定义中实现该返回
        return "A \(sides)-sided dice"
    }
}

任何一个Dice的实例现在都可以当作TextRepresentable:所以呢这个返回值就是通过扩展协议添加在Dice里面的。

let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
print(d12.textualDescription)
// 输出:A 12-sided dice

相同的 这个类SnakeAndLadders也可以通过扩展来采用和遵循TextRepresentable这个协议。

extension SnakesAndLadders: TextRepresentable {
    var textualDescription: String {
        return "A game of Snakes and Ladders with \(finalSquare) squares"
    }
}
print(game.textualDescription)
// 输出:A game of Snakes and Ladders with 25 squares

8.1 Conditionally Conforming to a Protocol (条件性遵循协议)

只有在某些条件下泛型才能满足协议的一些要求,就像当一个类型的泛型参数遵循协议的时候,当扩展这个类型是可以通过约束来使泛型选择性的遵守某个协议。在协议的名字后面添加这些约束采用一个泛型where子句,

下面这个例子通过扩展创建一个Array的实例采用和遵循这个协议TextRepresentable,无论这个实例存储的是什么Elememt,但是必须要遵循这个协议。

extension Arrary: TextRepresentable where Element: TextRepresentable
{
	var textualDescription: String {
		let itemsAsText = self.map { $0.textualDescription }
		return "[" + itemsAsText.joined(separator: ", ") + "]"
	}
}
let myDice = [d6, d12]
print(myDice.textualDescription)
// 输出:[A 6-sided dice, A 12-sided dice]

8.2 Declaring Protocol Adoption with an Extension

如果说一个类型已经遵循来某个协议的所有要求,但是并没有申明要采用这个协议,我们可以通过一个空白的扩展来采用这个协议。

struct Hamster {
	var name: String 
	var textualDescription: String{
		return "A hamster named \(name)"
	} 
}
/* Haster通过扩展采用这个协议TextRepresentable
	但是并没有传入任何参数*/
extension Hamster: TextRepresentable{}
let simonTheHamster = Hamster(name: "Simon")
// 进一步传值加输出调用
let somethingTextRepresentable: textRepresentable = simonTheHamster
print("somethingTextRepresentable.textualDescription")
// 输出:A hamster named Simon

上面这个例子是通过实例化后在进行采用和遵循协议来达到输出某个信息,而直接通过扩展采用和遵循某个协议,在扩展体里面也可以进行添加相对应的代码以达到相同的输出要求。

9. Collection of Protocol Types (协议类型的集合)

协议可以被用作类型,而这个类型要被存储在一个像数组或字典一样的集合。在协议当作类型章节中有介绍,下面这个例子创建了TextRepresentable的一个数组

// 常量things是一个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

需要注意的是这个things常量是一个TextRepresentable的类型,而不是Dice,DiceGame或者Hamster的类型,在幕后即便是其中一个类型,但是由于thing是TextRepresentable类型,任何TextRepresentable的实例都有一个Description属性,所以在每次循环中可以安全地访问 thing.textualDescription 。

10. Protocol Inheritance (协议继承)

协议可以继承一个或多个其他的协议,并且可以在前一个继承的协议上面添加更多其他的需求,协议继承的语法和类继承的语法较为相似,可以用逗号分隔开多个需要继承的协议

protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
    // protocol definition goes here
}

下面这个例子就是继承了TextRepresentable的一个新的协议。该例定义了一个新的协议PrettyTextRepresentable,而这个新的协议继承了TextRepresentable,也就是说任何采用PrettyTextRepresentable的这个协议也要满足TextRepresentable协议的所有要求,加上这个新的协议额外的对TextRepresentable协议的要求。也就是这个可读的属性要求prettyTextualDescription。所以说协议的继承和类的继承在某些方面是相似雷同的。

protocol PrettyTextRepresentable: TextRepresentable {
    var prettyTextualDescription: String { get }
}

在蛇和梯子游戏中SnakesAndLadders类也可以被扩展和采用来遵循这个PrettyTextRepresentable协议,extension即在不改变原类或协议等的基础上在添加额外的属性等要求对当前的类或协议作进一步约束。

// SnakesAndLadders接受并遵循PrettyTextRepresentable协议
extension SnakesAndLadders: PrettyTextRepresentable {
	/* 该扩展为接受并遵循PrettyTextRepresentable协议的SnakesAndLadders类
		提供了一个类型为String的变量属性prettyTextualDescription*/
    var prettyTextualDescription: String {
    	// 变量输出是一个表达(textualDescription)加换行
        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
    }
}

该扩展声明并且采用了PrettyTextRepresentable协议并且为SnakesAndLadders类型提供了prettyTextualDescription属性的实现,任何接受PrettyTextRepresentable协议的也会接受TextRepresentable协议,在 prettyTextualDescription的实现中从TextRepresentable协议中读取textualDescription属性, 在实现中开始这次的输出结果(output)。用:\n替代加上原文本的表达后的一个换行。接着循环数组中的元素,添加一个几何图案来表达每个棋盘格子上的内容。

  • 棋盘格子上面的值大于0,用▲表示
  • 盘格子上面的值小于0,用▼表示
  • 盘格子上面的值是0,用○表示

所以现在这个属性prettyTextualDescription可以被SnakesAndLadders的实例用来输出精确的文本表达了。

print(game.prettyTextualDescription)
// A game of Snakes and Ladders with 25 squares:
// ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○

11. Class-Only Protocols (类类型协议)

通过将AnyObject协议添加到协议的继承列表中,可以限制协议对类类型(非结构体与枚举)的采用和遵循。下面这个例子。SomeClassOnlyProtocol只能被类类型所采用,如果尝试让结构体或枚举来采用SomeClassOnlyProtocol,将会出现编译错误。

protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
    // class-only protocol definition goes here
}

swift subscript的使用场景 swift description_ios