Back-End

2 fev, 2017

Introduzindo Ohana, Biblioteca de Contatos iOS da Engenharia Uber

Publicidade

 

Dentro do ecossistema móvel da Uber, estamos sempre tentando projetar nossa arquitetura para reutilização. Nós nos beneficiamos muito do código adequadamente modularizado porque estamos constantemente executando experimentos para nos ajudar a tomar decisões de produtos em nossos variados mercados e tipos de pessoas que atendemos. Ao longo do ano passado, trabalhamos de forma iterativa no Ohana, um framework para iOS para recuperar e formatar informações de contato, que deixamos com código aberto para que outros pudessem construir em cima dela.

Ohana, que significa família em havaiano, fornece ferramentas para facilmente organizar e apresentar informações de contatos do catálogo de endereços do iOS. É um conjunto de ferramentas de dados, não um widget de interface do usuário. Ohana é parte de um esforço mais amplo para modularizar tanto a lógica quanto a interface do usuário na exibição de contatos para os usuários do aplicativo iOS do Uber para o motorista. Aqui apresentamos mais sobre o que é Ohana e como ele pode ser uma parte útil da arquitetura do app iOS. Vamos apresentar uma visão geral e o que os seus componentes fazem, com algumas das interfaces de código fonte caso você queira usá-las para sua aplicação.

Arquitetura

A arquitetura de alto nível do Ohana filtra dados de várias fontes (abstraídos dos Provedores de Dados), através de um conjunto ordenado de pós-processadores para transformar os dados de acordo com uma necessidade específica do usuário, como, por exemplo, exibi-lo no aplicativo:

Ohana usa UberSignals (uma implementação do padrão observer) como sua principal mecânica de fluxo de dados. Ohana é estruturado com um pequeno conjunto de protocolos implementáveis (DataProvider, PostProcessor, SelectionFilter) que permitem que o aplicativo consumidor personalize seu comportamento. Nós ordenamos a explicação de seus componentes, em ordem de precedência de importância para como você usaria Ohana.

OHContact

Este é o modelo de dados para um contato e é a saída do sistema: o resultado final. OHContact contém propriedades para campos como nome e sobrenome que são comuns na maioria dos contatos, bem como uma lista de OHContactFields para os campos nomeados arbitrariamente como ‘iPhone’ ou ‘fax’:

@interface OHContact : NSObject <NSCopying>

@property (nonatomic, nullable) NSString *firstName;
@property (nonatomic, nullable) NSString *lastName;
@property (nonatomic, nullable) NSOrderedSet<OHContactField *> *contactFields;
// properties omitted

– (BOOL)isEqualToContact:(OHContact *)contact;

@end

OHContactsDataSource

O Data Source é de onde você recupera os contatos. É um Data Source do jeito Cocoa; é o que fornece os dados de contatos finais para sua aplicação. Data Source é a classe que contém o núcleo da implementação interna de Ohana. Ela dispara o carregamento de contato através dos Data Providers e usa os Pós Processadores para configurar seus dados. Uma vez que os contatos foram carregados e processados, o onContactsDataSourceReadySignal é acionado e a lista de contatos é preenchida. Os consumidores do framework encaixam esses Data Sources personalizados e Pós Processadores neste objeto para configurar o Ohana para seu caso de uso:

@interface OHContactsDataSource : NSObject

@property (nonatomic, readonly) OHContactsDataSourceReadySignal *onContactsDataSourceReadySignal;
@property (nonatomic, readonly, nullable) NSOrderedSet<OHContact *> *contacts;

– (instancetype)initWithDataProviders:(NSOrderedSet<id<OHContactsDataProviderProtocol>> *)dataProviders postProcessors:(NSOrderedSet<id<OHContactsPostProcessorProtocol>> *)postProcessors;

– (void)loadContacts;

// properties omitted

@end

OHContactsDataProviderProtocol

