Desenvolvimento para iOS

Nos dispositivos móveis, a resposta que o sistema dá ao usuário quando ele faz algo é extremamente importante. É totalmente indesejável, por exemplo, que seus usuários fiquem sem saber o que está acontecendo enquanto o sistema vai buscar algo na internet. Além disso, você também não irá querer que o sistema, como um todo, deixe de responder, por exemplo, aos movimentos de scroll enquanto algo é realizado.

Desenvolver uma app que permita uma experiência de uso agradável para o usuário é algo que envolve muitas variáveis. Desde a escolha certa de imagens até o uso eficiente dos recursos do dispositivo móvel no qual a aplicação irá funcionar. Conhecer conceitos como blocks, memory leaks, Activity Monitor, Time Profiler e GCD passa a ser então de extrema importância, pois eles, em conjunto, permitem que você construa apps efetivamente responsivos para os usuários.

Por exemplo, o Activity Monitor permite que você verifique a alocação dos recursos, principalmente memória livre. Já com o Time Profiler é possível identificar onde seu App está ficando mais tempo, quais funções estão fazendo o App “parar” e fazer seu usuário esperar.

Neste artigo iremos apresentar algumas dicas e boas práticas durante o desenvolvimento de seu App para iOS para evitar leaks de memória e gargalos na execução para garantir melhor experiência possível ao usuário final.

Qualquer aplicativo deve ser feito levando-se em consideração a atenção do usuário e sua vontade em permanecer ou regressar ao aplicativo. Um aplicativo mal feito ou deficiente no seu projeto não irá cativar o usuário e ele irá buscar uma alternativa.

Com o uso das boas práticas apresentadas neste artigo seus Apps ficarão mais estáveis, menores e responsivos ao usuário.

O desenvolvimento de software considera uma série de práticas que tem por objetivo um resultado final: um produto bem acabado que será usado por um usuário. Fácil. Vamos pegar uma ideia e começar a fazer nosso software. Mas não é bem assim.

Com a chegada do iPhone em 2007, e da AppStore em 2008, a comercialização de aplicativos tornou-se muito mais simples, pois já não é mais necessário custear caixas, mídias e logística de transporte (sem mencionar a negociação com cadeias de distribuição). Além disso, abriram-se portas para a venda internacional, antes possível somente para grandes empresas desenvolvedoras de software e, para pequenas empresas de desenvolvimento ou mesmo para profissionais autônomos. E, como tudo na vida, tudo tem um lado bom e um lado ruim. O lado ruim é que o número de pessoas desenvolvendo aplicativos, mais especificamente Apps, aumentou incrivelmente. Isso não é ruim pelo fato de existir mais concorrência, é ruim porque muitas dessas pessoas não são capacitadas no desenvolvimento de Apps.

O objetivo deste artigo não é discutir diferenças, possivelmente ideológicas, de plataformas e linguagens, mas Apps não são aplicações. Apps são produtos desenvolvidos para dispositivos móveis, como smartphones e tablets. Aplicações são produtos para desktop e servidores e, WebApps são produtos para a Web.

A diferença entre esses três conceitos é grande e eles não podem ser misturados. Um desktop é quase ilimitado em suas capacidades de processamento e configurações. Um servidor é mais flexível ainda, principalmente agora, com o Cloud Computing (computação na nuvem), onde é possível ter várias máquinas trabalhando em conjunto para a realização de uma tarefa e, os dispositivos móveis são, por natureza, fechados nas suas configurações iniciais.

No caso do iPhone e do iPad, estamos falando de uma memória de 512 MBytes (ou 1 GByte nos modelos mais atuais) e espaço de armazenamento máximo de 128 GBytes, sendo que a média deve ser de 32 GB. Em comparação simplificada, no computador que utilizo diariamente para trabalhar, tenho 16 GB de memória e 5TB de armazenamento espalhados em vários discos. E, ainda por cima, um sistema operacional que faz swap (utiliza parte do disco como memória, para permitir a utilização de mais aplicativos ao mesmo tempo, sem degradação da performance). Isso significa, no mínimo, 16 vezes mais poder de processamento que os smartphones.

O que isso significa na prática? Na prática, a limitação de memória dos dispositivos móveis, a ausência de swap e a limitação de disco obrigam os desenvolvedores a criarem Apps mais enxutos e bem acabados. Enxutos não significam menores, significa que sabem usar bem os recursos da máquina e somente usá-los quando necessário.

E é esse o objetivo desse artigo, abordar algumas das boas práticas de programação para a criação de Apps enxutos, sem problemas no gerenciamento dos recursos dos dispositivos e com uma resposta adequada ao usuário final.

Conceitos e Ferramentas
Conceitos e Ferramentas

Serão vistos a partir de agora um conjunto de conceitos que o ajudarão a construir apps iOS considerando boas práticas de desenvolvimento.

Interface com o Usuário

O manual iOS Human Interface Guidelines da Apple recomenda que a área de toque de um objeto seja de, no mínimo, 23 pixels e, recomendado, 31 pixels. Esses números foram definidos considerando estudos que levaram em conta o tamanho médio do dedo de um adulto normal. Sendo assim, sempre que for projetar a interface considere os seguintes pontos:

  • Tamanho mínimo dos elementos;
  • Distância entre elementos;
  • Posicionamento relativo dos elementos (quem está “acima/abaixo” de quem).

Nos aparelhos mobile, a responsividade, ou seja, a resposta que o sistema dá ao usuário quando ele faz algo, é extremamente importante. Você não irá querer que seus usuários fiquem sem saber o que está acontecendo enquanto o sistema vai buscar algo na internet ou atualiza as imagens do Twitter, e também não irá querer que o sistema, como um todo, deixe de responder, por exemplo, aos movimentos de scroll enquanto algo é realizado.

Blocos

Blocks – um pedaço de código que é caracterizado pelo acento circunflexo (^) - é uma forma de codificar que permite a inclusão de rotinas dentro de rotinas, sem a necessidade da criação de funções ou classes e sua execução é sempre feita em uma thread separada. A grande vantagem da utilização dessa técnica é que o gerenciamento da fila de execução é feita pelo próprio sistema, dentro do GCD, ou Grand Central Dispatch, que é o sistema gerenciador de filas de execução de códigos em paralelo.

Threads

Threads são tarefas que são executadas em paralelo, liberando a interface para responder ao usuário. Neste item é importante estar atento ao fato de que toda atualização da interface deve ser feita na thread principal, sempre.

Animações

O uso de animações em qualquer sistema nos dá uma sensação mais agradável e um estímulo maior para usar. Os conceitos de animação clássica são usados há muito tempo e têm por objetivo simular o que acontece na vida real.

Alguns exemplos do uso da animação nos aplicativos são: o bounce do scroll (a mola como é chamado); o fade in/out (aparecimento) das telas; o movimento de aparecer do teclado, entre outros.

Outros exemplos de como melhorar a usabilidade, principalmente em telas touch onde não há resposta tátil ao que o usuário está fazendo é a utilização de sons para mostrar que o aplicativo está respondendo às ações do usuário. Os maiores exemplos de uso de som estão no teclado e no envio de e-mail, onde, após o envio com sucesso, ouve-se um som característico.

Memory Leak, Garbagge Collection e ARC

Tudo o que é feito por um computador, e dispositivos móveis são computadores, é feito em memória. Cada programa aberto ocupa uma área específica de memória e, cada vez que uma nova atividade vai ocorrer, novos espaços de memória são criados (alocados). Se o programador não tomar o cuidado de limpar esses espaços, liberando memória para o dispositivo, a memória irá acabar e haverá o chamado crash.

Quando uma determinada função acaba sua execução e suas variáveis não foram devidamente tratadas (limpas), ocorrerá o chamado Memory Leak. Aqui, o exemplo de um copo furado é perfeito, já que Leak em inglês é vazamento. Imagine uma bacia com vários copos com alguns furos. Vá colocando água nesses copos. Ao atingir o furo, a água começa a vazar para a bacia. Vários copos vazando, depois de um tempo, fará com que a bacia esteja cheia de água. Traduzindo: cada copo é um programa, a água é a alocação de memória e o vazamento é o não tratamento adequando das variáveis do programa. A cada vazamento (leak), a memória do dispositivo (bacia) vai sendo ocupada até que se esgote. O sistema operacional iOS avisa a cada aplicativo aberto que existe um problema com a memória e é de responsabilidade do programa limpar as variáveis que não estão sendo mais utilizadas. Se isso não ocorrer, o sistema operacional se encarrega de fechar o aplicativo por falta de memória.

Até o iOS 4, era de responsabilidade total do programador a gestão de memória de seu programa. A partir do iOS 5, foram introduzidos dois novos conceitos: Garbage Collection e ARC.

Garbage Collection é literalmente o uso de lixo, ou seja, o sistema operacional cria uma área onde as variáveis são criadas e o próprio sistema operacional se encarrega de limpá-las. Pode-se dizer que são variáveis temporárias. Cabe ao programador se encarregar de determinar qual variável é permanente (retained) e qual será temporária.

ARC é Automatic Reference Counting, que significa que o sistema operacional se encarrega de saber quando uma variável está sendo usada e quando ela pode ser jogada fora, usando o conceito do Garbage Collection.

Dessa maneira, usando o ARC, o programador não precisa se preocupar mais em gerenciar a memória, pois isso é feito pelo sistema. Mas cabe ao desenvolvedor garantir que a variável existe quando for requerida.

Na prática

Os recursos, ou seja, a memória e o disco do iOS são bastante limitados. As primeiras versões do iPhone tinham 128MBytes de memória. Hoje, na data que este artigo foi publicado, o iPhone 4 e 4S e o iPad 2 e Mini têm 512MBytes e o iPhone 5 e iPad 3 e 4 têm 1GBytes. Em termos de disco para armazenamento, são vendidos aparelhos com 16GB, 32GB, 64GB e 128GB, mas a média dos aparelhos comercializados está em torno de 32GBytes. Ambos os recursos são compartilhados entre todos os Apps que estão no aparelho, mais o sistema operacional em si e os conteúdos de cada app, como fotos, vídeos e músicas.

Isso significa que a memória disponível para uso em seu App é restrita. Mais restrita que se imagina. Em torno de 70 a 80MBytes da memória é exclusiva para uso do sistema. Nas primeiras versões do iPhone isto significava que menos de 50% da memória estava disponível para os Apps, todos os Apps.

Então como fazer para evitar o crash por falta de memória e deixar o App lento? A seguir são apresentadas algumas dicas que podem ajudar nesse processo.

Gestão de Memória

Cada variável que é criada no sistema aloca um espaço em memória que pode ser pequeno, como um int (inteiro - que tem 32bits) ou gigante, como um NSMutableDictionary com 1.000.000 (um milhão de elementos) variáveis. Mas, sem essas variáveis, seu App não faz nada, certo? Então a primeira dica é: apague os recursos que não estão mais sendo usados.

Outra maneira de evitar a sobrecarga do sistema é não usando literais, ou seja, a associação direta de um tipo de variável como:

  • NSString *texto = @"meu texto";
  • Ao invés disso, trabalhe da seguinte maneira:
  • NSString *texto = [[NSString alloc] initWithString:@"meu texto"];
  • {faça o que for necessário com essa variável}
  • [texto release]; texto = nil;

Desta forma você terá controle sobre a alocação da memória e quando ela poderá ser liberada. Se possível, sempre atribua valor nil (nulo) às variáveis após seu uso. Isso fará com que o espaço alocado seja zerado, liberando recursos para o sistema.

Use @autoreleasepool: ele é utilizado principalmente em loops e gerencia a memória automaticamente. Por exemplo, dentro do loop são criadas várias variáveis e, ao final do loop, o @autoreleasepool se encarrega de limpar essas variáveis e seu conteúdo. Ele é muito usado no processamento de dados como o parse de um arquivo XML.

Por fim, outra dica é responder aos eventos de memory warning. Libere os recursos que forem possíveis. Sempre que receber uma mensagem didReceiveMemoryWarning, verifique quais recursos efetivamente precisam ser tipo retain (ou retidos em memória para uso posterior e compartilhado) e quais não. Verifique quais recursos podem ser recriados quando forem necessários e, se houver um didReceiveMemoryWarning, libere esses recursos.

Carregar quando necessário

Carregue o conteúdo (lazy load), principalmente imagens, somente quando necessário e, quando possível, reutilize esse objeto. Por exemplo, em uma tabela, reutilize as células - quero dizer, a estrutura da célula, seu desenho e objetos, não os dados propriamente ditos. Se for necessário carregar uma imagem da internet, faça em paralelo - usando blocks ou a comunicação assíncrona - e libere a interface para o usuário fazer outras coisas.

Imagens

Trate todas as imagens que serão usadas no App. Prefira imagens do tipo PNG, pois podem ser comprimidas pelo App, diminuindo seu tamanho.

Se for necessário carregar uma imagem, prefira o método [[UIImage alloc] initWithContentsOfFile:@""]; em vez de [UIImage imageNamed:@""];. Esse primeiro não faz cache da imagem em memória.

Se tiver que carregar uma imagem gigante, tente cortá-la em pedaços e carregue somente a parte que estiver visível. No site da Apple há um exemplo sobre esse assunto em ScrollViewSuite.

Sempre que possível, tente diminuir o tamanho das imagens e/ou sua resolução. O iPhone e iPad trabalham com uma resolução de 72dpi. Se você não precisar fazer zoom na imagem, não é necessário ter uma imagem maior que o tamanho da tela do aparelho. Ao customizar botões ou outros elementos da interface, usando imagens, faça a imagem do tamanho do elemento.

Crie sempre duas imagens, uma normal, do tamanho do elemento e outra com o dobro do tamanho (@2x) para os aparelhos com tela retina.

Quando possível, evite imagens com fundo transparente. Aproveite o próprio fundo da imagem para otimizar performance. O uso de transparência em objetos custa muito caro para o processador.

Novas tecnologias

Mantenha seu App sempre atualizado com os novos recursos do XCode e iOS. Use blocks, GCD (Grand Central Dispatch) e ARC. Utilize também, sempre, o Static Analysis Tool. Esta ferramenta faz uma pré-checagem em seu código e informa variáveis que não estão sendo usadas, variáveis que não estão sendo liberadas e outras informações muito úteis para melhorar seu código.

A lógica de programação e funcionamento do App é de sua responsabilidade, mas o uso constante dessas tecnologias ajudará a evitar os erros mais comuns, ou “esquecimentos” como, por exemplo, declarar uma Array e tentar modificá-la no decorrer da função. O Static Analysis Tool irá dizer para você que existe esse erro, indicando onde está o problema.

Instruments

Instruments é uma ferramenta do XCode que permite monitorar diversos itens do dispositivo e do programa em execução. O uso do Instruments é essencial para verificar memory leaks, consumo de memória e tempo de execução de cada função.

Aprender a usar o Instruments é fundamental para entregar um App relativamente estável. Nem o Instruments nem o XCode podem resolver problemas de lógica, isso é de responsabilidade do programador.

Utilize o Instruments para melhorar a velocidade, tempo de resposta do App, tempo de vida da bateria e eficiência.

Utilize, pelo menos, as funções de Leaks, Activity Monitor e Time Profiler. Com Leaks é possível ver, em tempo de execução, onde estão ocorrendo leaks de memória, ou seja, onde uma variável foi criada, usada e não liberada. Lembre-se que quanto mais leaks houver na sua App, haverá menos memória disponível para seu App e também para os demais Apps e sistema.

Com o Activity Monitor, verifique a alocação dos recursos, principalmente memória livre. Se seu App consumir muita memória durante a execução, poderá ocorrer um crash por falta de memória. Verifique todas as partes de seu App e verifique como a memória é consumida após um controlador ser criado e se está sendo liberada corretamente após esse controlador deixar de existir. Se as variáveis não estiverem sendo corretamente liberadas, a cada uso desse controlador, mais e mais recursos serão ocupados evitando a gestão correta de memória.

Com o Time Profiler, veja onde seu App está ficando mais tempo, quais funções estão fazendo o App “parar” e fazer seu usuário esperar. Otimize a execução dessa parte do código. Use blocks, GCD, threads e outras otimizações para diminuir o tempo de execução dessa determinada função e libere os recursos do sistema, principalmente a interface do usuário.

Performance

Após o correto gerenciamento de memória e uso de recursos, o tratamento ao usuário é essencial. Deixar o usuário esperando nunca é uma boa política.

Diversas coisas podem ser feitas para otimizar o tempo de resposta de seu App. Entre elas, a coisa mais importante é o render. Mas o que é render? Render é a transformação de muita matemática em imagem. Tudo o que se vê na tela é um render. Uma imagem é renderizada, um texto é renderizado. Ou seja, para que os pixels apareçam na tela, ele precisa ser renderizado.

