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 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
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
{
NSManagedObject *newObject =
[NSEntityDescription insertNewObjectForEntityForName:[mapping destinationEntityName]
inManagedObjectContext:[manager destinationContext]];
NSDate *date = [sInstance valueForKey:@"timeStamp"];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
[newObject setValue:[dateFormatter stringFromDate:date] forKey:@"printedDate"];
[dateFormatter release];
[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 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 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.