В документации для IteratorProtocol говорится, что всякий раз, когда вы используете цикл for-in с массивом, установите или любую другую коллекцию или последовательность, вы используете итератор этого типа. Таким образом, мы гарантируем, что цикл for in
будет использовать .makeIterator()
и .next()
, которые чаще всего определяются для Sequence
и IteratorProtocol
соответственно.
В документации для Sequence говорится, что протокол Sequence
не требует соответствия типов в отношении того, будет разрушительно поглощен итерацией. Как следствие, это означает, что итератор для Sequence
не требуется для создания копии, и поэтому я не думаю, что изменение последовательности во время ее повторения в целом безопасно.
Такой же оговорки нет в документации для Collection, но я также не думаю, что есть какая-то гарантия, что итератор сделает копию, и поэтому я не думаю, что изменение коллекции во время ее повторения в целом безопасно.
Но большинство типов коллекций в Swift — это struct
с семантикой значений или семантикой копирования при записи. Я не совсем уверен, где находится документация для этого, но эта ссылка говорит, что в Swift все Array
, String
и Dictionary
являются типами значений... Вам не нужно делать ничего особенного — например, создавать явную копию — чтобы предотвратить изменение этих данных другим кодом за вашей спиной. В частности, это означает, что для Array
.makeIterator()
не может содержать ссылку на ваш массив, потому что итератор для Array
не должен делать ничего особенного, чтобы предотвратить изменение данных другим кодом (т. е. вашим кодом). держит.
Мы можем исследовать это более подробно. Тип Iterator
для Array
определен как тип IndexingIterator<Array<Element>>
. В документации IndexingIterator говорится, что это реализация итератора по умолчанию для коллекций, поэтому мы можно предположить, что большинство коллекций будут использовать это. Мы можем увидеть в исходный код для IndexingIterator
что он содержит копию своей коллекции
@frozen
public struct IndexingIterator<Elements: Collection> {
@usableFromInline
internal let _elements: Elements
@usableFromInline
internal var _position: Elements.Index
@inlinable
@inline(__always)
/// Creates an iterator over the given collection.
public /// @testable
init(_elements: Elements) {
self._elements = _elements
self._position = _elements.startIndex
}
...
}
и что по умолчанию .makeIterator()
просто создает эту копию.
extension Collection where Iterator == IndexingIterator<Self> {
/// Returns an iterator over the elements of the collection.
@inlinable // trivial-implementation
@inline(__always)
public __consuming func makeIterator() -> IndexingIterator<Self> {
return IndexingIterator(_elements: self)
}
}
Хотя вы можете не доверять этому исходному коду, в документации по развитию библиотеки утверждается, что Атрибут @inlinable
— это обещание разработчика библиотеки, что текущее определение функции останется правильным при использовании с будущими версиями библиотеки, а @frozen
также означает, что члены IndexingIterator
не могут изменяться.
В целом это означает, что любой тип коллекции с семантикой значений и IndexingIterator
в качестве его Iterator
должен создавать копию при использовании циклов for in
(по крайней мере, до следующего разрыва ABI, который должен быть далеко ). Даже тогда я не думаю, что Apple изменит такое поведение.
В заключение
Я не знаю ни одного места, где в документах было бы явно указано, что вы можете изменять массив во время итерации по нему, и итерация будет продолжаться так, как если бы вы сделали копию, но это также тот язык, который, вероятно, не должен не следует записывать, так как написание такого кода определенно может запутать новичка.
Тем не менее, существует достаточно документации, в которой говорится, что цикл for in
просто вызывает .makeIterator()
и что для любой коллекции с семантикой значений и типом итератора по умолчанию (например, Array
) .makeIterator()
создает копию, и поэтому на него не может повлиять код внутри цикла. петля. Кроме того, поскольку Array
и некоторые другие типы, такие как Set
и Dictionary
, являются копируемыми при записи, изменение этих коллекций внутри цикла приведет к одноразовому штрафу за копирование, поскольку тело цикла не будет иметь уникальной ссылки на его хранилище (поскольку итератор будет). Это то же самое наказание, что и при изменении коллекции вне цикла, если у вас нет уникальной ссылки на хранилище.
Без этих предположений вам не гарантируется безопасность, но в некоторых случаях она все равно может быть.
Изменить:
Я только что понял, что мы можем создавать некоторые случаи, когда это небезопасно для последовательностей.
import Foundation
/// This is clearly fine and works as expected.
print("Test normal")
for _ in 0...10 {
let x: NSMutableArray = [0,1,2,3]
for i in x {
print(i)
}
}
/// This is also okay. Reassigning `x` does not mutate the reference that the iterator holds.
print("Test reassignment")
for _ in 0...10 {
var x: NSMutableArray = [0,1,2,3]
for i in x {
x = []
print(i)
}
}
/// This crashes. The iterator assumes that the last index it used is still valid, but after removing the objects, there are no valid indices.
print("Test removal")
for _ in 0...10 {
let x: NSMutableArray = [0,1,2,3]
for i in x {
x.removeAllObjects()
print(i)
}
}
/// This also crashes. `.enumerated()` gets a reference to `x` which it expects will not be modified behind its back.
print("Test removal enumerated")
for _ in 0...10 {
let x: NSMutableArray = [0,1,2,3]
for i in x.enumerated() {
x.removeAllObjects()
print(i)
}
}
Тот факт, что это NSMutableArray
, важен, потому что этот тип имеет ссылочную семантику. Поскольку NSMutableArray
соответствует Sequence
, мы знаем, что изменение последовательности во время ее повторения небезопасно, даже при использовании .enumerated()
.
person
deaton.dg
schedule
27.05.2021
enumerated
возвращает последовательность, так что это то, что вы повторяете, а не массив, который вы изменяете в цикле. Так что это два разных объекта. Если вы, с другой стороны, используете индексi
для доступа к исходному массиву, вы можете привести к сбою, какfor (i, st) in slist.enumerated() { slist.remove(at: i) }
- person Joakim Danielson   schedule 08.05.2021enumerated()
, и результат тот же (за исключением того, что у меня нет индекса для отображения). Так что должно быть больше. Это похоже на то, что для итерации будет выполняться копия массива. - person Christophe   schedule 08.05.2021