O protocolo Data Provider descreve uma interface para recuperar informações de contato de uma fonte de sua escolha. Ohana fornece implementações padrão para usar o catálogo de endereços do iOS via ABAddressBook ou API CNContacts. Você pode usar vários provedores de dados ao mesmo tempo. Você poderia, por exemplo, implementar seu próprio provedor de dados que recupera contatos do Facebook e os intercala com os contatos do sistema:

@protocol OHContactsDataProviderProtocol <NSObject>

@property (nonatomic, readonly) OHContactsDataProviderFinishedLoadingSignal *onContactsDataProviderFinishedLoadingSignal;
@property (nonatomic, readonly, nullable) NSOrderedSet<OHContact *> *contacts;

– (void)loadContacts;

// properties omitted

@end

OHContactsPostProcessorProtocol

O protocolo Post Process descreve uma interface para filtrar os dados a serem expostos ao consumidor. Um pós-processador pode filtrar somente contatos que têm números de telefone associados ou reordenar contatos de entrada para serem exibidos em ordem alfabética. Você poderia, então, encadear esses pós processadores para que os dados refletissem ambas as transformações. Esses e muitos outros pós-processadores úteis estão disponíveis por padrão em Ohana.

O poder real do sistema surge quando o consumidor implementa seus próprios pós-processadores para suas necessidades personalizadas. Deseja filtrar os contatos com um determinado código de área? Deduplicar contatos por endereço compartilhado? Garantir a exibição apenas dos contatos que têm todos os campos que você deseja? Todos esses casos de uso se encaixam bem dentro da API. (Depois de construí-los, considere fazer um pull request!).

@protocol OHContactsPostProcessorProtocol <NSObject>

– (NSOrderedSet<OHContact *> *)processContacts:(NSOrderedSet<OHContact *> *)preProcessedContacts;

@end

Aqui está um exemplo de filtragem se você tivesse o seguinte conjunto de dados …

{ “first”: “Marge”, “last”: “Simpson”, “image”: <UIImage> }
{ “first”: “Mom”, “last”: nil, “image”: <UIImage> }
{ “first”: “Wednesday”, “last”: “Addams”, “image”: <UIImage> }
{ “first”: “Homer”, “last”: “Simpson”, “image”: <UIImage> }
{ “first”: “Maggie”, “last”: “Simpson”, “image”: nil }
{ “first”: “Morticia”, “last”: “Addams”, “image”: <UIImage> }
{ “first”: “Gomez”, “last”: “Addams”, “image”: <UIImage> }
{ “first”: “bae”, “last”: nil, “image”: nil }

… e quisesse filtrar os dados completos e alterar os dados a serem enviados para um servidor em um formato diferente, como o seguinte:

{ “full_name”: “Morticia Addams”, “image”: <UIImage> }
{ “full_name”: “Gomez Addams”, “image”: <UIImage> }
{ “full_name”: “Wednesday Addams”, “image”: <UIImage> }
{ “full_name”: “Homer Simpson”, “image”: <UIImage> }
{ “full_name”: “Marge Simpson”, “image”: <UIImage> }

Você pode usar três pós-processadores para executar as seguintes etapas:

  1. Filtrar para contatos com dados para todos os campos: ‘primeiro’, ‘último’ e ‘imagem’. (OHSplitOnFieldTypePostProcessor)
  2. Encomendar os contatos por ‘último’, depois ‘primeiro’ (OHAlphabeticalSortPostProcessor)
  3. Mesclar os campos ‘primeiro’ e ‘último’ em um campo ‘full_name’ (sua implementação do pós-processador personalizada)

OHContactsSelectionFilterProtocol

Como selecionar e desmarcar contatos em uma interface de usuário é um caso de uso extremamente comum, o Ohana fornece suporte de primeira classe para seleções de filtragem com status e notificação quando elas acontecem. Por exemplo, um seletor de contatos pode apenas permitir a seleção de três contatos ao mesmo tempo (OHMaximumSelectedCountSelectionFilter) e pode desejar mostrar um estado de erro nítido e cancelar a seleção quando o usuário tentar selecionar um quarto contato.

