25%OFF no 1º ano | 50%OFF No 2º ano

Últimos dias!

Últimos dias!

00

DIAS

00

HORAS

00

MIN

00

SEG

Trabalhando com JSON no iOS

Trabalhando com JSON no iOS
Diego Chohfi
Diego Chohfi

Compartilhe

Com o crescimento do mercado mobile, a integração de sistemas com os pequenos aparelhos se tornou essencial. O formato JSON se popularizou com a mesma velocidade que o desenvolvimento mobile. Sua simplicidade e facilidade de desenvolver parsers o tornou uma das principais alternativas na integração de sistemas web, em especial para ser consumido via AJAX.

No iOS não foi diferente. A necessidade dos desenvolvedores levou o surgimento de diversas bibliotecas que faziam o trabalho de ler e gerar JSON com extrema facilidade, como o JSON Framework e o TouchJSON. Porém a Apple não quis ficar de fora e incorporou a classe NSJSONSerialization, capaz de gerenciar o formato tanto no iOS como no desenvolvimento para OSX, com a mesma facilidade que as bibliotecas opensource trouxeram.

Recetemente desenvolvemos um projeto para iOS para auxiliar os alunos do nosso curso de iOS, o IP-67, enriquecendo a experiência e trazendo um exemplo bem palpável. A aplicação consiste em buscar os pontos de ônibus próximos a localização do usuário. Para integrar a nossa aplicação com um backend em Ruby utilizamos JSON como o formato e a classe NSJSONSerialization se mostrou extremamente simples e fácil de se trabalhar. Vamos entender como fizemos, do lado do dispositivo, a leitura e manipulação dos dados.

Banner da Black Friday da Alura com destaque para até 50% de desconto em cursos, válido até 26/11. Transforme a sua carreira com o maior desconto do ano, matricule-se já!

Primeiramente precisamos obter as informações no formato JSON para então parsea-los. Utilizando o método + (id)dataWithContentsOfURL:(NSURL *)url; da classe NSData para fazer uma request e obter as informações de forma síncrona em um endereço da WEB:

 NSString \*url = ```NSString stringWithFormat:@"http://ondeestaoalbi.herokuapp.com/onibusesNosPontosProximos.json?lat=%f&long=%f", -23.588453, -46.632103
; NSData \*jsonData = ```NSData dataWithContentsOfURL: \[NSURL URLWithString:url
\]; 

O nosso serviço retornará um JSON com a seguinte estrutura:

 { "nome": "920016054", "descricao": "R. Dr. Neto De Araujo, 311", "coordenada": { "latitude": -23.5876, "longitude": -46.6321 } } 

Após receber as informações, vamos utilizar a classe NSJSONSerialization para transformar o JSON em um NSDictionary, contendo as chaves e valores das informações sobre o ponto.

 NSError\* error; NSDictionary \*resultados = ```NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error
; 

Caso o JSON seja válido e a conversão seja feita com sucesso podemos buscar os valores no NSDictionary a partir de uma chave, utilizando o método - (id)objectForKey:(id)aKey;:

 NSError \*error; NSDictionary \*resultados = ```NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error
; if(!error) { NSString \*nome = ```resultados objectForKey:@"nome"
; NSString \*descricao = ```resultados objectForKey:@"descricao"
; NSLog(@"Nome: %@, descrição: %@", nome, descricao); } 

