Exemplo ou explicação da migração de dados principais com várias passagens?

85

Meu aplicativo para iPhone precisa migrar seu armazenamento de dados principal e alguns dos bancos de dados são muito grandes. A documentação da Apple sugere o uso de "passagens múltiplas" para migrar dados e reduzir o uso de memória. No entanto, a documentação é muito limitada e não explica muito bem como fazer isso. Alguém pode me indicar um bom exemplo ou explicar em detalhes o processo de como realmente conseguir isso?

Jason
fonte
você teve problemas de memória na verdade? Sua migração é leve ou deseja usar um NSMigrationManager?
Nick Weaver
Sim, o console GDB mostrou que havia avisos de memória e, em seguida, o aplicativo travou devido à memória limitada. Eu tentei a migração leve e o NSMigrationManager, mas agora estou tentando usar o NSMigrationManager.
Jason
ok, você pode entrar um pouco mais em detalhes o que mudou?
Nick Weaver
finalmente, descobri, li minha resposta.
Nick Weaver
Olá Jason, você poderia corrigir o like na pergunta?
Yuchen Zhong,

Respostas:

175

Eu descobri o que a Apple sugere em sua documentação . Na verdade, é muito fácil, mas um longo caminho a percorrer antes que seja óbvio. Ilustrarei a explicação com um exemplo. A situação inicial é esta:

Versão 1 do modelo de dados

insira a descrição da imagem aqui insira a descrição da imagem aqui

É o modelo que você obtém ao criar um projeto com o modelo "aplicativo baseado em navegação com armazenamento de dados centrais". Eu compilei e fiz algumas batidas fortes com a ajuda de um loop for para criar cerca de 2k entradas, todas com alguns valores diferentes. Lá vamos nós 2.000 eventos com um valor NSDate.

Agora adicionamos uma segunda versão do modelo de dados, que se parece com isto:

insira a descrição da imagem aqui

Versão do modelo de dados 2

A diferença é: a entidade Event se foi e nós temos duas novas. Um que armazena um carimbo de data / hora como um doublee o segundo que deve armazenar uma data como NSString.

O objetivo é transferir todos os eventos da versão 1 para as duas novas entidades e converter os valores ao longo da migração. Isso resulta em duas vezes os valores de cada um como um tipo diferente em uma entidade separada.

Para migrar, escolhemos a migração manualmente e isso fazemos com modelos de mapeamento. Esta também é a primeira parte da resposta à sua pergunta. Faremos a migração em duas etapas, porque está demorando muito para migrar 2k entradas e gostamos de manter o consumo de memória baixo.

Você pode até mesmo ir em frente e dividir ainda mais esses modelos de mapeamento para migrar apenas intervalos das entidades. Digamos que tenhamos um milhão de registros, isso pode travar todo o processo. É possível restringir as entidades buscadas com um predicado de Filtro .

De volta aos nossos dois modelos de mapeamento.

Criamos o primeiro modelo de mapeamento assim:

1. Novo Arquivo -> Recurso -> Modelo de Mapeamento insira a descrição da imagem aqui

2. Escolha um nome, eu escolhi StepOne

3. Defina o modelo de dados de origem e destino

insira a descrição da imagem aqui

Mapping Model Step One

insira a descrição da imagem aqui

insira a descrição da imagem aqui

insira a descrição da imagem aqui

A migração em várias etapas não precisa de políticas de migração de entidade personalizadas, no entanto, faremos isso para obter um pouco mais de detalhes neste exemplo. Portanto, adicionamos uma política personalizada à entidade. É sempre uma subclasse de NSEntityMigrationPolicy.

insira a descrição da imagem aqui

Esta classe de política implementa alguns métodos para fazer nossa migração acontecer. No entanto, é simples, neste caso, por isso vamos ter de implementar apenas um método: createDestinationInstancesForSourceInstance:entityMapping:manager:error:.

A implementação será semelhante a esta:

StepOneEntityMigrationPolicy.m

#import "StepOneEntityMigrationPolicy.h"


@implementation StepOneEntityMigrationPolicy

- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance 
                                      entityMapping:(NSEntityMapping *)mapping 
                                            manager:(NSMigrationManager *)manager 
                                              error:(NSError **)error
{
    // Create a new object for the model context
    NSManagedObject *newObject = 
        [NSEntityDescription insertNewObjectForEntityForName:[mapping destinationEntityName] 
                                      inManagedObjectContext:[manager destinationContext]];

    // do our transfer of nsdate to nsstring
    NSDate *date = [sInstance valueForKey:@"timeStamp"];
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
    [dateFormatter setDateStyle:NSDateFormatterMediumStyle];    

    // set the value for our new object
    [newObject setValue:[dateFormatter stringFromDate:date] forKey:@"printedDate"];
    [dateFormatter release];

    // do the coupling of old and new
    [manager associateSourceInstance:sInstance withDestinationInstance:newObject forEntityMapping:mapping];

    return YES;
}

Etapa final: a própria migração

Vou pular a parte de configuração do segundo modelo de mapeamento que é quase idêntico, apenas um timeIntervalSince1970 usado para converter o NSDate em um duplo.