Utilize, sempre que possível, os mecanismos padrões do sistema, ou seja, o framework UIKit. Ele já foi otimizado para isso.

Evite usar objetos com transparência, pois o tempo para calcular o render desse objeto implica em verificar o que está por trás, fazendo duas vezes o cálculo - o cálculo do objeto abaixo e o cálculo do objeto a ser mostrado propriamente dito.

Utilize imagens PNG tipo BGRA e não RGBA, ou seja, uma imagem pré-multiplicada. Mas, o que significa imagem pré-multiplicada? Toda imagem é composta por três canais de cor, RGB (vermelho, verde e azul) e um canal de transparência (Alpha). Usar imagens pré-multiplicadas significa colocar o canal Alpha diretamente em cada canal de cor. Verifique no seu programa favorito de edição de imagens como fazer isso.

Quando o usuário estiver fazendo algo na tela como um scroll, por exemplo, evite atualizar a tela e mostrar novos/atualizar os elementos.

Evite fazer muita coisa durante a inicialização do App. O manual de usabilidade da Apple diz que todo App que demorar mais do que dois segundos para iniciar será recusado e, se tiver sido aprovado previamente, será terminado no aparelho. Deixe para carregar o que for necessário para o momento certo, quando essa informação for realmente necessária.

Utilize CoreData para a gestão de dados muito grandes. O controlador do CoreData se encarrega de obter os dados quando necessário, ao mesmo tempo em que também gerencia as atualizações dos objetos CoreData salvando na base de dados.

Animações e resposta ao usuário

Tudo o que acontece está intrinsecamente ligado à expectativa. Imagine que você está parado no semáforo, sem saber a quanto tempo ele está vermelho e quanto tempo falta para ficar verde. Qual é a sua expectativa? Após certo tempo de espera, sua ansiedade começa a falar mais alto e, os mais apressadinhos, até passam no sinal vermelho. Sua expectativa de tempo não foi atendida.

O mesmo acontece com seu programa. Se o usuário espera que algo aconteça em um determinado tempo e isso não acontecer, sua expectativa irá atrapalhar seu julgamento sobre a qualidade do App.

Agora, já existem semáforos que mostram quanto tempo falta para ficar verde ou vermelho, fazendo com que sua expectativa de tempo de espera não seja afetada, pois a informação está lá, sendo mostrada.

Faça o mesmo no seu App. Coloque indicações visuais sobre o que está acontecendo.

Use o objeto spin (UIActivityIndicator) para indicar que o App está processando algo. Use o “elástico” do scroll para mostrar que o conteúdo acabou. Use o indicar de scroll (Show Horizontal Scroll ou Show Vertical Scroll) para indicar o tamanho total do conteúdo que está sendo mostrado.

Utilize animações para melhorar a experiência do usuário. Por exemplo, ao atualizar o conteúdo de uma tabela, não faça simplesmente o recarregamento do conteúdo ([self.tableView reloadData];), utilize as funções para inserir, apagar ou atualizar uma ou várias linhas da tabela, com animação ([self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];).

Quando um objeto for aparecer, use animação, como no aparecimento do teclado ou de um ModalViewController. Use o código apresentado na Listagem 1 para fazer aparecer usando o efeito de fade-in.

Listagem 1. Uso do efeito fade-in.


    [_objeto setAlpha:0];
      [UIView animateWithDuration:0.4
       animations:^{
        [_objeto setAlpha:1];
       }]; 

Veja em outros Apps ou mesmo em desenhos animados, como a animação muda tudo o que está acontecendo. Veja como ocorrem transições entre telas. Veja, no App iBooks, como ocorrem as transições entre páginas. Inspire-se.

Essas mudanças irão mudar a percepção como os usuários enxergam seu App, pois a atualização ocorrerá de forma suave e animada e não brusca e sem “feedback”. Lembre-se, é muito importante mostrar ao usuário o que está acontecendo.

Finalmente, faça todos os testes no dispositivo. O simulador é ótimo para testar a lógica da programação e verificar se está tudo ocorrendo conforme as especificações, mas é o dispositivo que irá dizer se a performance está adequada, se a memória está sendo usada corretamente, enfim, se o App está dando a melhor experiência possível ao usuário.