Não descreveremos mais esta API aqui, mas em nossa documentação de código aberto temos algumas implementações de exemplo para saber mais sobre como a seleção de contatos em Ohana funciona.

Usando Ohana como parte de seu projeto

Configurar o Ohana como um simples consumidor do catálogo de endereços do sistema iOS é fácil. Esse snippet de código de um ViewController instancia um único Provedor de Dados para o catálogo de endereços do sistema e um único Pós Processador para ordenar esses contatos por seus nomes completos:

let alphabeticalSortProcessor = OHAlphabeticalSortPostProcessor(sortMode: .fullName)
var dataProvider = OHABAddressBookContactsDataProvider(delegate: self)
let dataSource = OHContactsDataSource(dataProviders: NSOrderedSet(objects: dataProvider), postProcessors: NSOrderedSet(object: alphabeticalSortProcessor))

Finalmente, indicamos que código acionar quando os contatos são retornados e chamamos para carregá-los:

dataSource.onContactsDataSourceReadySignal.addObserver(self, callback: { [weak self] (observer) in
   self?.tableView?.reloadData()

   // The table view reads contacts from dataSource.contacts
})
dataSource.loadContacts()

Para uma visão básica do selecionador de contatos, é isso. Tudo o que resta é ligar o seu OHContactsDataSource como a fonte de dados para sua exibição de tabela.

Ohana permite que você crie uma UI básica do address book em um número muito pequeno de etapas

Ohana inclui um aplicativo de exemplo com um grande conjunto de exemplos em Swift e Objective-C. O aplicativo de exemplo mostra como fazer transformações mais complexas, e também como Ohana facilmente lida com problemas assíncronos como autenticar desafios.

Usando Ohana no Uber

O esforço que produziu Ohana começou na equipe de inscrições de motoristas, cuja missão é aumentar as inscrições de parceiros através do aplicativo do motorista. Descobrimos que a componentização e a cuidadosa separação das preocupações no código eram essenciais para nos permitir manter a qualidade da base de código enquanto fazíamos a experimentação rápida que era necessário fazer.

Uma vez adequadamente componentizado, nossos contatos que entregam a lógica tornaram-se extensíveis o suficiente, tanto que outros engenheiros quiseram usá-los. As equipes responsáveis por contatos exibem a interface de usuário em outras partes de nossos aplicativos (por exemplo, divisão de tarifa, compartilhamento de ETA, convites) rapidamente consolidados em usar a mesma arquitetura. O modelo de dados de contatos que criamos tornou-se o formato de fato para interagir com informações de contato em todas as camadas do aplicativo.

Desde então abrimos o código de Ohana. A Uber tem uma política de código aberto flexível. O código que lançamos deve ser de interesse geral para a comunidade de código aberto e devemos ser capazes de licenciá-lo para permitir que ele tenha utilidade para os outros. De modo importante, também deve haver um conjunto claro de proprietários para o projeto de código aberto que podem se comprometer a lidar com o ciclo de vida do projeto, enquanto a comunidade de código aberto se torna envolvida e enquanto o ambiente técnico em torno do projeto muda.

Desde que tornamos Ohana open source, nós adicionamos suporte para gerenciar dependências com Carthage, recursos adicionados e corrigimos alguns erros no suporte Swift 3.

Tornar os contatos do iOS mais acessíveis do que as APIs principais permite que você tenha mais controle criativo sobre como exibir contatos do aplicativo. Será útil para lidar com contatos em seu projeto se suas necessidades vão além de mostrar o seletor de contato iOS padrão. Dê uma olhada no Ohana, veja se ele corresponde às necessidades do seu projeto. Nós damos boas vindas aos pull requests e nós, na Engenharia Uber, adoraríamos ver a comunidade iOS mais ampla dando uma chance para a ferramenta.

 

***

Este artigo é do Uber Engineering Team. Ele foi escrito por Adam Zethraeus. A tradução foi feita pela Redação iMasters com autorização. Você pode conferir o original em: https://eng.uber.com/ohana/