Finalmente, precisamos acionar a migração. Vou pular o código padrão por enquanto. Se precisar, postarei aqui. Ele pode ser encontrado em Customizing the Migration Process e é apenas uma fusão dos dois primeiros exemplos de código. A terceira e última parte será modificada da seguinte maneira: Em vez de usar o método de NSMappingModelclasse da classe mappingModelFromBundles:forSourceModel:destinationModel:, usaremos o initWithContentsOfURL:porque o método de classe retornará apenas um, talvez o primeiro, modelo de mapeamento encontrado no pacote.

Agora temos os dois modelos de mapeamento que podem ser usados ​​em cada passagem do loop e enviar o método de migração para o gerenciador de migração. É isso aí.

NSArray *mappingModelNames = [NSArray arrayWithObjects:@"StepOne", @"StepTwo", nil];
NSDictionary *sourceStoreOptions = nil;

NSURL *destinationStoreURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataMigrationNew.sqlite"];

NSString *destinationStoreType = NSSQLiteStoreType;

NSDictionary *destinationStoreOptions = nil;

for (NSString *mappingModelName in mappingModelNames) {
    NSURL *fileURL = [[NSBundle mainBundle] URLForResource:mappingModelName withExtension:@"cdm"];

    NSMappingModel *mappingModel = [[NSMappingModel alloc] initWithContentsOfURL:fileURL];

    BOOL ok = [migrationManager migrateStoreFromURL:sourceStoreURL
                                               type:sourceStoreType
                                            options:sourceStoreOptions
                                   withMappingModel:mappingModel
                                   toDestinationURL:destinationStoreURL
                                    destinationType:destinationStoreType
                                 destinationOptions:destinationStoreOptions
                                              error:&error2];
    [mappingModel release];
} 

Notas

  • Um modelo de mapeamento termina em cdmpacote.

  • O armazenamento de destino deve ser fornecido e não deve ser o armazenamento de origem. Após a migração bem-sucedida, você pode excluir o antigo e renomear o novo.

  • Fiz algumas alterações no modelo de dados após a criação dos modelos de mapeamento, isso resultou em alguns erros de compatibilidade, que só consegui resolver recriando os modelos de mapeamento.

Nick Weaver
fonte
59
Caramba, isso é complicado. O que a Apple estava pensando?
aroth
7
Não sei, mas sempre que acho que os dados centrais são uma boa ideia, tento muito encontrar uma solução mais simples e mais sustentável.
Nick Weaver
5
Obrigado! Esta é uma resposta excelente. Parece complicado, mas não é tão ruim assim que você aprende as etapas. O maior problema é que a documentação não explica isso para você dessa forma.
bentford
2
Aqui está o link atualizado para Customizing the Migration Process. Ele mudou desde que este post foi escrito. developer.apple.com/library/ios/documentation/Cocoa/Conceptual/…
user1021430
@NickWeaver como você está determinando o destinationStoreURL? Você está criando ou ele é criado pelo sistema de dados principal durante o processo de migração ????
dev gr
3

Essas questões estão relacionadas:

Problemas de memória ao migrar grandes armazenamentos de dados CoreData no iPhone

Multiple Pass Core Data Migration em blocos com iOS

Para citar o primeiro link:

Isso é discutido na documentação oficial na seção "Multiple Passes", no entanto, parece que a abordagem sugerida é dividir sua migração por tipo de entidade, ou seja, fazer vários modelos de mapeamento, cada um deles migrando um subconjunto dos tipos de entidade do modelo de dados completo.

occulus
fonte
1
Obrigado pelos links. O problema é que ninguém explica em detalhes como configurá-lo em várias passagens. Como devo configurar vários modelos de mapeamento para que funcionem de forma eficaz?
Jason
-5

Suponha que seu esquema de banco de dados tenha 5 entidades, por exemplo, pessoa, aluno, curso, classe e registro para usar o tipo padrão de exemplo, onde as subclasses de aluno pessoa, classe implementam curso e registro juntam-se à classe e ao aluno. Se você fez alterações em todas essas definições de tabela, você deve começar nas classes básicas e ir subindo. Portanto, você não pode começar com a conversão de inscrições, porque cada registro de inscrição depende da presença de turma e alunos. Portanto, você começaria migrando apenas a tabela Person, copiando as linhas existentes para a nova tabela, preenchendo todos os novos campos existentes (se possível) e descartando as colunas removidas. Faça cada migração dentro de um pool de liberação automática, de modo que, uma vez feita, sua memória volte a iniciar.

Assim que a tabela Person estiver pronta, você poderá converter a tabela do aluno. Em seguida, pule para o Curso e depois para a Classe e, finalmente, para a mesa de Registro.

A outra consideração é o número de registros, se como Pessoa tivesse mil linhas, você teria que, a cada 100 ou mais, executar o NSManagedObject equivalente a uma versão, que é para informar o contexto do objeto gerenciado [moc refreshObject: ob mergeChanges: NÃO]; Defina também o temporizador de dados obsoletos para baixo, para que a memória seja liberada com frequência.

PapaSmurf
fonte
Então, você está essencialmente sugerindo ter um novo esquema de dados principais que não faz parte do esquema antigo e copiar os dados para o novo esquema manualmente?
Jason
-1 Mapear manualmente seu banco de dados não é necessário. Você pode migrar bancos de dados implantados usando migração leve ou com MappingModels explícitos.
bentford