-->

How to show migration progress of NSMigrationManag

2019-04-15 06:08发布

问题:

I have a lot of binary data on Core Data which I would like to remove and save as files so I setup the code so that it would run a manual migration.

The problem is that because I have to pull binary from Core Data and save them to files, the migration process could take a very long time depending on how much data there is.

I would like to show a simple progress label so that users wouldn't think that the app is frozen.

// app did launch
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    ...
    // add the progress label
    self.progressLabel = [[UILabel alloc] initWithFrame:CGRectMake(103, 36, 114, 21)];
    [self.progressLabel setText:@"0%"];
    [self.window addSubview:self.progressLabel];
    [self.window makeKeyAndVisible];
    ...
    // start migration
    [self progressivelyMigrateURL:storeURL];
    ...
}

// manually migrate store
- (BOOL)progressivelyMigrateURL:(NSURL*)sourceStoreURL
{
    NSLog(@"start progressivelyMigrateURL");
    ...
    // create the migration manager
    NSMigrationManager *manager = [[NSMigrationManager alloc] initWithSourceModel:sourceModel destinationModel:targetModel];
    ...
    // register for KVO of migration progress here
    [manager addObserver:self forKeyPath:@"migrationProgress" options:NSKeyValueObservingOptionNew context:NULL];

    // start the migration
    if (![manager migrateStoreFromURL:sourceStoreURL type:type options:nil withMappingModel:mappingModel toDestinationURL:destinationStoreURL destinationType:type destinationOptions:nil error:&error])
    {
        return NO;
    }
    ...
}

// handle KVO of migration process here
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqual:@"migrationProgress"])
    {
        float migrationProgress = [[change objectForKey:NSKeyValueChangeNewKey] floatValue];
        int percent = migrationProgress * 100;
        [self.progressLabel setText:[NSString stringWithFormat:@"%d%", percent]];
        [self.progressLabel layoutIfNeeded];
        [self.progressLabel setNeedsDisplay];

        NSLog(@"migrationProgress: %d", percent);
        NSLog(@"self.testLabel text: %@", self.testLabel.text);
    }
}

The migration runs perfectly and I can confirm that the NSLog is returning incrementing values while the migration is taking place.

The problem is that self.progressLabel does not redraw every time the progress is updated and just skips from 0% to 100% while the migration is taking place. I know I can't run the migration on a separate thread. How can I make this work?

回答1:

You precisely need to perform the migration on a background thread, and update the UI as you get progress notifications on the main thread.

- (void)startMigration {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
    NSMigrationManager  * manager = [[NSMigrationManager alloc] initWithSourceModel:sourceModel destinationModel:destinationModel];
    [manager addObserver:self forKeyPath:@"migrationProgress" options:0 context:NULL];
    BOOL success = [manager migrateStoreFromURL:sourceStoreURL type:type options:nil withMappingModel:mappingModel toDestinationURL:destinationStoreURL destinationType:type destinationOptions:nil error:&error];
    [manager removeObserver:self forKeyPath:@"migrationProgress"];
    if(success) {
        dispatch_async(dispatch_get_main_queue(), ^{
        // Go back to the main thread and continue…
        });
    }
}

And when getting notifications:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    dispatch_sync(dispatch_get_main_queue(), ^{
        CGFloat progress = [(NSMigrationManager *)object migrationProgress];
        // Update UI progress indicator
    });
}