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?
85
Respostas:
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
É 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:
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
double
e o segundo que deve armazenar uma data comoNSString
.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
2. Escolha um nome, eu escolhi StepOne
3. Defina o modelo de dados de origem e destino
Mapping Model Step One
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
.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
NSMappingModel
classe da classemappingModelFromBundles:forSourceModel:destinationModel:
, usaremos oinitWithContentsOfURL:
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
cdm
pacote.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.
fonte
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:
fonte
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.
fonte