Uma biblioteca de gerenciamento de dependência inspirada no "ambiente" de Swiftui.
Essa biblioteca foi motivada e projetada ao longo de muitos episódios da Free Point, uma série de vídeos que explora a programação funcional e a linguagem Swift, hospedada por Brandon Williams e Stephen Celis.
As dependências são os tipos e funções em seu aplicativo que precisam interagir com os sistemas externos que você não controla. Exemplos clássicos disso são clientes da API que fazem solicitações de rede para servidores, mas também coisas aparentemente inócuas, como UUID
e Date
Initializers, acesso a arquivos, padrões de usuário e até relógios e temporizadores, podem ser considerados dependências.
Você pode chegar muito longe no desenvolvimento de aplicativos sem nunca pensar em gerenciamento de dependência (ou, como alguns gostam de chamá -lo de "injeção de dependência"), mas, eventualmente, dependências não controladas podem causar muitos problemas em sua base de código e ciclo de desenvolvimento:
Dependências não controladas dificultam a gravação de testes determinísticos e rápidos , porque você é suscetível aos caprichos do mundo exterior, como sistemas de arquivos, conectividade de rede, velocidade da Internet, tempo de atividade do servidor e muito mais.
Muitas dependências não funcionam bem nas visualizações de Swiftui , como gerentes de localização e reconhecedores de fala, e alguns não funcionam mesmo em simuladores , como gerentes de movimento e muito mais. Isso impede que você seja capaz de iterar facilmente no design de recursos se você usar essas estruturas.
Dependências que interagem com as bibliotecas de 3ª partes, sem manchas (como Firebase, bibliotecas de soquete da web, bibliotecas de rede etc.) tendem a ser pesadas e demoram muito para compilar . Isso pode desacelerar seu ciclo de desenvolvimento.
Por esses motivos, e muito mais, é altamente encorajado para você assumir o controle de suas dependências, em vez de deixá -las controlá -lo.
Mas, controlar uma dependência é apenas o começo. Depois de controlar suas dependências, você se depara com um conjunto de novos problemas:
Como você pode propagar dependências em toda a sua aplicação de uma maneira mais ergonômica do que passá -las explicitamente por toda parte, mas mais segura do que ter uma dependência global?
Como você pode substituir as dependências para apenas uma parte do seu aplicativo? Isso pode ser útil para substituir dependências para testes e visualizações SwiftUI, além de fluxos específicos de usuários, como experiências de integração.
Como você pode ter certeza de que substituiu todas as dependências que um recurso usa nos testes? Seria incorreto para um teste zombar de algumas dependências, mas deixou outros como interagindo com o mundo exterior.
Esta biblioteca aborda todos os pontos acima e muito, muito mais.
A biblioteca permite que você registre suas próprias dependências, mas também vem com muitas dependências controláveis fora da caixa (consulte DependencyValues
para uma lista completa) e há uma boa chance de você poder usar imediatamente um. Se você estiver usando Date()
, UUID()
, Task.sleep
ou Combine agendadores diretamente na lógica do seu recurso, você já poderá começar a usar essa biblioteca.
@ Observable
final class FeatureModel {
var items : [ Item ] = [ ]
@ ObservationIgnored
@ Dependency ( . continuousClock ) var clock // Controllable way to sleep a task
@ ObservationIgnored
@ Dependency ( . date . now ) var now // Controllable way to ask for current date
@ ObservationIgnored
@ Dependency ( . mainQueue ) var mainQueue // Controllable scheduling on main queue
@ ObservationIgnored
@ Dependency ( . uuid ) var uuid // Controllable UUID creation
// ...
}
Depois que suas dependências são declaradas, em vez de chegar à Date()
, UUID()
etc., diretamente, você pode usar a dependência definida no modelo do seu recurso:
@ Observable
final class FeatureModel {
// ...
func addButtonTapped ( ) async throws {
try await clock . sleep ( for : . seconds ( 1 ) ) // ? Don't use 'Task.sleep'
items . append (
Item (
id : uuid ( ) , // ? Don't use 'UUID()'
name : " " ,
createdAt : now // ? Don't use 'Date()'
)
)
}
}
Isso é o suficiente para começar a usar dependências controláveis em seus recursos. Com esse pouco de trabalho inicial, você pode começar a aproveitar os poderes da biblioteca.
Por exemplo, você pode controlar facilmente essas dependências nos testes. Se você deseja testar a lógica dentro do método addButtonTapped
, você pode usar a função withDependencies
para substituir quaisquer dependências para o escopo de um único teste. É tão fácil quanto 1-2-3:
@ Test
func add ( ) async throws {
let model = withDependencies {
// 1️⃣ Override any dependencies that your feature uses.
$0 . clock = . immediate
$0 . date . now = Date ( timeIntervalSinceReferenceDate : 1234567890 )
$0 . uuid = . incrementing
} operation : {
// 2️⃣ Construct the feature's model
FeatureModel ( )
}
// 3️⃣ The model now executes in a controlled environment of dependencies,
// and so we can make assertions against its behavior.
try await model . addButtonTapped ( )
#expect (
model . items == [
Item (
id : UUID ( uuidString : " 00000000-0000-0000-0000-000000000000 " ) ! ,
name : " " ,
createdAt : Date ( timeIntervalSinceReferenceDate : 1234567890 )
)
]
)
}
Aqui, controlamos a dependência date
para sempre retornar a mesma data e controlamos a dependência uuid
para retornar um UUID de incrementação automática toda vez que é invocada, e até controlamos a dependência clock
usando um ImmediateClock
para esmagar todo o tempo em um único instantâneo. Se não controlássemos essas dependências, esse teste seria muito difícil de escrever, pois não há como prever com precisão o que será retornado por Date()
e UUID()
, e teríamos que esperar o tempo do mundo real para passar, tornando o teste lento.
Mas, dependências controláveis não são úteis apenas para testes. Eles também podem ser usados nas visualizações do Xcode. Suponha que o recurso acima use um relógio para dormir por um tempo antes de algo acontecer na visualização. Se você não deseja literalmente esperar o tempo para passar para ver como a visualização muda, você pode substituir a dependência do relógio para ser um relógio "imediato" usando o traço de visualização .dependencies
:
#Preview (
traits : . dependencies {
$0 . continuousClock = . immediate
}
) {
// All access of '@Dependency(.continuousClock)' in this preview will
// use an immediate clock.
FeatureView ( model : FeatureModel ( ) )
}
Isso fará com que a pré -visualização use um relógio imediato quando executado, mas ao executar em um simulador ou em dispositivo, ele ainda usará um Live ContinuousClock
. Isso possibilita substituir as dependências apenas para visualizações sem afetar como seu aplicativo será executado na produção.
Esse é o básico para começar o uso da biblioteca, mas ainda há muito mais que você pode fazer. Você pode aprender mais em profundidade sobre a biblioteca explorando a documentação e os artigos:
Início rápido (o mesmo que as informações acima) : Aprenda o básico para começar a biblioteca antes de mergulhar profundamente em todos os seus recursos.
O que são dependências? : Aprenda o que são dependências, como elas complicam seu código e por que você deseja controlá -las.
Usando dependências : aprenda a usar as dependências registradas na biblioteca.
Registro dependências : Aprenda a registrar suas próprias dependências na biblioteca para que elas fiquem disponíveis imediatamente em qualquer parte da sua base de código.
Dependências ao vivo, visualização e teste : Aprenda a fornecer diferentes implementações de suas dependências para uso no aplicativo ao vivo, bem como nas visualizações do Xcode e até nos testes.
Teste : um dos principais motivos para controlar dependências é permitir um teste mais fácil. Aprenda algumas dicas e truques para escrever melhores testes com a biblioteca.
Projetando dependências : Aprenda técnicas para projetar suas dependências para que sejam mais flexíveis para injetar recursos e substituir para testes.
Dependências domésticas : Saiba como as dependências podem ser alteradas em tempo de execução, para que certas partes do seu aplicativo possam usar dependências diferentes.
Vida da dependência : Aprenda sobre as vidas das dependências, como prolongar a vida útil de uma dependência e como as dependências são herdadas.
Sistemas de ponto de entrada única : Aprenda sobre os sistemas "ponto de entrada única" e por que eles são mais adequados para esta biblioteca de dependências, embora seja possível usar a biblioteca com sistemas de ponto de entrada que não são de um solteiro.
Reconstruímos o aplicativo de demonstração Scrumdinger da Apple usando as práticas modernas e melhores para o desenvolvimento Swiftui, incluindo o uso desta biblioteca para controlar dependências no acesso ao sistema de arquivos, temporizadores e APIs de reconhecimento de fala. Essa demonstração pode ser encontrada aqui.
A documentação mais recente para as APIs de dependências está disponível aqui.
Você pode adicionar dependências a um projeto Xcode, adicionando -o ao seu projeto como um pacote.
https://github.com/pointfreeco/swift-dependences
Se você deseja usar dependências em um projeto SWIFTPM, é tão simples quanto adicioná -lo ao seu Package.swift
:
dependencies: [
. package ( url : " https://github.com/pointfreeco/swift-dependencies " , from : " 1.0.0 " )
]
E depois adicionar o produto a qualquer destino que precise de acesso à biblioteca:
. product ( name : " Dependencies " , package : " swift-dependencies " ) ,
Se você deseja discutir esta biblioteca ou tiver uma dúvida sobre como usá-la para resolver um problema específico, há vários lugares que você pode discutir com colegas entusiastas sem pontos:
Esta biblioteca controla uma série de dependências prontas para uso, mas também está aberta à extensão. Todos os projetos a seguir se baseiam em cima das dependências:
Existem muitas outras bibliotecas de injeção de dependência na comunidade Swift. Cada um tem seu próprio conjunto de prioridades e compensações que diferem das dependências. Aqui estão alguns exemplos conhecidos:
Esta biblioteca é lançada sob a licença do MIT. Consulte a licença para obter detalhes.