quinta-feira, 12 de julho de 2012

Persistência no iOS com Core Data

Olá povo,

Eu já mostrei aqui no blog como persistir informações no iOS utilizando o SQLite. Para quem quer ter um  controle maior sobre o que está sendo salvo no banco de dados, é uma boa opção. Entretanto a Core Data realiza o mapeamento objeto-relacional de uma forma bem interessante e nos poupa de escrever um monte de código de baixo nível (uma vez que a lib do SQLite é escrita em C).

Crie um novo projeto no Xcode do tipo Single View Application e mãos à obra! Se você selecionar o template Master/Detail application no assistente, aparecerá a opção para utilizar Core Data. Isso servirá para incluir a lib do Core Data no projeto e inserir algum código para nós. Se não usar esse template, teremos que adicionar manualmente. Para isso, basta selecionar o projeto, e na aba Build Fases na seção Link Binary With Libraries e clicar em "+". Na lista que será exibida, selecione CoreData.framework.

Vamos agora adicionar ao projeto o arquivo onde ficarão as definições das entidades que serão persistidas no banco. No Xcode, selecione File | New | File... selecione a opção Core Data no lado esquerdo, e então selecione a opção Data Model. Clique em Next e nomeie o arquivo para CarroModel, em seguida,  clique em Create para concluir o assistente.

Selecione o arquivo recém criado, e clique no botão Add Entity que fica na parte inferior. Renomeie a entidade para Carro. Em seguida, clique no botão Add Attribute, selecione o atributo e o renomeie para "nome" e modifique seu tipo (na janela da direita) para String (através da propriedade Attribute Type). Adicione mais dois atributos, placa e ano, e modifique os seus tipos para String e Int16 respectivamente.
Abaixo a imagem do model após nossas alterações.
Vamos agora criar a classe carro que será persistida no banco de dados. Selecione a entidade Carro e clique no menu File | New | File... Na janela que for exibida, selecione Core Data no lado esquerdo, e então selecione NSManagedObject subclass. Será criada a classe Carro conforme abaixo.
// Carro.h -----------------------------
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>


@interface Carro : NSManagedObject

@property (nonatomic, retain) NSString * nome;
@property (nonatomic, retain) NSString * placa;
@property (nonatomic, retain) NSNumber * ano;

@end

// Carro.m -----------------------------
#import "Carro.h"

@implementation Carro

@dynamic nome;
@dynamic placa;
@dynamic ano;

@end
Notem que nossa classe herda de NSManagedObject o que quer dizer que os objetos dessa classe serão persistidos. Outro detalhe é que os atributos são implementados com @dynamic.

Crie uma nova classe chamada RepositorioCarro e deixe-a conforme abaixo.

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@class Carro;

@interface RepositorioCarro : NSObject {
  NSManagedObjectModel *mom;
  NSPersistentStoreCoordinator *coordinator;
  NSManagedObjectContext *context;
}

- (void) test;
- (Carro *) newCarro;
- (void) salvar;
- (void) excluir:(Carro *)carro;
- (NSArray *) todosCarros;

@end
Nessa classe, declaramos um NSManagedObjectModel, NSPersistentStoreCoordinator e um NSManagedObjectContext. Vamos entender o papel de cada um:
- NSManagedObjectContext é a classe que vai realizar as operações com o banco. Logo, para inserir, alterar, excluir ou obter objetos (NSManagedObject) do banco, utilizamos métodos dessa classe.
- NSPersistentStoreCoordinator é como se fosse a conexão com o banco. É nela onde você define o local onde está armazenado o arquivo do banco e outras configurações adicionais.
- NSManagedObjectModel é o esquema do banco de dados. Que aqui no nosso projeto é o arquivo onde definimos o modelo da classe Carro.

O código abaixo inicia com o "construtor" da classe, que chama o método initContext. Esse método por sua vez chama o initCoordinator que por sua vez chama o initMom.

#import "RepositorioCarro.h"
#import "Carro.h"

@implementation RepositorioCarro

- (id) init {
  self = [super init];  
  [self initContext];
  return self;
}

// Retorna a URL para o diretório Documents
- (NSURL *)appDocsDir {
  return [[[NSFileManager defaultManager] 
    URLsForDirectory:NSDocumentDirectory 
      inDomains:NSUserDomainMask] lastObject];
}

// Carrega o arquivo de modelo
- (void) initMom {
  NSURL *modelURL = [[NSBundle mainBundle] 
    URLForResource:@"CarroModel" 
      withExtension:@"momd"];

  mom = [[NSManagedObjectModel alloc] 
    initWithContentsOfURL:modelURL];
}

// Abre a conexão com o banco
- (void) initCoordinator {
  [self initMom];    
  NSURL *storeURL = [[self appDocsDir] 
    URLByAppendingPathComponent:
     @"CarrosDB.sqlite"];
        
  NSError *error = nil;
  coordinator =
    [[NSPersistentStoreCoordinator alloc]
      initWithManagedObjectModel:mom];

  if (![coordinator 
    addPersistentStoreWithType:NSSQLiteStoreType
    configuration:nil URL:storeURL
    options:nil error:&error]) {
    NSLog(@"Erro... %@",
      [error localizedDescription]);
  }
}

// Inicializa o contexto
- (void) initContext {
  [self initCoordinator];
    
  context = [[NSManagedObjectContext alloc] init];
  [context setPersistentStoreCoordinator:coordinator];
}

- (void) test {
  // Testando inserção
  Carro *carro = [self newCarro];
    
  carro.nome  = @"Uno";
  carro.placa = @"UNO0001";
  carro.ano   = [NSNumber numberWithInt:2000];
    
  [self commit];

  // Testando alteração
  carro = @"Palio";
  [self commit];
  // Testando listagem...
  NSArray *fetchedObjects = [self todosCarros];
  for (Carro *carro in fetchedObjects) {
    NSLog(@"Nome: %@", carro.nome);
    NSLog(@"placa: %@", carro.placa);
  }
}

// A classe carro não deve ter construtor e 
// deve ser inicializada dessa forma.
- (Carro *) newCarro {
  return [NSEntityDescription
    insertNewObjectForEntityForName:@"Carro"
    inManagedObjectContext:context];

}

// As alterações que são feitas nos objetos
// são persistidas no banco ao dar commit
- (void) commit {
    NSError *error;
    if (![context save:&error]) {
      NSLog(@"Erro... %@", 
        [error localizedDescription]);
    }
}

- (void) excluir:(Carro *)carro {
    [context deleteObject:carro];  
}

- (NSArray *) todosCarros {
  NSFetchRequest *fetchRequest = 
    [[NSFetchRequest alloc] init];

  NSEntityDescription *entity = 
    [NSEntityDescription entityForName:@"Carro"
       inManagedObjectContext:context];

  [fetchRequest setEntity:entity];

  NSError *error;
  return [context 
    executeFetchRequest:fetchRequest 
      error:&error];
}

@end

O método newCarro cria uma nova instância de um objeto carro no contexto. Ou seja, se comitarmos esse objeto ele já estará persistido no banco. Se alterarmos uma propriedade de um objeto carro do array que é retornado pelo método todosCarros, e depois chamarmos o método commit, a alteração já é realizada no banco de dados.
O método test, como próprio nome diz, serve para testarmos nossa classe. Onde inserimos, alteramos e listamos as informações do banco.

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

Fonte: http://www.raywenderlich.com/934/core-data-on-ios-5-tutorial-getting-started

Nenhum comentário: