Design Pattern para iOS – Parte 3

Continuando a tradução sobre padrões de desenvolvimento, esta é a terceira pate, lembrando que esta tradução pode conter erros, e para devida consulta ir ao link para o site de origem: http://www.raywenderlich.com/46988/ios-design-patterns

Observer Pattern

Em Observer pattern, um objeto notifica outros objetos sobre qualquer mudança de status. Os objetos envolvidos não precisam saber sobre o outro – incentivando assim um design desacoplado. Este padrão é mais freqüentemente usado para notificar os objetos interessados ​​quando uma propriedade é alterada.

A implementação habitual exige que um observador registre interesse no estado de outro objeto. Quando há mudanças de estado, todos os objetos de observação são notificados da mudança. Serviço de Push Notification da Apple é um exemplo global disto.

Se você quiser manter o conceito MVC (dica: que você já fez), é necessário permitir que os objetos do modelo se comuniquem com objetos View, mas sem referências diretas entre eles. E é aí que o padrão Observer entra.

Cocoa implementa o padrão observador de duas formas conhecidas: Notificações e Key-Value Observing (KVO).

Notifications (Notificações)

Não se deve confundir com Push ou Local notifications, as notificações são baseadas em um modelo de subscribe-and-publish que permite que um objeto (o publisher) a enviar mensagens para outros objetos (subscribers/listeners). O publisher não precisa saber nada sobre os subscribers.

As notificações são muito utilizadas pela Apple. Por exemplo, quando o teclado é exibido / oculto o sistema envia uma UIKeyboardWillShowNotification / UIKeyboardWillHideNotification, respectivamente. Quando seu aplicativo vai para background, o sistema envia uma notificação UIApplicationDidEnterBackgroundNotification.

Nota: Abra UIApplication.h, no final do arquivo, você verá uma lista de mais de 20 notificações enviadas pelo sistema.

Como Usar Notifications

Vá para AlbumView.m e insira o seguinte código após [self addSubview: indicator]; em initWithFrame: albumCover ::

[[NSNotificationCenter defaultCenter]

postNotificationName:@"BLDownloadImageNotification"
object:self

userInfo:@{@"imageView":coverImage,

@"coverUrl":albumCover}];

Esta linha envia uma notificação através do singleton NSNotificationCenter. A informação notificação contém o UIImageView para preencher a URL da imagem da capa para ser baixado. Essa é toda a informação que você precisa para realizar a tarefa de download de cobertura.

Adicione a seguinte linha para o init em LibraryAPI.m, logo após isOnline = NO:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadImage:) name:@"BLDownloadImageNotification" object:nil];

Este é o outro lado da equação: o observador. Cada vez que a classe AlbumView posta uma notificação BLDownloadImageNotification, desde que LibraryAPI tenha um observador registrado para a mesma notificação, o sistema notifica LibraryAPI. E LibraryAPI executa downloadImage: em resposta.

No entanto, antes de você implementar downloadImage: você deve se lembrar de unsubscribe desta notificação quando sua classe é desalocada. Se não fizer o unsubscribe corretamente da notificação, sua classe registrada, uma notificação pode ser enviada para uma instancia desalocada. Isso pode causar crash na aplicação.

Adicione o seguinte método para LibraryAPI.m

- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

Quando essa classe é desalocada, isso remove seu próprio observador de todas as notificações a quias está registrado.

Há mais uma coisa a fazer. Provavelmente seria uma boa idéia para salvar as capas baixadas localmente para que o aplicativo não precise baixar as mesmas capas outra vez.

Abra PersistencyManager.h e adicione os dois seguintes protótipos de métodos:

- (void)saveImage:(UIImage*)image filename:(NSString*)filename;
- (UIImage*)getImage:(NSString*)filename;

E adcione a implementações dos métodos em PersistencyManager.m:

- (void)saveImage:(UIImage*)image filename:(NSString*)filename
{
filename = [NSHomeDirectory() stringByAppendingFormat:@"/Documents/%@", filename];
NSData *data = UIImagePNGRepresentation(image);
[data writeToFile:filename atomically:YES];
}

- (UIImage*)getImage:(NSString*)filename
{
filename = [NSHomeDirectory() stringByAppendingFormat:@"/Documents/%@", filename];
NSData *data = [NSData dataWithContentsOfFile:filename];
return [UIImage imageWithData:data];
}

