Programando para Windows Mobile usando MVC e Delegates - Parte 2
Escrito por Afonso Junior em 24/09/09 14:40
Continuando o post sobre a utilização de MVC em um projeto para Windows Mobile no Visual Studio 2008, falaremos agora como lançar mão dos eventos para poder completar o padrão.
De acordo com algumas literaturas, a implementação do padrão MVC especifica que o modelo notifique a view em caso de alterações, de forma que os dados exibidos na view sejam atualizados.

Para fazer essa notificação, vamos ampliar um pouco mais o conceito de delegates, passado no primeiro post, e utilizar também eventos.
Na verdade, para os mais atentos, o conceito de evento já foi utilizado no post anterior, quando eu criei o seguinte método na view para adicionar um delegate:
public void AddSalvarClick(ViewEventsDelegate metodo) {
BtnSalvar.Click += new EventHandler(metodo);
}
Estávamos na verdade dizendo que o evento Click do botão BtnSalvar iria acionar o método passado por ele por parâmetro. Assim, fica mais fácil entender o funcionamento de um evento:
Os eventos são os "gatilhos" para os delegates: quando um evento é chamado, os métodos adicionados a ele são chamados;
Os eventos não aceitam qualquer tipo de delegate: ao declarar um evento, você deve especificar qual o tipo de delegate está associado a ele. De uma forma direta, isso implica dizer que os eventos só aceitam os métodos com a assinatura especificada na declaração do delegate.
Voltemos ao nosso exemplo. Como disse acima, a implementação do padrão em camadas pede a notificação da view em caso de alteração no modelo. Mas aqui é necessário pensar um pouco na forma como essa notificação vai ser feita, principalmente no que diz respeito ao modo como a informação a ser alterada será passada para a view. Em alguns textos, ao notificar a view, o objeto de modelo passa como parâmetro ele mesmo, e a view pega então todos os valores do modelo e preenche. O problema que alguns podem ver aqui é o da acoplagem, pois eu estaria prendendo a classe view a um tipo específico de modelo. Uma solução alternativa seria criar notificações para várias alterações, passando então o valor (geralmente em tipos básicos como strings, inteiros, etc) só do que está sendo alterado.
Não sou puritano, mas acho as duas abordagens válidas. A vantagem da segunda - além da baixa acoplagem - é reduzir para métodos pequenos a alteração do conteúdo. Tenho certo medo de métodos do tipo AlteraTudo(object modelo), por ter séria tendência a se tornar um método frankenstein, difícil de dar manutenção.
Vamos ao código! Em um exemplo fictício, onde tenho um sistema de venda de livros, um modelo óbvio seria um Livro. Vou criar nesse exemplo um evento que trata a notificação da alteração do preço do livro. Quando o preço for alterado, ele notifica a todos aqueles que "assinaram" o evento.
namespace PostMVC {
//declarando o delegate
public delegate void PriceChangeDelegate(float price);
class Book {
//criando o evento
private event PriceChangeDelegate OnPriceChange;
//metodo que adiciona delegates ao evento do preco
public void AddPriceChangeDelegate(PriceChangeDelegate priceDelegate) {
OnPriceChange += priceDelegate;
}
//o atributo preco
private float _price;
// Get e Set do campo preço. Perceba que no set eu chamo o
//evento que sera executado por causa da alteracao feita
public float Price {
get { return _price; }
set {
_price = value;
if (OnPriceChange != null)
OnPriceChange(value);
}
}
}
}
Percebam a validação feita antes de executar o evento:
if (OnPriceChange != null)
OnPriceChange(value);
Um evento é nulo se nenhum delegate está associado a ele. Agora, na view, o código fica bem simples: basta adicionar um método que tenha a assinatura do delegate especificada, que faça essa alteração:
public void AlteraPrecoNaTela(float preco) {
//codigo que escreve na tela
}
E no construtor também fica bem tranquila a implementação:
class BookController {
private Book model;
private BookPriceForm view;
//construtor
BookController() {
model = new Book();
view = new BookPriceForm();
model.AddPriceChangeDelegate(view.AlteraPrecoNaTela);
}
}
Com isso, o único que fica acoplado é o controller, que é quem realmente tem que ter os modelos e as vistas.
Dessa forma, você evita colocar o código todo no formulário, você traz a sua lógica de negócio para o controlador, seu código fica mais simples, mais fácil dar manutenção, e todos ficam felizes! :)
Leia também:
Performance do SQLite Persistent Object
Escrito por Felipe Barreto em 18/09/09 11:46
Da última vez que comparei as soluções ORM para iPhone, cheguei a conclusão de que o Sqlite Persistent Object - SPO era o mais indicado para o projeto que estávamos desenvolvendo. Agora, com o projeto concluído, observamos que a performance não estava adequada às necessidades do cliente e começamos a buscar soluções.
Minha primeira idéia foi aproveitar a chegada do iPhone OS 3.0 e migrar do SPO para Core Data imaginando que, por ser uma solução desenvolvida especificamente para a plataforma, seria bem mais eficiente. Contudo, após estudo da API, vi que a mudança seria um pouco mais complicada, pois ela usa um paradigma diferente da anterior e eu teria que mexer muito além das classes de modelo, possivelmente mexendo em todas as outras classes, controladores principalmente. Embora tenha coberto as classes de modelo com testes unitários, achei que seria arriscado demais fazer a mudança estando tão próximo da entrada do sistema em produção. Decidi deixar essa idéia na gaveta e parti para tentar melhorar o que já estava implementado e em funcionamento.
Avaliando o tempo de cada atividade
Não faz sentido buscar melhorias na performance se você não tiver como saber se as modificações estão realmente influenciando no tempo de cada atividade. Para avaliar isso, existem algumas ferramentas disponíveis no Mac OS, como o Instruments e o Shark. Utilizando a segunda, consegui ter uma idéia clara de quais métodos estavam consumindo mais tempo da CPU através da análise de Time Profile.
Mas para medir o tempo de uma atividade completa - que atravessa vários métodos, threads, callbacks - eu desenvolvi uma classe simples, porém eficaz, para calcular esses tempos. Uma classe que batizei de Benchmark e que pode ser usada da seguinte maneira:
- baixarLista {
[Benchmark start:@"baixarLista"];
....
}
- exibirLista {
.....
[Benchmark finish:@"baixarLista"];
}
A saída seria algo parecido com:
2009-09-17 13:52:49.729 MyAPP[876:20b] BENCHMARK: baixarLista started
2009-09-17 13:52:56.507 MyAPP[876:6b07] BENCHMARK: baixarLista: 6.777050s
As vantagens desta abordagem são:
- o código é pouco invasivo, pois você pode dar o import do Benchmark no .pch e usar em todas as classes;
- permite o uso de labels para identificar o benchmark, o que permite realizar vários benchmarks ao mesmo tempo sem se confundir;
- por utilizar somente métodos estáticos, permite que um benchmark comece num método de uma classe e termine no de outra classe sem que seja necessário o compartilhamento de dados entre as classes.
O código completo da classe você confere aqui.
Identificando os gargalos
Depois de levantar o tempo gasto e os métodos mais custosos durante a execução de cada atividade, pude me concentrar em encontrar os pontos considerados gargalos e que eu poderia fazer alguma mudança.
Datas
O SPO armazena os campos de data como strings no banco e todas as operações de recuperação ou gravação passam por métodos de conversão definidos em NSDate-SQLitePersistence.m. Analisando a saída do Shark, observei que estas conversões estavam tomando muito tempo de processamento e, para minha surpresa, a maior parte do tempo não era gasta com a conversão em si, mas com a instanciação da classe NSDateFormatter. Para solucionar o problema, bastou declarar o formatador como estático e instanciá-lo somente uma vez, criando uma espécie de cache. Esta modificação já surtiu um grande efeito, pois a atividade que estava sendo trabalhada envolvia, de uma só vez, o download, gravação e exibição de uma lista de entidades que possuíam 3 campos de data cada uma.
Lista dinâmica de propriedades
Dentre vários métodos do SPO, são feitas chamadas ao método propertiesWithEncodedTypes das entidades para obter um dicionário com todas suas propriedades e seus respectivos tipos. Esse método é relativamente custoso, pois esse dicionário é montado toda vez que o método é solicitado e, a cada busca por uma lista de entidades, podem ser feitas dezenas - ou centenas - de chamadas a este método.
Mais uma vez, a solução foi simples: cache. Depois da primeira chamada do método, o dicionário fica gravado numa variável estática que é consultada nas chamadas subsequentes. Resultado: mais um salto na performance.
Strings
As propriedades das entidades em Objective C são declaradas em camelCase, enquanto as respectivas colunas do banco seguem uma notação_com_underscores e para fazer essa conversão, o SPO define dois métodos em NSString-SQLiteColumnName.m. Como nos casos anteriores, esses métodos são muito solicitados durante as atividades, mas eu consegui fazer uma estatística que mostrava em em mais de 80% dos casos a conversão era desnecessária, pois a propriedade tinha a mesma notação tanto no código quanto na base. A mudança neste caso foi simplesmente verificar antes se o nome já estava no formato certo e retorná-lo imediatamente. Outro salto na performance.
Conclusão
Com as mudanças acima e utilizando a classe de benchmark, conseguir registrar melhoras de mais de 60% em diversas atividades do aplicativo. Registrei quedas, por exemplo, de 11 para 4 segundos, outras de 9 para 3.
Fiquei muito satisfeito com o resultado, principalmente por não ter que encarar uma mudança profunda no mecanismo de armazenamento do aplicativo tão próximo da entrada em produção.
Se você também utiliza o SQLite Persistent Objects na sua app, pode baixar o patch aqui.
Acessando a lista de contatos do iPhone dentro da sua app
Escrito por Karin em 16/09/09 18:56
Olá pessoal!
Depois que descobri que não mais poderia enviar SMS dentro da minha app, tive que mudar a funcionalidade que estava implementando para deixar de ser envio de SMS para ser de email. Mas eu não queria que este envio de email fosse feito fora da minha aplicação por meio do Mail App. Então decidi criar um formulário de email com a mesma facilidade de poder acessar a lista de contatos do iPhone e escoher o email de um contato já cadastrado.
Pesquisando, achei a referência no iPhone Dev Center de como acessar a lista. Mas como sou legal, segue um exemplo.
Primeiro, adicione um botão à sua view e crie um método para este botão no controller. Neste método, a tela de contatos será chamada e exibida:
- (IBAction)clickAdicionaEmail:(id)sender {
ABPeoplePickerNavigationController *picker = [[ABPeoplePickerNavigationController alloc] init];
picker.peoplePickerDelegate = self;
[self presentModalViewController:picker animated:YES];
[picker release];
}
A seguir, o delegate da classe ABPeoplePickerNavigationController deve ser implementado, conforme vemos abaixo:
- (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker {
[self dismissModalViewControllerAnimated:YES];
}
O método acima é chamado quando a ação de exibir a lista de contatos é cancelada. No nosso exemplo, simplesmente a tela é fechada.
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person {
return YES;
}
Este segundo é chamado após um contato da lista é selecionado. Acima, este método retorna YES porque quero selecionar uma informação do contato e não o próprio, ou seja, se fosse necessário somente o nome do contato, era só pegar esta informação e retornar NO no método.
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier {
if (property == kABPersonEmailProperty) {
CFTypeRef emails = ABRecordCopyValue(person, property);
CFIndex indiceEmail = ABMultiValueGetIndexForIdentifier(emails, identifier);
self.emailContato.text = (NSString *)ABMultiValueCopyValueAtIndex(emails, indiceEmail);
[self dismissModalViewControllerAnimated:YES];
}
return NO;
}
Já o último é quando desejo selecionar um informação do contato, como, por exemplo, o email. No nosso caso, email é uma propriedade multivalorada, ou seja, o contato pode ter mais de um, por isso, precisamos saber exatamente qual queremos. No exemplo, qualquer email selecionado do contato é válido.
Pronto! É assim que conseguimos selecionar email dos nossos contatos do iPhone na nossa app.
Até a próxima :)
Leia também:
Mobits no Diário do Comércio
Escrito por Hildi em 08/09/09 18:41
Confira a matéria sobre a Mobits no Diário do Comércio!
A Mobits acabou de ganhar destaque na matéria do Diário do Comércio. Para conferir acesse: http://www.dcomercio.com.br/Materia.aspx?id=26483&canal=53
Ficamos muito felizes em ver nosso trabalho mais uma vez exposto na mídia!
Leia também
Promoção do Mobits Button Soccer. Somente hoje!
Escrito por Felipe Barreto em 05/09/09 09:00
Promoção especial no dia de Brasil x Argentina! 50% de desconto! Aproveite e indique aos amigos!
Não perca! Por causa do clássico internacional Brasil x Argentina - que são os times disponíveis no MBS - a Mobits decidiu fazer uma promoção do jogo liberando-o por 50% de desconto.
A promoção ficará no ar somente hoje, então não dê bobeira!
Aproveito para avisar que já estamos devenvolvendo a próxima versão do jogo a partir das sugestões de todos os usuários. As mudanças previstas para entrar são:
- Inteligencia Artificial
- Escolha de times
A previsão é que esta versão esteja disponível dentro de um mês. Então fiquem ligados no nosso twitter (@futeboldebotao) para saber mais detalhes.
Envio de SMS: Java ME x iPhone OS
Escrito por Karin em 01/09/09 19:25
Olá pessoal,
estou aqui para contar para vocês a minha última saga com o desenvolvimento de aplicativos em Java ME e iPhone OS. Dessa vez parecia ser uma funcionalidade boba, mas que me rendeu algumas horinhas e trabalho jogado fora. :(
A funcionalidade era envio de SMS. Primeiro desenvolvi em Java ME. Não foi difícil pesquisar e encontrar o algoritmo que fizesse isso. O mais chato foi ter a certeza de que este algoritmo realmente funcionava para os mais diversos celulares.
Para que vocês não tenham o mesmo tabalho que tive, segue o algoritmo de envio de email em Java ME:
try {
MessageConnection connection = (MessageConnection)Connector.open("sms://99999999");
TextMessage sms = (TextMessage)connection.newMessage(MessageConnection.TEXT_MESSAGE);
sms.setPayloadText("Olá");
connection.send(sms);
connection.close();
}
catch (SecurityException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
catch (Exception e) {
e.printStackTrace();
}
Terminado o desenvolvimento em Java ME, fui em busca do algoritmo que fizesse o mesmo em iPhone. Porém, como para mim era certo de que acharia a solução, fiz logo um formulário de envio de SMS. Mas não estava certa. Depois de inúmeras pesquisas, inclusive em fóruns da própria Apple, a única coisa que descobri foi que os aplicativos para iPhone que desejam enviar SMS, só farão isso fora do mesmo, ou seja, a única coisa que fazemos é chamar o aplicativo de mensagens que o iPhone possui. E para piorar, nem a mensagem pode ser preenchida pelo nosso aplicativo, somente o número. Então, percebi que de nada adiantou meu esforço para fazer o formulário de envio de SMS. Dessa vez a Apple me decepcionou. :(
Segue abaixo, como chamar o aplicativo de mensagens do iPhone:
NSString *stringURL = @"sms:99999999";
NSURL *url = [NSURL URLWithString:stringURL];
[[UIApplication sharedApplication] openURL:url];
Ah, e não se esqueçam que depois de enviada sua mensagem SMS, o seu aplicativo não volta, ou seja, o usuário precisa novamente acessá-lo.
É, dessa vez os créditos estão todos com o Java ME! Espero sinceramente que a Apple libere o envio de SMS de dentro de aplicativos desenvolvidos por nós.
Valeu :)