Desenvolvimento

1 abr, 2016

RxSwift: como eu vim parar aqui?

Publicidade

Este artigo foi publicado no equinociOS, promovido pelo Cocoaheads-Br.

  • Este artigo não visa a 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 este guia.
  • A maioria dos exemplos contidos neste 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 em 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.

swift-1

Logo eu me vingarei!

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, em vez de 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 exatamente 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:

swift-2

meu snippet para ;rac no TextExpander

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 por meio de futuros e promessas.

E foi aí que comecei a entender um pouco o que significava programação funcional e por que 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 textos. 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 i 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 textos (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ê, esta é a explicação desse código (traduzido do repositório do Bright Futures):

Quando o futuro retornado por User.logIn falha (por exemplo, se 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 textos. Se os textos 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 textos. Se os textos puderem ser baixados com sucesso, onSuccess é chamado com os textos 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 ideia é 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 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:

typesafe1

O compilador está do nosso lado! Após concatenar várias funções, você (quase) sempre pode confiar nele para dizer o tipo do retorno das suas funções. Essa técnica é muito útil para você checar se o tipo retornado é mesmo o que você espera.

typesafe2

A mesma estratégia funciona também para os parâmetros das funções concatenadas. Mesmo que você não entenda o código acima, a ideia é que você tem a segurança de saber que está trabalhando com o tipo de dado correto (diferentemente do Objective-C, onde não há essa garantia).

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) por meio de operadores.

Opinião: novamente, a melhor forma – na minha opinião – de aprender os conceitos básicos de programação reativa é este link: The introduction to Reactive Programming you’ve been missing.

A ideia 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: em vez 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):

rxswift1

Lembre-se: ⌥ + click é seu melhor amigo!

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 irá tomar, baseado 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 de o RxSwift 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 o dispose do RxSwift. Não é algo muito complicado e você pode ler mais sobre o assunto aqui.

O subscribeNext é a forma como 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 esta 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 estranha pra você, eles são recursos do próprio Swift. Recomendo a leitura do capítulo de Closures do The Swift Programming Language.

E este é o resultado:

bind

Suas telas de Settings nunca mais serão as mesmas! ✨

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 (diferentemente 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

Referências

Fontes utilizadas para a escrita deste artigo:

Obrigado pela leitura!