Este código é bastante simples. As imagens transferidas serão salvas no diretório Documentos e getImage: retornará nulo se um arquivo correspondente não for encontrado no diretório de documentos.

Agora adicione o seguinte método em LibraryAPI.m

- (void)downloadImage:(NSNotification*)notification {

  // 1
  UIImageView *imageView = notification.userInfo[@"imageView"];
  NSString *coverUrl = notification.userInfo[@"coverUrl"];

  // 2
  imageView.image = [persistencyManager getImage:[coverUrl lastPathComponent]];

  if (imageView.image == nil){
     // 3
     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     UIImage *image = [httpClient downloadImage:coverUrl];

     // 4
     dispatch_sync(dispatch_get_main_queue(), ^{
         imageView.image = image;
         [persistencyManager saveImage:image filename:[coverUrl lastPathComponent]];
    });
   });
  }
}

Aqui está uma explicação do código acima:

1 – downloadImage é executada através de notificações e assim o método recebe o objeto de notificação como parâmetro. O UIImageView e URL da imagem são obtidos a partir da notificação.
2 – Obtem a imagem do PersistencyManager se ele foi baixada previamente.
3 – Se a imagem ainda não tiver sido transferida, então ela é obtida usando HTTPClient.
4 – Quando o download estiver concluído, exibir a imagem no modo de exibição de imagem e usar o PersistencyManager para salvá-lo localmente.

Mais uma vez, você está usando o padrão Facade para ocultar a complexidade da transferência de uma imagem das outras classes. O emissor de notificação não se importa se a imagem veio da web ou do sistema de arquivos.

Build e execute o aplicativo e confira as belas capas dentro de sua HorizontalScroller:

3-1

Pare o aplicativo e execute o novamente. Observe que não há atraso no carregamento das capas porque elas foram salvas localmente. Você pode até se desconectar da Internet e seu aplicativo irá funcionar perfeitamente. No entanto, há um pouco estranho aqui: o spinner nunca pára de girar! O que está acontecendo?

Você começou o spinner ao fazer o download da imagem, mas você não implementou a lógica para parar o spinner, uma vez a imagem está baixada. Você poderia enviar uma notificação toda vez que uma imagem foi baixada, mas em vez disso, você vai fazer isso usando o outro padrão Observer, o KVO.

Key-Value Observing (KVO)

Em KVO, um objeto pode pedir para ser notificado sobre quaisquer alterações a uma propriedade específica; ou a sua própria ou a de um outro objeto. Se você estiver interessado, você pode ler mais sobre isso em Apple’s KVO Programming Guide.

Como Usar o KVO Pattern

Como mencionado acima, o mecanismo de KVO permite que um objeto observe alterações em uma propriedade. No seu caso, você pode usar KVO para observar alterações na propriedade imagem do UIImageView que mantém a imagem.

Abra AlbumView.m e adicione o seguinte código em initWithFrame: albumCover :, logo após a linha [self addSubview: indicator];:

[coverImage addObserver:self forKeyPath:@”image” options:0 context:nil];

Isso adiciona self, que é a classe atual, como observador para a propriedade imagem de Cover.

Também é necessário cancelar o registro de um observador quando tiver terminado. Ainda em AlbumView.m, adicione o seguinte código:

- (void)dealloc
{
[coverImage removeObserver:self forKeyPath:@"image"];
}

Finalmente adicione este código:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
   if ([keyPath isEqualToString:@"image"]){
     [indicator stopAnimating];
   }
}

Você deve implementar esse método em cada classe que trabalha como observador. O sistema executa esse método cada vez que há alterações na propriedade observada. No código acima, você interrompe o spinner quando a propriedade “imagem” é alterada. Dessa forma, quando uma imagem é carregada, o spinner vai parar de girar.

Build e execute o seu projeto. O spinner deve desaparecer:

3-2

Nota: Lembre-se sempre de remover seus observadores quando estão desalocados, ou então o seu aplicativo irá falhar quando o sujeito tenta enviar mensagens para esses observadores inexistentes!

Se você brincar com seu aplicativo um pouco e terminá-lo, você vai perceber que o estado de seu aplicativo não é salvo. O último álbum que você viu não será o álbum padrão quando o aplicativo inicia.

Para corrigir isso, você pode fazer uso do padrão seguinte na lista: Memento.

Memento Pattern

