Sequence协议既可以表达一个有限的集合,也可以表达一个无限的集合,只需要提供集合中的元素和如何访问这些元素的接口即可。
Collection是一个继承自 Sequence 的协议。
一、Sequence协议
1、for in 的本质
Swift中的for in是一个语法糖。
let nums = [1, 2, 3, 4, 5];
for element in nums {
print(element)
}
编译成sil文件看一下:
可以看到,当执行 for in 的时候,本质上会通过 Collection 创建一个迭代器(Collection<>.makeIterator()),然后把当前的数组传给迭代器,最后调用迭代器的 next方法(IndexingIterator.next())将数组的元素遍历出来。
所以,我们也可以显试地调用迭代器
let nums = [1, 2, 3, 4, 5];
var numsIterator = nums.makeIterator()
for element in numsIterator {
print(element)
}
var numsIterator2 = nums.makeIterator()
while let element2 = numsIterator2.next() {
print(element2)
}
打开swift源码中 Collection.swift 文件,找到 IndexingIterator结构体 的定义
打开Sequence.swift这个文件,可以看到IteartorProtocol是一个提供一个序列值的类型,它和Sequence协议是息息相关的,Sequence协议每次通过创建迭代器来访问序列中的元素。
再来看Sequence协议的定义:
Sequence协议既可以表达一个有限的集合,也可以表达一个无限的集合,只需要提供集合中的元素,和如何访问这些元素的接口给它即可。
2、Sequence协议表达一个有限的集合:
struct CTIterator: IteratorProtocol{
typealias Element = Int
let sequence: CTSequence
init(_ sequence: CTSequence) {
self.sequence = sequence
}
var count = 0
mutating func next() -> Int? {
guard count < self.sequence.arrayCount else {
return nil
}
count += 1
return count;
}
}
struct CTSequence:Sequence {
typealias Element = Int
var arrayCount: Int
func makeIterator() -> CTIterator {
return CTIterator(self)
}
}
var ctIterator = CTSequence(arrayCount: 10)
for item in ctIterator{
print(item)
}
3、Sequence协议表达一个无限的集合
struct unlimitedIterator:IteratorProtocol{
typealias Element = Int
let value:Int
func next() -> Element? {
return value
}
}
var iterator = unlimitedIterator(value: 10)
while let x = iterator.next() {
print(x)
}
二、Collection
1、环形数组
通过Collection来表达一个环型数组。
对于普通数组来说,头尾的下标都是固定的,头下标为 0,尾下标为 length - 1。
而环形数组(circular array)通常使用两个额外的数字去标识数组的头下标和尾下标。
struct RingBuffer<Element>{
internal var _buffer: ContiguousArray<Element?>
internal var headIndex: Int = 0
internal var tailIndex: Int = 0
internal var mask: Int{
return self._buffer.count - 1
}
init(initalCapacity: Int) {
let capcatiy = initalCapacity.nextPowerOf2()
self._buffer = ContiguousArray<Element?>.init(repeating: nil, count:capcatiy)
}
mutating func advancedTailIndex(by: Int){
self.tailIndex = self.indexAdvanced(index: self.tailIndex, by: by)
}
mutating func advancedHeadIndex(by: Int){
self.headIndex = self.indexAdvanced(index: self.headIndex, by: by)
}
func indexAdvanced(index: Int, by: Int) -> Int{
return (index + by) & self.mask
}
mutating func append(_ value: Element){
_buffer[self.tailIndex] = value
self.advancedTailIndex(by: 1)
if self.tailIndex == self.headIndex {
fatalError("out of bounds")
}
}
mutating func read() -> Element?{
let element = _buffer[self.headIndex]
self.advancedHeadIndex(by: 1)
return element
}
}
这个结构体通过一个 ContiguousArray 类型的 _buffer 来记录环形数组的元素。(ContiguousArray 可以理解为存粹的 Swift 的数组,和 OC 没有任何关系的数组,它的效率比 Array 高)
接下来我们通过 headIndex 和 tailIndex 来表示环形数组的起始和结束的位置。剩下的都是这个环形数组的一些初始化方法,移动环形数组的起始和结束的位置的方法,以及追加元素和读取元素的方法和获取元素索引的方法。
初始化方法,nextPowerOf2 这个方法表示 2^n,这个表示使 capacity 始终为 2^n,其实现如下:
extension FixedWidthInteger {
/// Returns the next power of two.
@inlinable
func nextPowerOf2() -> Self {
guard self != 0 else {
return 1
}
return 1 << (Self.bitWidth - (self - 1).leadingZeroBitCount)
}
}
2、Collection协议
- 定义
startIndex和endIndex属性,表示集合起始和结束位置; - 实现一个
index(after:)方法用于在集合中移动索引位置; - 定义一个只读的下标操作符。
subscript(position: Int) -> Element? {get{}}
extension RingBuffer:Collection{
var startIndex: Int{
return self.headIndex
}
var endIndex: Int{
return self.tailIndex
}
func index(after i: Int) -> Int {
return (i + 1) & self.mask
}
subscript(position: Int) -> Element? {
get{
return self._buffer[position]
}
}
}
3、MutableCollection协议
允许集合通过下标修改自身元素,实现内容的与Collection协议内容基本一致,多了subscript(position: Int) -> Element? {}的set()方法。
extension RingBuffer:Collection,MutableCollection{
var startIndex: Int{
return self.headIndex
}
var endIndex: Int{
return self.tailIndex
}
func index(after i: Int) -> Int {
return (i + 1) & self.mask
}
subscript(position: Int) -> Element? {
get{
return self._buffer[position]
}
set{
self._buffer[position] = newValue
}
}
}
类似这样的操作,本质上都是实现了MutableCollection协议
var nums = [1, 2, 3, 4]
// 直接通过下标来修改元素的值
nums[2] = 8
4、RangeReplaceableCollection协议
RangeReplaceableCollection 允许集合修改任意区间的元素。
RangeReplaceableCollection 协议要求我们实现一个 init 方法。
extension RingBuffer: RangeReplaceableCollection {
init() {
self.init(initalCapacity: 10)
}
}
为了实现删除的功能,我们再实现一个remove()方法。
extension RingBuffer:RangeReplaceableCollection{
init() {
self.init(initalCapacity: 10)
}
mutating func remove(at i: Int) -> Element? {
//要删除的索引
var currentIndex = i
//要删除的值
let element = self._buffer[i]
switch i {
//要删除的索引 正好是 headIndex
case self.headIndex:
self.advancedHeadIndex(by: 1) //把头结点向后移动一位
self._buffer[currentIndex] = nil //把原来头结点的值置空
//要删除的索引 不是 headIndex
default:
self._buffer[i] = nil //把对应节点值置空
var nextIndex = self.indexAdvanced(index: i, by: 1) //拿到下一个节点索引
//循环 直到碰到尾节点
while nextIndex != self.tailIndex {
self._buffer.swapAt(currentIndex, nextIndex) //要删除的值 跟 下一个节点的值 交换(相当于把要删除的值向后面推,当然 此刻这个值是nil)
currentIndex = nextIndex //要删除的索引 向后移动一位
nextIndex = self.indexAdvanced(index: currentIndex, by: 1) //下一个节点索引 向后移动一位
}
self.advancedTailIndex(by: -1) //将尾索引指向最后一个值
}
return element
}
}
当然,RangeReplaceableCollection协议中还有许多方法,例如:replaceSubrange(_ :with:)、removeFirst()、removeFirst()等,依据需求来实现。
5. BidirectionalCollection协议
可以向前或向后遍历集合。
可以获取最后一个元素、反转序列、快速的获取倒序等等。
Collection协议中正序是通过 index(after:) 来实现的,那么遵循了BidirectionalCollection协议后,倒序是通过实现 index(before:) ,这就好像双向链表 一样,只不过双向链表获取的是值,而这里的集合获取的都是索引。
extension RingBuffer: BidirectionalCollection{
func index(before i: Int) -> Int {
return (i - 1) & self.mask
}
}
6. RandomAccessCollection协议
访问任意集合元素
前面对集合元素的访问都是诸如:index=1,index=x,这样的访问,遵循了RandomAccessCollection协议之后,可以实现访问 距离初始索引x个距离的值。
extension RingBuffer: RandomAccessCollection{
// i 是初始索引,distance是距离多少
func index(_ i: Int, offsetBy distance: Int) -> Int {
return (i + distance) & self.mask
}
func distance(from start: Int, to end: Int) -> Int {
return end - start
}
}
测试代码:
var buffer: RingBuffer = RingBuffer<Int>.init(initalCapacity: 10)
for i in 0..<10 {
buffer.append(i)
}
print(buffer.index(0, offsetBy: 2)) // 2
当然,对于这个集合,我们不去实现index(i: offsetBy:)也是可以的。因为我们把 index(after:) 和 index(before:) 实现了,对于系统来说,它是可以通过这两个方法来推断当前要移动的距离。但是考虑到效率的问题,在这里直接实现会比系统去推断要好得多,因为直接省去了系统推断的时间。