O simulador está otimizado para rodar em um desktop Mac e comportar-se como o iPhone. Entretanto, existem funções que não são possíveis de testar no simulador, como o uso do giroscópio, câmera e alguns outros avanços de hardware.

O dispositivo é o instrumento que deve ser usado para finalizar os testes e garantir o funcionamento do App. Se for desenvolver para diversas versões de sistemas operacionais, faça o teste em todas, seja via simulador, seja no dispositivo.

Conclusão

O projeto da interface e sua responsividade, gestão de recursos e atenção aos detalhes são palavras chaves no desenvolvimento de um aplicativo mobile.

O usuário não quer ficar esperando sem fazer nada enquanto seu aplicativo baixa um conteúdo grande da internet, então libere a interface para outras atividades. Use blocks e o GCD (Grand Central Dispatch) para isso.

Além disso, mostre ao usuário o que está acontecendo. Use as animações e elementos animados, como o spin, para chamar a atenção e ao mesmo tempo, mostrar que o sistema está trabalhando e não está travado. Faça transições animadas e, quando for mostrar um objeto, use animações para suavizar o aparecimento do objeto.

Se seu App irá processar muitas coisas ou dados muito grandes, ou até mesmo uma imagem gigante, gerencie corretamente os recursos para que o App não trave e deixe o usuário sem saber o que aconteceu. Casos assim fazem com que o usuário faça uma avaliação negativa no App e isso, por consequência, diminui as vendas.

Use a ferramenta Instruments para melhorar ao máximo a responsividade, gestão de recursos e tempo de resposta de seu App.

Vimos neste artigo que a programação em blocos e o uso do GCD permite a execução de tarefas em paralelo, liberando o aplicativo para ser usado enquanto ele executa algum processamento interno. Assim, faça cálculos complexos ou loops nas threads secundárias e não na thread principal. Vimos também a gestão dos recursos, principalmente memória e como otimizar ao máximo esse recurso. Trate suas imagens, libere as variáveis e, quando necessário, responda aos eventos do sistema operacional e libere os recursos que são criados em tempo de execução de cada função/classe. Use animações para dar feedback aos usuários.

Espera-se que com essas dicas seus Apps fiquem mais estáveis, menores e responsivos ao usuário, afinal, a experiência do usuário é o que conta.

Links Úteis

  • Aplicativos híbridos com Ionic:
    Veja como criar aplicativos de forma rápida, flexível e produtiva para a plataforma do Ionic.
  • Transformando layouts em código:
    Neste DevCast conheceremos algumas ferramentas que são utilizadas pelas equipes de design e programação para a criação e compartilhamento de layouts/protótipos, facilitando a comunicação entre as áreas.
  • O que é NF-e e NFC-e?:
    Neste curso você terá o primeiro contato com mundo dos emissores de documentos fiscais eletrônicos Brasileiros.

Saiba mais sobre Mobile ;)

  • Guias Programação Mobile:
    Aumente seus conhecimentos nas tecnologias móveis. Aqui você encontra um Guia de estudo perfeito para cada ferramenta do mercado. Escolha o seu!
  • Guia Completo de Ionic:
    Neste Guia de Referência você encontrará o conteúdo que precisa para aprender a desenvolver aplicativos mobile multiplataforma utilizando o Ionic.
  • Guia Completo Cordova:
    Neste Guia de Referência você encontrará o conteúdo que precisa para aprender a desenvolver aplicativos mobile multiplataforma utilizando o Cordova.

Referênciais:

APPLE. Develop for iOS
https://developer.apple.com

DALRYMPLE, Mark; KNASTER, Scott. Learn Objective-C on the Mac [S. l.]: Apress, 2009.

KOCHAN, Stephen. Programming in Objective-C 2.0 [S. l.]: Addison-Wesley, 2009.

MARK, Dave; NUTTING, Jack; LAMARCHE, Jeff. Beginning iPhone 4 Development. [S. l.]: Apress, 2011.

PILONE, Dan; PILONE, Tracey. Head First iPhone Development. [S. l.]: O’Reilly Media, 2010.

TREBITOWSKI, Brandon; ALLEN, Christopher; APPELCLINE, Shannon. iPhone and iPad in Action. [S. l.]: Manning, 2011.