O padrão memento captura e externaliza o estado interno de um objeto. Em outras palavras, ele salva suas coisas em algum lugar. Mais tarde, este estado exteriorizada pode ser restaurada sem violar o encapsulamento; ou seja, os dados privados permanecem privados.

Como Usar O Memento Pattern

Adicone o seguinte método a ViewController.m:


- (void)saveCurrentState{
  // Quando o usuário sai do aplicativo e depois volta novamente, ele quer que esteja no mesmo estado
  // que ele deixou. Para fazer isso, precisamos guardar o álbum exibido atualmente.
  // Uma vez que é apenas um pedaço de informação então podemos usar 

   // NSUserDefaults.
  [[NSUserDefaults  standardUserDefaults] setInteger:currentAlbumIndex forKey:@"currentAlbumIndex"];
}
- (void)loadPreviousState{
  currentAlbumIndex = [[NSUserDefaults standardUserDefaults] integerForKey:@"currentAlbumIndex"];
  [self showDataForAlbumAtIndex:currentAlbumIndex];
}

saveCurrentState salva o atual índice de álbum para NSUserDefaults – NSUserDefaults é um repositório de dados padrão fornecido pelo iOS para salvar as configurações específicas de aplicativos e dados.

loadPreviousState carrega o índice salvo anteriormente. Esta não é a plena implementação do padrão Memento, mas você está chegando lá.

Agora, adicione a seguinte linha ao viewDidLoad em ViewController.m antes da inicialização do scroller:

[self loadPreviousState];

Isso carrega o estado salvo anteriormente quando o aplicativo é iniciado. Mas onde você salva o estado atual do aplicativo, para carregar a partir dele? Você vai usar Notifications para fazer isso. iOS envia uma notificação UIApplicationDidEnterBackgroundNotification quando o aplicativo entra em Background. Você pode usar essa notificação para chamar saveCurrentState. Isso não é conveniente?

Adicione a seguinte linha ao final de viewDidLoad:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveCurrentState) name:UIApplicationDidEnterBackgroundNotification object:nil];

Agora, quando o aplicativo está prestes a entrar em background, o ViewController irá automaticamente salvar o estado atual chamando saveCurrentState.

Agora, adicione o seguinte código:

- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

Isso garante que você remova a classe como um observador quando a ViewController é liberada.

Build e execute o aplicativo. Navegue até um dos álbuns, coloque o aplicativo para em background Command + Shift + H (se você estiver no simulador) e, em seguida, encerre o aplicativo. Reinicie e verifique se o álbum selecionado anteriormente está no centro:

3-3

Parece que os dados do album estam corretos, mas o scroller não está centralizado no álbum correto. Oque houve?

Método opcional initialViewIndexForHorizontalScroller: é feito para isto! Dado que esse método não é implementado no delegate, neste caso o é ViewController, a view inicial é sempre definido como a primeira view.

Para corrigir isso, adicione o seguinte código para ViewController.m:

- (NSInteger)initialViewIndexForHorizontalScroller:(HorizontalScroller *)scroller{

return currentAlbumIndex;

}

Agora, a primeira view HorizontalScroller é configurado para qualquer álbum é indicado por currentAlbumIndex. Esta é uma ótima maneira de certificar-se da experiência de aplicação permanece pessoal e retomável.

Execute o aplicativo novamente. Vá até um álbum como antes, pare o aplicativo, em seguida, reinicie para verificar que o problema seja corrigido:

3-4

Se você olhar para o init do PersistencyManager, você verá que os dados do álbum são codificados e recriados cada vez que PersistencyManager é criado. Porém é melhor para criar a lista de álbuns de uma vez e armazená-los em um arquivo. Como você salva os dados de álbum para um arquivo?

Uma opção é fazer uma iteração através das propriedades do álbum, salvá-los em um arquivo plist e recriar as instâncias do álbum quando eles são necessários. Esta não é a melhor opção, uma vez que requer que você escreva um código específico, dependendo de qual data / propriedades existem em cada classe. Por exemplo, se você mais tarde criar uma classe de filme com propriedades diferentes, o salvamento e carregamento de dados exigiria um novo código.

Além disso, você não será capaz de salvar as variáveis ​​particulares para cada instância de classe, uma vez que não são acessíveis para uma classe externa. É exatamente por isso que a Apple criou o mecanismo de Archiving (arquivamento).

Acompanhe a quarta parte do tutorial: Design Pattern para iOS – Parte 4

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s