常量序列可以使用for-in循环迭代,但不能直接调用next()?

a constant sequence can iterate using for-in loop, but can't call next() directly?

在下面的代码中,c是一个常量序列(Countdown的一个实例),它可以遍历它的元素并在满足条件时中断,它可以从重新开始。

但是当我直接调用 c.next() 时,我得到一个编译器错误:cannot use mutating member on immutable value.

所以,我有两个问题:

  1. 如果我不能调用c.next(),为什么它可以首先遍历所有元素?不是在内部使用next()方法来遍历它们吗?
  2. 在下面代码的第二个for-in循环中,为什​​么不是从第一次迭代结束的1开始计数,而是从头开始计数,即3 ]?

struct Countdown: Sequence, IteratorProtocol { 
    // internal state
    var count: Int
    // IteratorProtocol requirement
    mutating func next() -> Int? {
        if count == 0 { return nil } else {
            defer { count -= 1 } 
            return count
        }
    }
}

// a constant sequence
let c = Countdown(count: 3)

// can iterate and break
for i in c { 
    print(i)              // 3, 2
    if i == 2 { break }
}

// iterate again from start (not from 1, why?)
for i in c { print(i) }   // 3, 2, 1

// ⛔️ Error: cannot use mutating member on immutable value.
//          `c` is a `let` constant.
c.next()

因为每个 for 循环都通过调用序列的 makeIterator() 函数来创建一个新的迭代器,它是由标准库在 Sequence 的条件表达式中定义的,但是仅当符合序列也符合 IteratorProtcol,就像你的一样。

for 循环进行脱糖揭示了问题所在:

let c = Countdown(from: 3)

var iterator1 = c.makeIterator()
while let i = iterator1.next() { 
    print(i)              // 3, 2
    if i == 2 { break }
}

var iterator2 = c.makeIterator()
while let i = iterator2.next() { print(i) }   // 3, 2, 1

制作了两个独立的迭代器,每个 for 循环一个。每个 "starts fresh" 都源自 c 的副本(从未发生过变异)。

您不能调用 next,因为 nextmutatingnext 改变迭代器的状态,使其"moves closer"结束。

for 循环不直接调用 next。它创建 c (var) 的 mutable 副本,并调用 next

// The for loop basically does this:
var copy = c // creates a copy
var element = copy.next()
element = copy.next()
element = copy.next()
...

第二个for循环之所以从头开始,就是因为这个。 for 循环实际上并不会改变你正在迭代的事物的状态。他们创建一个副本,使用该副本,然后将其丢弃。

避免这种复制行为的一种方法是将 Countdown 设为 class:

class Countdown: Sequence, IteratorProtocol {
    // internal state
    var count: Int
    // IteratorProtocol requirement
    func next() -> Int? {
        if count == 0 { return nil } else {
            defer { count -= 1 }
            return count
        }
    }

    init(from n: Int){
        count = n
    }
}