Ao criar apps com Swift, escrevemos dezenas de vezes por dia loops como for item in array. Como isso é tão natural, só costumamos perguntar o que acontece por dentro depois de tentar percorrer um tipo personalizado com for-in e encontrar um erro de compilação. “O que exatamente acontece dentro desse loop?”
Em resumo, o loop for-in do Swift é resultado da combinação dos protocolos Sequence e IteratorProtocol. Sequence declara “posso ser percorrido”, enquanto IteratorProtocol realmente extrai os valores: “vou entregar o próximo”.
Neste artigo, vamos ver como os protocolos dividem suas funções, como for-in é expandido internamente e como criar uma sequência personalizada. Se o padrão Iterator parece difícil, leia até o fim.
Qual é a diferença entre Sequence e IteratorProtocol?
Vamos começar pelo ponto mais confuso. Ambos têm relação com iteração, mas suas funções são claramente diferentes.
IteratorProtocol tem apenas um requisito: mutating func next() -> Element?. A cada chamada, retorna um elemento; quando não há mais nada, retorna nil.
Ou seja, o iterator é um “cursor que carrega estado”. Ele lembra até onde leu e avança uma posição sempre que next() é chamado.
Sequence está em um nível mais alto. Sua função é criar e entregar um iterator pelo método makeIterator(). É como dizer: “se quiser me percorrer, use este iterator”.
Resumo:
- IteratorProtocol: cursor que extrai valores um a um. O essencial é next().
- Sequence: fábrica que cria esse cursor. O essencial é makeIterator().
Array, dicionário, Set, strings, Range… Todos os tipos iteráveis da biblioteca padrão do Swift seguem Sequence.
Como for-in é expandido internamente?
Este é o ponto central do artigo. O for-in que usamos sem pensar é, na verdade, açúcar sintático (syntactic sugar).
Este é o loop que usamos normalmente.
for number in [1, 2, 3] {
print(number)
}
O compilador transforma esse código aproximadamente na forma abaixo: cria um iterator com makeIterator() e executa um loop while até next() retornar nil.
var iterator = [1, 2, 3].makeIterator()
while let number = iterator.next() {
print(number) // 1, 2, 3 imprimir em ordem
}
for-in não é mágica; no fim, é apenas a combinação de makeIterator() e next().
Conhecendo essa estrutura, fica natural entender por que um tipo que adota apenas Sequence pode ser usado diretamente em for-in: ele só precisa de makeIterator().
Como criar uma sequência personalizada?
Implementar uma ajuda a entender. Vamos usar como exemplo uma sequência que gera infinitamente a sequência de Fibonacci.
O jeito mais simples é adotar Sequence e IteratorProtocol no mesmo tipo pequeno. Assim, a implementação padrão de makeIterator() copia e retorna o próprio tipo.
struct Fibonacci: Sequence, IteratorProtocol {
var current = 0
var nextValue = 1
mutating func next() -> Int? {
defer { (current, nextValue) = (nextValue, current + nextValue) }
return current
}
}
O trabalho de next() é simples: usar defer para calcular antecipadamente o próximo estado antes de retornar o valor atual.
Agora o tipo pode ser usado diretamente em for-in. Como a sequência é infinita, você precisa limitar o número de iterações manualmente.
for n in Fibonacci().prefix(8) {
print(n) // 0 1 1 2 3 5 8 13
}
Também é importante poder usar métodos como prefix, map e filter gratuitamente. Ao adotar Sequence, essas operações padrão vêm pelas extensões do protocolo; não é preciso implementá-las.
Perguntas frequentes
P. Basta adotar Sequence ou IteratorProtocol também é obrigatório? R. Para executar for-in, você precisa de um iterator. Porém, como no exemplo acima, se um tipo adota os dois protocolos, não precisa escrever makeIterator() separadamente. Para separar o estado de iteração da coleção, extraia o iterator para um struct próprio.
P. O que acontece se eu percorrer novamente com for-in uma sequência já percorrida? R. Tipos por valor, como Array, criam um novo iterator a cada vez e podem ser percorridos várias vezes com segurança. Já sequências consumíveis, como um stream de rede, só podem ser percorridas uma vez.
P. Qual é a diferença em relação a Collection? R. Collection é um conceito superior que herda de Sequence. Ele exige mais recursos, como acesso repetido por índice, contagem com count e garantia de ordem. Se você só precisa percorrer valores em uma direção, Sequence é suficiente.
Por trás de uma linha de for-in existe uma divisão clara entre Sequence e IteratorProtocol: o iterator é o cursor, e a sequência é a fábrica. Quando você distingue os dois, criar tipos iteráveis personalizados fica muito mais fácil.
Na próxima vez que usar for-in, pense: “agora next() está sendo chamado”. Recomendo começar criando sua própria sequência de Fibonacci.

