☝️ Esse artigo não visa ensinar nenhum conceito ou técnica diretamente. Ele é muito mais um relato pessoal da minha experiência no aprendizado de alguns conceitos e paradigmas. Se você não tem ideia do que é programação reativa, eu recomendo fortemente esse guia.
✌️ A maioria dos exemplos contidos nesse artigo foram retirados de projetos reais e de documentações ou exemplos providos pelos autores das bibliotecas citadas. Todos os respectivos links estão disponíveis no final do artigo.
Passado
Programo para iOS desde 2009. Até 8 meses atrás, Objective-C (e o básico de C) eram as duas únicas linguagens que eu sabia programar.
E por muitos anos a minha abordagem para desenvolver software sempre envolveu: Objective-C, programação 100% orientada a objetos e programação imperativa. Foi assim que aprendi a programar e foi assim que sempre programei.
Presente
Em junho de 2015, logo após o lançamento do Swift 2.0, entrei num projeto 100% escrito em Swift (1.2). Era meu primeiro contato real com Swift.
Opinião: aprender Swift é como aprender qualquer outra linguagem de programação. Sua sintaxe é relativamente simples de aprender e já há muita documentação e exemplos na internet. Não acho que Swift deveria ser barreira para ninguém: é algo que se aprende (o básico) em poucos dias. E é questão de dias para você ser produtivo usando Swift.
Porém, a camada de comunicação com o servidor desse aplicativo era construída em cima de uma biblioteca escrita chamada BrightFutures. E com isso me deparei com dois outros conceitos que eu nunca havia estudado (muito menos usado): programação funcional e programação reativa.
Opinião: o resumo de programação funcional é, para mim, algo do tipo: uma mudança de pensamento que, ao invés de você pensar em classes e objetos e na interação entre essas entidades, você pensa muito mais em funções e composição de funções. É um tema bastante extenso. Pela minha experiência, isso é algo que leva um certo tempo para aprender e requer disciplina para ler teorias e documentações.
E por fim: programação reativa. Até então, eu nunca tinha conseguido entender exatamete o que era programação reativa. Todas a vezes que tentei usar ReactiveCocoa desisti no meio por perder o controle do que estava acontecendo.
Opinião:
Futuro
Voltando ao BrightFutures
: na época, mesmo sem saber nada de programação funcional e programação reativa, foi fácil para eu entender a teoria e a filosofia por trás do framework: prover uma forma de tratar com código assíncrono e tratamento de erros através de futuros e promessas.
E foi aí que comecei a entender um pouco o que significava programação funcional e porque o Swift era uma linguagem que permitia a implementação e o uso de conceitos funcionais.
Vamos começar a viajar um pouco. Essa é a hora ideal para uma dose de cafeína ☕️.
Imagine o caso em que você tem uma série de eventos futuros que você quer que sejam encadeados, respeitando (ou não) uma ordem e que no final desse encadeamento de eventos (ou seja, no futuro) você quer ser notificado.
Por exemplo: um aplicativo em que o usuário pode logar com a sua conta e baixar todos os seus posts. Temos então um evento de logIn
que, em caso de sucesso, dispara um evento de fetchPosts
para aquele usuário. Tanto o evento de logIn
como o de fetchPosts
seriam funções que retornam um “Futuro” (no caso do BrightFutures, um Future
). O resultado da execução do logIn
, por exemplo, é um future
que representa um erro (em caso de erro) ou um userId
(em caso de sucesso). No caso do fetchPosts
, o retorno seria um future
representando um erro (falha) ou um array de posts (sucesso).
Antes mesmo de ler sequer uma linha de código, a abstração acima faz sentido. O problema agora é como programar usando essa “arquitetura”. No exemplo acima, o código seria algo assim (código retirado do repositório do Bright Futures):
User.logIn(username,password).flatMap { user in
Posts.fetchPosts(user)
}.onSuccess { posts in
// do something with the user's posts
}.onFailure { error in
// either logging in or fetching posts failed
}
A assinatura das funções User.logIn
e Posts.fetchPosts
seria algo assim:
func logIn(username: String, password: String) -> Future<User, ErrorType>
func fetchPosts(user: User) -> Future<[Posts], ErrorType>
Se você não estiver acostumado com a sintaxe do Swift ou o código acima parecer muito confuso para você, essa é a explicação desse código (traduzido do repositório do Bright Futures):
Quando o futuro retornado por User.logIn
falha (por exemplo, se o username
e password
não estiverem corretos), tanto o flatMap
como o onSuccess
são pulados, e o closure onFailure
é chamado com o error
que ocorreu na tentativa de logIn
. Se a tentativa de realizar o logIn
for bem sucedida, o resultado da operação (que é um objeto user
) é passado para o flatMap
, que “transforma” o usuário em um array com seus posts. Se os posts não puderem ser baixados (por causa de um erro), onSuccess
é pulado, e onFailure
é chamado com o error
que ocorreu na tentativa de baixar os posts. Se os posts puderem ser baixados com sucesso, onSuccess
é chamado com os posts do usuário.
Opinião: o código pode parecer estranho a princípio, mas novamente a teoria faz sentido. E por isso que acho que tanto programação funcional como programação reativa são dois conceitos que requerem estudo da teoria antes da prática. É um pouco diferente de aprender uma linguagem de programação nova (como Swift), que algo 100% hands on pode ser efetivo.
Falando nisso, o flatMap
aí em cima é um dos caras que fazem parte dos conceitos funcionais. Caso você não o entenda (ainda!), a idéia é que ele “transforma” (ou “mapeia”) o resultado de um Future
no valor de um novo Future
. Ou seja, ele transforma o resultado do logIn
(que é um user
) no valor de entrada para o fetchPosts
(que por sua vez retorna outro Future
).
E é aí que entra a mágica do Swift ser tão restrito em relação a tipos (ou seja, ser uma linguagem type safe, totalmente diferente do Objective-C): o compilador consegue te garantir que os tipos de dados dos valores retornados por uma função e passados para outra função estão sempre corretos. E a partir daí, o atalho preferido do Xcode passa a ser ⌥ + click
:
Resumindo
Futures
são ações futuras (assíncronas, com tratamento de erros). O paradigma da programação funcional permite transformar e encadear essas ações de uma forma clara e segura. Além disso, ao trabalhar com Futures
, estamos trabalhando de uma forma “reativa”. Em outras palavras, estamos programando baseado no “retorno” dos futures
. Esses futures
se responsabilizam de fazer o trabalho deles assincronamente. E ao encadear esses futures
, estamos trabalhando com os conceitos funcionais. Por isso que, apesar de diferentes, esses dois termos são vistos comumente juntos: Programação Reativa Funcional.
RxSwift
RxSwift é a versão escrita em Swift do ReactiveX. O ReactiveX por sua vez é uma biblioteca que possibilita o uso de programação baseada em eventos (reativa), de forma assíncrona e que pode ser composta e encadeada (funcional) através de operadores.
Opinião: novamente, a melhor forma - na minha opinião - de aprender os conceitos básicos de programação reativa é esse link: The introduction to Reactive Programming you’ve been missing.
A idéia básica da programação reativa é que você trabalha em cima de streams (fluxos, correntes, sequências) de dados. Cada stream é uma sequência de eventos que acontecem em uma linha de tempo e podem emitir três tipos de “coisas”: um valor, um erro ou um sinal de finalizado.
No nosso exemplo anterior, usando BrightFutures, a nossa entidade Future
nada mais é do que um stream, que pode produzir um valor (e um sinal de “finalizado” logo em seguida) ou um erro.
Com o RxSwift
, porém, as coisas são mais amplas que apenas Futures
. Você pode definir outros tipos de streams. Um bom exemplo é um UITextField
: ao invés de programar pensando nos conceitos de delegates, o RxSwift
te permite observar os valores do stream rx_text
do UITextField
e reagir de acordo com a emissão desses valores. Veja um exemplo:
let lengthOfBytes =
textView.rx_text
.map { $0.lengthOfBytesUsingEncoding(NSUTF8StringEncoding) }
.filter { $0 > 5 }
Com o código acima, estamos definindo que a cada vez que rx_text
emitir um valor, nós iremos mapear (ou seja, transformar) esse valor em um Int
, e então iremos filtrar esse valor, seguindo adiante apenas se ele for maior que 5.
Fazendo novamente a comparação com Futures
, o RxSwift
encapsula seus streams em Observables
. Ou seja, o tipo de dado de rx_text
é um Observable<String>
, enquanto, no exemplo acima, o tipo de dados de lengthOfBytes
é um Observable<Int>
(ou seja, é um stream também):
A última peça que falta nesse quebra-cabeças do RxSwift
é que nada acontece até você dar um subscribe no seu stream (ou na sua sequência de streams). Antes disso, você está apenas definindo as ações que você irá tomar, baseada nos eventos que podem acontecer. O subscribe é o sinal verde para que o Observable
comece a emitir itens:
self.messagesHandler.messages
.window(timeSpan: 0.5, count: 10000, scheduler: MainScheduler.instance)
.flatMap { observable -> Observable<ServerMessage> in
observable.take(1)
}
.subscribeNext { [weak self] message in
self?.playMessageArrivedSound()
}
}.addDisposableTo(disposeBag)
O
disposeBag
, a grosso modo, é a forma doRxSwift
liberar os recursos alocados, quando você acabar de observar a sequência (seja por opção ou porque ela terminou). Não vou entrar em detalhes sobre como usar odispose
doRxSwift
. Não é algo muito complicado e você pode ler mais sobre o assunto aqui.
O subscribeNext
é a forma que o seu Observer
fala “a cada valor emitido, faça isso”. O código acima é um exemplo de como observar mensagens recebidas em um aplicativo de mensagens instantâneas e disparar um som cada vez que uma mensagem chega. Os operadores window
e flatMap
garantem que haja um intervalo mínimo de 0.5 segundo entre cada som disparado (para saber mais, veja essa pergunta no StackOverflow). No exemplo acima, não estamos reagindo em caso de erro, nem para os sinais de completed
, já que, em teoria, esse stream de mensagens recebidas nunca encerra.
Uma forma comum de se usar esse paradigma de observar streams
é com binding. Data binding é o processo de “conectar” a informação apresentada na sua UI com o seu modelo ou lógica de negócios.
Usando o RxSwift
por exemplo, a implementação de uma tela de Settings poderia ser a seguinte:
//bind, onde nameLabel = firstName + " " + lastName
Observable
.combineLatest(firstNameTextField.rx_text, lastNameTextField.rx_text) {
$0 + " " + $1
}.bindTo(nameLabel.rx_text)
.addDisposableTo(disposeBag)
//bind, onde mapeamos o valor do Switch em um emoji:
settingsSwitch.rx_value
.map {
$0 ? "👍" : "👎"
}.bindTo(settingsLabel.rx_text)
.addDisposableTo(disposeBag)
Se a sintaxe de
$0
e$1
for meio etranha pra você, eles são recursos do próprio Swift. Recomendo a leitura do capítulo de Closures do The Swift Programming Language.
E esse é o resultado:
E esse é só o começo
Acredite: esses são apenas os primeiros passos dentro do mundo de programação reativa e/ou funcional. Ainda existe muito a ser explorado e muito, muito a ser aprendido. Algo que tem me ajudado bastante nesse processo de aprendizado é ter a consciência de que o tema é extenso e não se aprende da noite pro dia (diferente de aprender uma nova linguagem de programação, por exemplo). A verdade é que Programação Funcional e Reativa são conceitos longos e complexos e que levam tempo para serem assimilados. Mas acredito que uma vez que você aprenda a teoria, a escolha de bibliotecas seja um “mero” detalhe.
Espero que compartilhar a minha experiência possa ser útil para você começar a entender um pouco mais desses paradigmas de programação. Caso queira se aprofundar mais, coloquei alguns links nas Referências abaixo.
Agradecimentos
- Lars por me ensinar Swift e os conceitos do BrightFutures;
- Thomas por ter escrito a BrightFutures;
- Mentos por ter sido a inspiração para eu criar meu snippet
;rac
no TextExpander.
Referências
Fontes utilizadas para a escrita deste artigo:
- Bright Futures
- RxSwift
- Slack do RxSwift
- The introduction to Reactive Programming you’ve been missing
- ReactiveX
- RxMarbles
Obrigado pela leitura!
Bruno Koga
@brunokoga
http://www.brunokoga.com