Questo è in effetti un comportamento previsto, ed è dovuto al fatto che uno Array
in Swift (così come molte altre raccolte nella libreria standard) è un tipo di valore con semantica copy-on-write. Ciò significa che il suo buffer sottostante (che è memorizzato indirettamente) sarà copiato dopo essere stato mutato (e, come ottimizzazione, solo quando non è referenziato in modo univoco).
(Si noti che questa risposta precedentemente affermato che la copia sarebbe accaduto dopo essere passato al iteratore che, come @MartinR giustamente osservato, non è ciò che accade.)
Quando si arriva a iterare su una Sequence
(come un array), che si tratti di forEach(_:)
o di un ciclo standard for in
, viene creato un iteratore dal metodo makeIterator()
della sequenza e il metodo next()
viene applicato ripetutamente per generare elementi in sequenza.
Si può pensare di iterazione su una sequenza come guardare in questo modo:
let sequence = [1, 2, 3, 4]
var iterator = sequence.makeIterator()
// next() will return the next element, or nil if it's reached the end of the sequence.
while let element = iterator.next() {
// do something with the element
}
Nel caso di Array
, un IndexingIterator
viene utilizzato come è iteratore - che iterare attraverso gli elementi di un dato di raccolta, semplicemente memorizzando la raccolta insieme all'indice corrente dell'iterazione. Ogni volta che viene chiamato next()
, la raccolta di base viene contrassegnata con l'indice, che viene poi incrementato fino a raggiungere endIndex
(è possibile vedere il suo exact implementation here).
Pertanto, quando si arriva a mutare l'array nel ciclo, il buffer sottostante è non con riferimento univoco, poiché l'iteratore ha anche una vista su di esso. Ciò impone una copia del buffer, che quindi usa myCollection
.
Quindi, ora ci sono due array: quello su cui viene ripetuto e quello che si sta modificando. Eventuali ulteriori mutazioni nel loop non attiveranno un'altra copia, purché il buffer di myCollection
rimanga referenziato in modo univoco.
Ciò significa che è perfettamente sicuro mutazione di una raccolta con semantica del valore mentre si enumera su di essa. L'enumerazione verrà ripetuta per tutta la lunghezza della raccolta, completamente indipendente da tutte le mutazioni che esegui, dato che verranno eseguite su una copia.
parola di avvertimento: 'removeItem (_ :)' è 'O (n)', come è 'foreach (_ :)'. Questa linea è 'O (n^2)' in totale. Potrebbe essere utile utilizzare l'aritmetica impostata. – Alexander
Non è necessariamente 'O (n)' - gli insiemi sono 'O (1)'. Fornisco il metodo 'removeItem (_ :)' (non esiste un metodo di questo tipo su una raccolta rapida) e faccio alcune altre cose di rimozione. –
Inoltre, questa raccolta contiene in genere 2-4 elementi, quindi ho preso consapevolmente il potenziale riscontro di runtime;) –