let numbersArray = [0, 1, 2]
for number in numbersArray { print(number) }
for
cycles, the type of the variable must implement the Sequence
protocol . Among other things, this protocol requires the type to have a associatedtype
Iterator
, which in turn must implement the requirements of the IteratorProtocol
protocol, as well as the factory method makeIterator()
, which returns a specific “iterator” for this type: protocol Sequence { associatedtype Iterator : IteratorProtocol func makeIterator() -> Self.Iterator // Another requirements go here… }
IteratorProtocol
protocol, in turn, contains only one method - next()
, which returns the next element in the sequence: protocol IteratorProtocol { associatedtype Element mutating func next() -> Self.Element? }
Array
type implements the Sequence
protocol (though not directly, but through the protocol inheritance chain: MutableCollection
inherits Collection
requirements, and that is Sequence
requirements), so its instances, in particular, can be “iterated” using for
cycles. struct Book { let author: String let title: String } struct Shelf { var books: [Book] }
Shelf
class, this class must comply with the requirements of the Sequence
protocol. For this example, it will be enough just to implement the method makeIterator()
, especially since the other protocol requirements have default implementations . This method should return an instance of the type implementing the IteratorProtocol
protocol. Fortunately, in the case of Swift, this is very little very simple code: struct ShelfIterator: IteratorProtocol { private var books: [Book] init(books: [Book]) { self.books = books } mutating func next() -> Book? { // TODO: Return next underlying Book element. } } extension Shelf: Sequence { func makeIterator() -> ShelfIterator { return ShelfIterator(books: books) } }
next()
method of the ShelfIterator
type ShelfIterator
declared mutating
, because an instance of the type must in one way or another store the state corresponding to the current iteration: mutating func next() -> Book? { defer { if !books.isEmpty { books.removeFirst() } } return books.first }
nil
if the sequence is empty. The defer
block defer
change code of the iterated collection, which deletes the element of the last iteration step immediately after the method returns. let book1 = Book(author: "Ф. Достоевский", title: "Идиот") let book2 = Book(author: "Ф. Достоевский", title: "Братья Карамазовы") let book3 = Book(author: "Ф. Достоевский", title: "Бедные люди") let shelf = Shelf(books: [book1, book2, book3]) for book in shelf { print("\(book.author) – \(book.title)") } /* Ф. Достоевский – Идиот Ф. Достоевский – Братья Карамазовы Ф. Достоевский – Бедные люди */
Array
, the underlying Shelf
) are based on the semantics of values (as opposed to references) , you can not worry that the value of the original variable will be changed during the iteration process. When dealing with types based on link semantics, this point should be kept in mind and taken into account when creating your own iterators.IteratorProtocol
: protocol ClassicIteratorProtocol: IteratorProtocol { var currentItem: Element? { get } var first: Element? { get } var isDone: Bool { get } }
struct ShelfIterator: ClassicIteratorProtocol { var currentItem: Book? = nil var first: Book? var isDone: Bool = false private var books: [Book] init(books: [Book]) { self.books = books first = books.first currentItem = books.first } mutating func next() -> Book? { currentItem = books.first books.removeFirst() isDone = books.isEmpty return books.first } }
next()
method changes the internal state of the iterator to go to the next element and is of type Void
, and the current element is returned by the currentElement()
method. In the IteratorProtocol
protocol, these two functions are combined into one.first()
method is also doubtful, since the iterator does not change the original sequence, and we always have the opportunity to refer to its first element (if present, of course).next()
method returns nil
, when the iteration is finished, the isDone()
method also becomes useless. func printShelf(with iterator: inout ShelfIterator) { var bookIndex = 0 while !iterator.isDone { bookIndex += 1 print("\(bookIndex). \(iterator.currentItem!.author) – \(iterator.currentItem!.title)") _ = iterator.next() } } var iterator = ShelfIterator(books: shelf.books) printShelf(with: &iterator) /* 1. Ф. Достоевский – Идиот 2. Ф. Достоевский – Братья Карамазовы 3. Ф. Достоевский – Бедные люди */
iterator
parameter is declared inout
, because its internal state changes during the execution of the function. And when a function is called, the iterator instance is not transmitted directly by its own value, but by reference.next()
method is not used, imitating the absence of the return value of the textbook implementation.Source: https://habr.com/ru/post/437614/