Poderíamos continuar utilizando o NSDictionary para armazenar as informações dos pontos encontrados, mas imagine que por algum motivo precisemos mostrar a descrição do nosso Ponto sempre com letra minúscula e removendo os espaços do começo e do fim da nossa NSString, o código ficaria da seguinte forma:

 NSString \*nome = ```resultados objectForKey:@"nome"
; NSString \*descricao = ```\[resultados objectForKey:@"descricao"
 lowercaseString\]; descricao = ```descricao stringByTrimmingCharactersInSet:\[NSCharacterSet whitespaceAndNewlineCharacterSet
\]; NSLog(@"Nome: %@, descrição: %@", nome, descricao); 

Perceba que o código não é mais tão simples quanto anteriormente, além disso, precisaríamos espalhá-lo por todo o projeto. A manutenção também seria difícil, pois precisaremos copiar e colar o mesmo código toda vez que a descrição for exibida e caso a regra de exibição seja alterada o código que copiamos e colamos terá que ser alterado.

Temos esse problema pois estamos utilizando a classe NSDictionary para guardar estado do nosso Ponto e não podemos definir nenhum comportamento adicional. Para manter os dados organizados e de fácil manutenção devemos sempre criar classes para representar tipos e não mantê-los na representação de chave e valor, desta forma, além de guardar o estado do objeto, podemos também definir novos comportamentos, facilitando a manutenção e a reutilização de código.

Vamos criar as classes com os referentes atributos e também definir um método de classe para criar uma instância do Ponto, esse método será responsável por buscar suas informações em um NSDictionary passado como argumento. Essa regra fica isolada e fácil de dar manutenção caso alguma das chaves mude:

 #import <Foundation/Foundation.h> #import "Coordenada.h"

@interface Ponto : NSObject

@property(nonatomic, strong) NSString \*nome; @property(nonatomic, strong) NSString \*descricao; @property(nonatomic, strong) Coordenada \*coordenada;

\+ (Ponto \*) comDicionario: (NSDictionary \*) dicionario;

@end

#import "Ponto.h"

@implementation Ponto @synthesize nome, descricao, coordenada;

\+ (Ponto \*) comDicionario: (NSDictionary \*) dicionario { Ponto \* ponto = ```\[Ponto alloc
 init\]; ```ponto setNome: \[dicionario objectForKey:@"nome"
\]; ```ponto setDescricao: \[dicionario objectForKey:@"descricao"
\]; Coordenada \*coordenada = ```Coordenada comDicionario: \[dicionario objectForKey: @"coordenada"
\]; ```ponto setCoordenada: coordenada
; return ponto; }

@end 

Se fosse preciso definir uma regra para buscar a descrição do Ponto da forma que descrevemos anteriormente, podemos alterar o método que busca o atributo na classe, isolando o código e facilitando futuras manutenções.

 #import "Ponto.h"

@implementation Ponto @synthesize nome, descricao, coordenada;

\- (NSString \*) descricao { NSString \*descricaoEmMinusculo = ```descricao lowercaseString
; return ```descricaoEmMinusculo stringByTrimmingCharactersInSet:\[NSCharacterSet whitespaceAndNewlineCharacterSet
\]; }

@end 

Para obter um Ponto a partir de um JSON chegamos ao seguinte código:

 NSString \*url = ```NSString stringWithFormat:@"http://ondeestaoalbi.herokuapp.com/onibusesNosPontosProximos.json?lat=%f&long=%f", -23.588453, -46.632103
; NSData \*jsonData = ```NSData dataWithContentsOfURL: \[NSURL URLWithString:url
\];

NSError \*error; NSDictionary \*resultados = ```NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error
; if(!error){ Ponto \*ponto = ```Ponto comDicionario: resultados
; NSLog(@"Ponto: %@", ponto.descricao); } 

Percebendo a dificuldade e a duplicação de código que temos que escrever ao buscar os valores no NSDictionary, desenvolvemos uma pequena biblioteca para facilitar essa tarefa. O [KeyValueObjectMapping](https://github.com/dchohfi/KeyValueObjectMapping) surgiu da necessidade de transformar grandes estruturas de dados automaticamente em objetos, seguindo padrões e quando necessário adicionando configurações para alterar as convenções. Poderíamos remover a implementação do método + (Ponto *) comDicionario: (NSDictionary *) dicionario; e com o KeyValueObjectMapping teríamos:

 DCKeyValueObjectMapping \*parser = ```DCKeyValueObjectMapping mapperForClass:\[Ponto class
\]; Ponto \*ponto = ```parser parseDictionary:resultados
; 

Lembrando que o KeyValueObjectMapping serve para transformar qualquer tipo de dado que se comporte como um NSDictionary, chave e valor, podendo ser um JSON, um PLIST, entre outros. Você está convidado a contribuir nos projetos!

Diego Chohfi
Diego Chohfi

Desenvolvedora de software e engenheira de computação.

Veja outros artigos sobre Inovação & Gestão