Olá pessoal. Hoje queremos compartilhar a tradução preparada às vésperas do lançamento do curso “iOS Developer. Curso avançado . ” Vamos lá!

Uma das principais vantagens do projeto baseado em protocolo da Swift é que ele permite escrever código genérico compatível com uma ampla variedade de tipos, em vez de ser implementado especificamente para todos. Especialmente se esse código geral for destinado a um dos protocolos, que pode ser encontrado na biblioteca padrão, o que permitirá usá-lo com os tipos internos e os definidos pelo usuário.
Um exemplo desse protocolo é o Sequence, aceito por todos os tipos de bibliotecas padrão que podem ser iteradas, como Matriz, Dicionário, Conjunto e muitos outros. Nesta semana, vamos ver como podemos agrupar o Sequence em contêineres universais, o que nos permitirá encapsular vários algoritmos no núcleo das APIs fáceis de usar.
A arte de ser preguiçoso
É muito fácil ficar confuso ao pensar que todas as seqüências são semelhantes à Matriz, pois todos os elementos são instantaneamente carregados na memória quando a sequência é criada. Como o único requisito do protocolo Sequence é que os receptores possam iterar, não podemos fazer nenhuma suposição sobre como os elementos de uma sequência desconhecida são carregados ou armazenados.
Por exemplo, como abordamos em Swift Sequences: The Art of Being Lazy , às vezes as sequências carregam seus elementos preguiçosamente - por razões de desempenho ou porque não é garantido que a sequência inteira caiba na memória. Aqui estão alguns exemplos dessas seqüências:
Como todas as seqüências acima são preguiçosas por algum motivo, não queremos forçá-las a uma matriz, por exemplo, chamando Array (folder.subpolders). Mas ainda podemos modificar e trabalhar com eles de maneiras diferentes, então vamos ver como podemos fazer isso criando um tipo de wrapper de sequência.
Criação da fundação
Vamos começar criando um tipo básico que podemos usar para criar todos os tipos de APIs convenientes sobre qualquer sequência. Vamos chamá-lo de WrappedSequence, e será um tipo universal que contém o tipo de sequência que envolvemos e o tipo de elemento que queremos que nossa nova sequência crie.
O principal recurso do nosso wrapper será sua IteratorFunction, que nos permitirá controlar a pesquisa da sequência base - alterando o Iterator usado para cada iteração:
struct WrappedSequence<Wrapped: Sequence, Element>: Sequence { typealias IteratorFunction = (inout Wrapped.Iterator) -> Element? private let wrapped: Wrapped private let iterator: IteratorFunction init(wrapping wrapped: Wrapped, iterator: @escaping IteratorFunction) { self.wrapped = wrapped self.iterator = iterator } func makeIterator() -> AnyIterator<Element> { var wrappedIterator = wrapped.makeIterator() return AnyIterator { self.iterator(&wrappedIterator) } } }
Como você pode ver acima, Sequence usa um padrão de fábrica para que cada sequência crie uma nova instância do iterador para cada iteração - usando o método makeIterator ().
Acima, usamos o tipo AnyIterator da biblioteca padrão, que é um iterador do tipo apagamento que pode usar qualquer implementação básica de IteratorProtocol para obter valores de elemento. No nosso caso, criaremos um elemento chamando nossa IteratorFunction, passando como argumento nosso próprio iterador da sequência agrupada e, como esse argumento é marcado como inout, podemos alterar o iterador base no lugar dentro de nossa função.
Como WrappedSequence também é uma sequência, podemos usar toda a funcionalidade da biblioteca padrão relacionada a ela, como iterar sobre ela ou transformar seus valores usando o mapa:
let folderNames = WrappedSequence(wrapping: folders) { iterator in return iterator.next()?.name } for name in folderNames { ... } let uppercasedNames = folderNames.map { $0.uppercased() }
Agora vamos começar com o nosso novo WrappedSequence!
Prefixos e sufixos
Ao trabalhar com sequências com muita frequência, existe o desejo de inserir um prefixo ou sufixo na sequência com a qual trabalhamos - mas não seria ótimo se pudéssemos fazer isso sem alterar a sequência principal? Isso pode levar a um melhor desempenho e nos permite adicionar prefixos e sufixos a qualquer sequência, e não apenas tipos gerais, como Array.
Usando WrappedSequence, podemos fazer isso facilmente. Tudo o que precisamos fazer é estender Sequence com um método que cria uma sequência agrupada a partir de uma matriz de elementos para inserir como prefixo. Então, quando iteramos, começamos a iterar sobre todos os elementos do prefixo antes de continuar com a sequência base - assim:
extension Sequence { func prefixed( with prefixElements: Element... ) -> WrappedSequence<Self, Element> { var prefixIndex = 0 return WrappedSequence(wrapping: self) { iterator in
Acima, usamos um parâmetro com um número variável de argumentos (adicionando ... ao seu tipo) para permitir a transmissão de um ou mais elementos para o mesmo método.
Da mesma forma, podemos criar um método que adiciona um determinado conjunto de sufixos ao final da sequência - primeiro executando nossa própria iteração da sequência base e depois iterando sobre os elementos com sufixo:
extension Sequence { func suffixed( with suffixElements: Element... ) -> WrappedSequence<Self, Element> { var suffixIndex = 0 return WrappedSequence(wrapping: self) { iterator in guard let next = iterator.next() else {
Com os dois métodos mencionados acima, agora podemos adicionar prefixos e sufixos a qualquer sequência que desejarmos. Aqui estão alguns exemplos de como nossas novas APIs podem ser usadas:
Embora todos os exemplos acima possam ser implementados usando tipos específicos (como Array e String), a vantagem de usar o tipo WrappedSequence é que tudo pode ser feito preguiçosamente - não realizamos nenhuma mutação ou avaliamos nenhuma sequência para adicionar nossa prefixos ou sufixos - que podem ser realmente úteis em situações críticas ao desempenho ou ao trabalhar com grandes conjuntos de dados.
Segmentação
A seguir, veremos como podemos agrupar seqüências para criar versões segmentadas delas. Em certas iterações, não basta saber qual é o elemento atual - também podemos precisar de informações sobre os elementos seguintes e anteriores.
Ao trabalhar com sequências indexadas, geralmente conseguimos isso usando a API enumerated (), que também usa um wrapper de sequência para nos dar acesso ao elemento atual e seu índice:
for (index, current) in list.items.enumerated() { let previous = (index > 0) ? list.items[index - 1] : nil let next = (index < list.items.count - 1) ? list.items[index + 1] : nil ... }
No entanto, a técnica mencionada não é apenas bastante detalhada em termos de invocação, mas também conta com o uso de matrizes novamente - ou pelo menos com alguma forma de sequência que nos dá acesso aleatório a seus elementos - que muitas sequências, especialmente as preguiçosas, não é bem vindo.
Em vez disso, vamos usar nosso WrappedSequence novamente - para criar um wrapper de sequência que preguiçosamente forneça visualizações segmentadas em sua sequência base, rastreando elementos anteriores e atuais e atualizando-os à medida que continua a iterar:
extension Sequence { typealias Segment = ( previous: Element?, current: Element, next: Element? ) var segmented: WrappedSequence<Self, Segment> { var previous: Element? var current: Element? var endReached = false return WrappedSequence(wrapping: self) { iterator in
Agora, podemos usar a API acima para criar uma versão segmentada de qualquer sequência sempre que precisarmos olhar para frente ou para trás ao fazer uma iteração. Por exemplo, eis como podemos usar a segmentação para determinar facilmente quando chegamos ao final da lista:
for segment in list.items.segmented { addTopBorder() addView(for: segment.current) if segment.next == nil { // , addBottomBorder() } } ```swift , . , : ```swift for segment in path.nodes.segmented { let directions = ( enter: segment.previous?.direction(to: segment.current), exit: segment.next.map(segment.current.direction) ) let nodeView = NodeView(directions: directions) nodeView.center = segment.current.position.cgPoint view.addSubview(nodeView) }
Agora estamos começando a ver o verdadeiro poder das sequências de quebra automática - pois elas nos permitem ocultar algoritmos cada vez mais complexos em uma API realmente simples. Tudo que o chamador precisa para segmentar a sequência é acessar a propriedade segmentada em qualquer Sequência e nossa implementação básica cuidará do resto.
Recursão
Finalmente, vamos ver como até iterações recursivas podem ser modeladas usando wrappers de sequência. Suponha que desejássemos fornecer uma maneira simples de iterar recursivamente sobre uma hierarquia de valores na qual cada elemento da hierarquia contém uma sequência de elementos filhos. Pode ser bastante difícil fazê-lo corretamente, portanto seria ótimo se pudéssemos usar uma implementação para executar todas essas iterações em nossa base de código.
Usando WrappedSequence, podemos conseguir isso estendendo Sequence com um método que usa a mesma restrição de tipo genérico para garantir que cada elemento possa fornecer uma sequência aninhada que tenha o mesmo tipo de iterador que o original. Para poder acessar dinamicamente cada sequência aninhada, também solicitaremos ao chamador que especifique KeyPath para a propriedade que deve ser usada para recursão, o que nos dará uma implementação semelhante a esta:
extension Sequence { func recursive<S: Sequence>( for keyPath: KeyPath<Element, S> ) -> WrappedSequence<Self, Element> where S.Iterator == Iterator { var parentIterators = [Iterator]() func moveUp() -> (iterator: Iterator, element: Element)? { guard !parentIterators.isEmpty else { return nil } var iterator = parentIterators.removeLast() guard let element = iterator.next() else {
Usando o exposto, agora podemos iterar recursivamente sobre qualquer sequência, independentemente de como ela é construída internamente, e sem precisar carregar a hierarquia inteira antecipadamente. Por exemplo, eis como podemos usar essa nova API para iterar recursivamente por uma hierarquia de pastas:
let allFolders = folder.subfolders.recursive(for: \.subfolders) for folder in allFolders { try loadContent(from: folder) }
Também podemos usá-lo para percorrer todos os nós da árvore ou percorrer recursivamente um conjunto de registros de banco de dados - por exemplo, para listar todos os grupos de usuários em uma organização:
let allNodes = tree.recursive(for: \.children) let allGroups = database.groups.recusive(for: \.subgroups)
Uma coisa que precisamos ter cuidado quando se trata de iterações recursivas é evitar referências circulares - quando um determinado caminho nos retorna a um elemento que já encontramos - o que nos levará a um loop infinito.
Uma maneira de corrigir isso é acompanhar todos os elementos que ocorrem (mas pode ser problemático do ponto de vista da memória), para garantir que não haja referências circulares em nosso conjunto de dados ou para lidar com esses casos sempre do lado da chamada (usando break, continue ou retorne para concluir qualquer iterações cíclicas).
Conclusão
A sequência é um dos protocolos mais simples da biblioteca padrão - requer apenas um método - mas ainda é um dos mais poderosos, especialmente quando se trata de quanta funcionalidade podemos criar com base nela. Assim como a biblioteca padrão contém sequências de wrapper para coisas como enumerações, também podemos criar nossos próprios wrappers - o que nos permite ocultar a funcionalidade avançada com APIs realmente simples.
Embora as abstrações sempre tenham um preço, é importante considerar quando vale a pena (e talvez mais importante quando não vale a pena) apresentá-las, se pudermos construir nossas abstrações diretamente sobre o que a biblioteca padrão fornece - usando as mesmas convenções -, então estas as abstrações geralmente têm maior probabilidade de resistir ao teste do tempo.