Objective C to Swift with CoreData and Cloud Documents

I have this old application written in Objective C that uses CoreData, backed with ICloud Documents.

https://i.stack.imgur.com/Q7FUa.png

Here's the setup for the old app

@property (nonatomic, strong) PersistentStack* persistentStack;

...

- (NSManagedObjectContext *)managedObjectContext
{
    if (!self.persistentStack) {
        NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"MyApp" withExtension:@"momd"];

        self.persistentStack = [[PersistentStack alloc] initWithStoreURL:self.storeURL modelURL:modelURL];

    }
    return self.persistentStack.managedObjectContext;
}
- (NSURL*)storeURL
{
    NSURL* documentsDirectory = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:NULL];
    return [documentsDirectory URLByAppendingPathComponent:@"MyApp.sqlite"];
}

_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
_managedObjectContext.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];

[option addEntriesFromDictionary:@{ NSPersistentStoreUbiquitousContentNameKey : @"iCloudStore" }];

[self.managedObjectContext.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:self.storeURL options:option error:&error];

This function fetches all the data. The entity defined in the .xcdatamodeld is called Nota

+(NSMutableArray*)getNoteInContext:(NSManagedObjectContext*)context
{
    if(context==nil) context = [AppDelegate mainManagedObjectContext];

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    [fetchRequest setReturnsObjectsAsFaults:NO];

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

    [fetchRequest setEntity:entity];

    NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:@"timestamp" ascending:NO];

    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1, nil];
    [fetchRequest setSortDescriptors:sortDescriptors];

    NSError *error;
    NSArray *risposta = [context executeFetchRequest:fetchRequest error:&error];

    return [NSMutableArray arrayWithArray:risposta];
}

Here's the "new" app, written in Swift (SwiftUI)

I defined a class to manage the core data stuff

final class CoreDataManager {
        private(set) lazy var managedObjectContext: NSManagedObjectContext = {
        let managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)

        managedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator

        return managedObjectContext
    }()

    private lazy var managedObjectModel: NSManagedObjectModel = {
        guard let modelURL = Bundle.main.url(forResource: "MyApp", withExtension: "momd") else {
            fatalError("Unable to Find Data Model")
        }

        guard let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL) else {
            fatalError("Unable to Load Data Model")
        }

        return managedObjectModel
    }()

    private lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
        let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)

        let fileManager = FileManager.default
        let storeName = "MyApp.sqlite"

        do {
            let documentsDirectoryURL = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)

            let persistentStoreURL = documentsDirectoryURL.appendingPathComponent(storeName)

            try persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: persistentStoreURL, options: nil)

        } catch {
            fatalError("Unable to Load Persistent Store")
        }

        return persistentStoreCoordinator
    }()
}

and here is how is consumed

struct Home: View {
    @State var notes: [Nota]?

    var body: some View {
        VStack {
            Text("Entries: \(self.notes?.count.description ?? "No entries at all")")
                .padding(.top)

        }.onAppear {
            let moc = CoreDataManager().managedObjectContext

            let fetchRequest = NSFetchRequest<Nota>()
            fetchRequest.returnsObjectsAsFaults = false
            let entity = NSEntityDescription.entity(forEntityName: "Nota", in: moc)
            fetchRequest.entity = entity
            let sortDescriptor1 = NSSortDescriptor(key: "timestamp", ascending: false)
            let sortDescriptors = [sortDescriptor1]
            fetchRequest.sortDescriptors = sortDescriptors

            do {
                self.notes = try moc.fetch(fetchRequest)

            } catch {
                fatalError("Failed to fetch notes: \(error)")
            }
        }
    }
}

In the new app, having the same CFBundleIdentifier, the same Developer account and the same Apple ID in either simulator or physical device, the data saved in the "Old" App is not available in the "New" one.

If I delete the old app and reinstall it, ICloud syncs correcly show me the data from the cloud. Even after a complete reset of the device (just in case...)

What is missing? Or what I've done wrong?

Thanks in advance!

How was PersistentStack defined in your Objective-Code?

@OOper I can't edit the question, so I added an answer to show the persistentstack code

Here the PersistentStack implementation

PersistenStack.h:

@import Foundation;
@import CoreData;

@protocol PersistentStackDelegate<NSObject>

- (void)storeWillChange;
- (void)storeDidChange;
- (void)storeDidImport;

@end

@interface PersistentStack : NSObject

@property (nonatomic,strong,readwrite) NSManagedObjectContext* managedObjectContext;
@property (nonatomic,strong,readwrite) NSManagedObjectContext* privateQueueContext;
@property (assign, nonatomic) id<PersistentStackDelegate> delegate;

- (void)saveContext;
- (NSManagedObjectContext *)managedObjectContext;
- (void)savePrivateContext;
- (NSManagedObjectContext *)privateQueueContext;
- (id)initWithStoreURL:(NSURL *)storeURL modelURL:(NSURL *)modelURL;

+(BOOL)isCloudAvaible;
+(void)display_iCloudAlertIfNeeded;
+(NSString*)checkForiCloudString;

@end

PersistentStack.m:

#import "PersistentStack.h"
import "Home.h"

@interface PersistentStack ()

@property (nonatomic,strong) NSURL* modelURL;
@property (nonatomic,strong) NSURL* storeURL;
@property (strong,nonatomic) NSTimer *iCloudUpdateTimer;
@property (readonly,nonatomic) NSUserDefaults *defaults;

@end

@implementation PersistentStack

-(NSUserDefaults*)defaults{
    NSUserDefaults *def = ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)?[[NSUserDefaults alloc] initWithSuiteName:@"group.myapp.com"]:[NSUserDefaults standardUserDefaults];
    return def;
}

- (id)initWithStoreURL:(NSURL*)storeURL modelURL:(NSURL*)modelURL
{
    self = [super init];
    if (self) {
        self.storeURL = storeURL;
        self.modelURL = modelURL;
        [self setupManagedObjectContext];
    }
    return self;
}

+(BOOL)isCloudAvaible{
    NSURL *ubiquityURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
    BOOL risp = (ubiquityURL == nil);
    if(risp){
        NSLog(@"ubiquityurl %@",ubiquityURL);
    }

    return risp;
}

+(NSString*)checkForiCloudString
{
    if ([PersistentStack isCloudAvaible])
    {
        return NSLocalizedString(@"iCloud is not avalaible", nil);
    }

    return NSLocalizedString(@"iCloud is avalaible", nil);
}

+(void)display_iCloudAlertIfNeeded{
    if ([PersistentStack isCloudAvaible]){
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"iCloud Not Configured" message:@"Open iCloud Settings, and make sure you are logged in." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
        [alertView show];
    }
}

- (void)setupManagedObjectContext
{
    _managedObjectContext = nil;
    _privateQueueContext = nil;
    _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    _privateQueueContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];

    _managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
    _managedObjectContext.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];

    _privateQueueContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
    _privateQueueContext.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
    
    __weak NSPersistentStoreCoordinator *psc = self.managedObjectContext.persistentStoreCoordinator;
    
    NSError* error;
    
    NSMutableDictionary *option = [NSMutableDictionary dictionaryWithDictionary:@{NSMigratePersistentStoresAutomaticallyOption:@YES, NSInferMappingModelAutomaticallyOption:@YES}];

    if([self.defaults boolForKey:AGGIORNAMENTO_2015]){
        [NSThread sleepForTimeInterval:0.2];        
        [option addEntriesFromDictionary:@{ NSPersistentStoreUbiquitousContentNameKey : @"iCloudStore" }];
    }
    
    [self.managedObjectContext.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:self.storeURL options:option error:&error];
    
    if(![self.defaults boolForKey:AGGIORNAMENTO_2015]){
        NSArray *messe = [Messa getMesseIn:self.managedObjectContext];
        NSLog(@"Messe:%@",messe);
        if(messe.count>0){
            [AppDelegate saveContext];
            [NSThread sleepForTimeInterval:0.5];
            NSArray *messepostSave = [Messa getMesseIn:self.managedObjectContext];
            NSLog(@"Messe:%@",messepostSave);
        }
    }
    else{
        [AppDelegate saveContext];
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"DopoCloud");
    }

    
    [self.privateQueueContext.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:self.storeURL options:option error:&error];

    NSNotificationCenter *dc = [NSNotificationCenter defaultCenter];
    
    [dc removeObserver:self];
    
    [dc addObserver:self selector:@selector(storesWillChange:) name:NSPersistentStoreCoordinatorStoresWillChangeNotification object:psc];
    
    [dc addObserver:self selector:@selector(storesDidChange:) name:NSPersistentStoreCoordinatorStoresDidChangeNotification object:psc];
    
    [dc addObserver:self selector:@selector(persistentStoreDidImportUbiquitousContentChanges:) name:NSPersistentStoreDidImportUbiquitousContentChangesNotification object:psc];

    [dc addObserver:self selector:@selector(contextDidSaveMainQueueContext:) name:NSManagedObjectContextDidSaveNotification object:_managedObjectContext];
    
    [dc addObserver:self selector:@selector(contextDidSavePrivateQueueContext:) name:NSManagedObjectContextDidSaveNotification object:_privateQueueContext];
    
    [dc addObserver:self selector:@selector(setupManagedObjectContext) name:AGGIORNAMENTO2015_TERMINATO object:nil];

    if (error) {
        NSLog(@"error: %@", error);
    }
}

- (NSManagedObjectModel*)managedObjectModel
{
    return [[NSManagedObjectModel alloc] initWithContentsOfURL:self.modelURL];
}

- (void)persistentStoreDidImportUbiquitousContentChanges:(NSNotification*)note
{
    NSLog(@"%s", __PRETTY_FUNCTION__);
    NSLog(@"%@", note);
    
    if(!note){
        [[Home sharedInstance] alert:@"iCLoud Debug" andMex:[NSString stringWithFormat:@"%@",note] icon:nil okAction:nil withCancel:YES];
        return;
    }
    
    NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
    
    [managedObjectContext performBlockAndWait:^{

        [managedObjectContext mergeChangesFromContextDidSaveNotification:note];
        
        NSDictionary *changes = note.userInfo;
        NSMutableSet *allChanges = [NSMutableSet new];
        [allChanges unionSet:changes[NSInsertedObjectsKey]];
        [allChanges unionSet:changes[NSUpdatedObjectsKey]];
        [allChanges unionSet:changes[NSDeletedObjectsKey]];
        
        for (NSManagedObjectID *objID in allChanges) {
            NSLog(@"inporting:%@",objID);
        }

    }];
    
    [self createCloudTimer];
    
    if(self.delegate){
        [self.delegate storeDidImport];
    }
    
}
#pragma mark -
#pragma mark Utility method

void runOnMain(void (^block)(void))
{
    if ([NSThread isMainThread])
    {
        block();
    }
    else
    {
        dispatch_sync(dispatch_get_main_queue(), block);
    }
};

- (void)storesWillChange:(NSNotification *)note {
    NSManagedObjectContext *managedObjectContext = self.managedObjectContext;

    [managedObjectContext performBlockAndWait:^{
        NSError *error = nil;
        if ([managedObjectContext hasChanges]) {
            [managedObjectContext save:&error];
        }
        
        [managedObjectContext reset];
    }];
    
    if(self.delegate){
        [self.delegate storeWillChange];
    }
    
    [self createCloudTimer];
}

- (void)storesDidChange:(NSNotification *)note {

    [self createCloudTimer];
    
    if(self.delegate){
        [self.delegate storeDidChange];
    }
}

-(void)createCloudTimer{
    if (self.iCloudUpdateTimer != nil){
        [self.iCloudUpdateTimer invalidate];
    }
    self.iCloudUpdateTimer = nil;
    self.iCloudUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(notifyOfCoreDataUpdates) userInfo:nil repeats:NO];
}

-(void)notifyOfCoreDataUpdates{
    //aggiorno notifiche lette
    runOnMain(^{
        [[NSNotificationCenter defaultCenter] postNotificationName:AGGIORNAMENTO_ICLOUD_NOTIFICA object:nil];
    });
}

#pragma mark -
#pragma mark CORE-DATA SAVE

- (void)saveContext{
    runOnMain(^{
        [self MainsaveContext];
    });
}

- (void)MainsaveContext
{
    NSError *error = nil;
    NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
    if (managedObjectContext != nil) {
        if (![managedObjectContext save:&error]) {
            NSLog(@"PersistenStack: Errore saving:%@",[NSString stringWithFormat:@"%@, %@\n%@", error, [error localizedDescription],[error userInfo]]);
            
            [[[UIAlertView alloc] initWithTitle:@"Errore saving" message:[NSString stringWithFormat:@"%@, %@\n%@", error, [error localizedDescription],[error userInfo]] delegate:nil cancelButtonTitle:@"abort" otherButtonTitles:nil] show];
        }
    }
}

- (void)savePrivateContext{
    runOnMain(^{
        [self MainsavePrivateContext];
    });
}

- (void)MainsavePrivateContext
{
    NSError *error = nil;
    @synchronized(self) {
        NSManagedObjectContext *managedObjectContext = self.privateQueueContext;
        if (managedObjectContext != nil) {
            if (![managedObjectContext save:&error]) {
                NSLog(@"AppdelegatePrivate: Errore saving:%@",[NSString stringWithFormat:@"%@, %@\n%@", error, [error localizedDescription],[error userInfo]]);
                abort();
            }
        }
    }
}

#pragma mark CORE-DATA NOTIFACTION

- (void)contextDidSavePrivateQueueContext:(NSNotification *)notification
{
    @synchronized(self) {
    if([self.privateQueueContext hasChanges]){
        [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
        }
    }
}


- (void)contextDidSaveMainQueueContext:(NSNotification *)notification
{
    if([self.managedObjectContext hasChanges]){
        [self.privateQueueContext performBlock:^{
            [self.privateQueueContext mergeChangesFromContextDidSaveNotification:notification];
        }];
    }
}

@end

@OOPER I've simplified the objective-c code when porting to swift to remove legacy work and testing pieces. So PersistentStack.swift now appear like this:

class PersistentStack {
    var modelURL: URL
    var storeURL: URL

    var managedObjectContext: NSManagedObjectContext

    init(storeURL: URL, modelURL: URL) {
        self.storeURL = storeURL
        self.modelURL = modelURL

        var managedObjectModel: NSManagedObjectModel {
            return NSManagedObjectModel(contentsOf: modelURL)!
        }

        self.managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)

        self.managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
        self.managedObjectContext.persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)

        weak var psc = self.managedObjectContext.persistentStoreCoordinator

        let option = [
            NSMigratePersistentStoresAutomaticallyOption: NSNumber(value: true),
            NSInferMappingModelAutomaticallyOption: NSNumber(value: true),
            NSPersistentStoreUbiquitousContentNameKey: "iCloudStore"
        ] as [String: Any]

        do {
            try self.managedObjectContext.persistentStoreCoordinator?.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: option)

            self.saveContext()
            Thread.sleep(forTimeInterval: 0.5)
            print("DopoCloud")

            let dc = NotificationCenter.default

            dc.removeObserver(self)

            dc.addObserver(self, selector: #selector(self.storesWillChange), name: .NSPersistentStoreCoordinatorStoresWillChange, object: psc)

            dc.addObserver(self, selector: #selector(self.persistentStoreDidImportUbiquitousContentChanges), name: .NSPersistentStoreDidImportUbiquitousContentChanges, object: psc)
        } catch {
            print("error: \(error)")
        }
    }

    @objc func persistentStoreDidImportUbiquitousContentChanges(_ note: Notification?) {
        print("\(#function)")
        if let note = note {
            print("\(note)")
        }

        self.managedObjectContext.performAndWait {
            self.managedObjectContext.mergeChanges(fromContextDidSave: note!)

            if let changes = note?.userInfo {
                for objID in changes {
                    guard let objID = objID as? NSManagedObjectID else {
                        continue
                    }
                    print("importing:\(objID)")
                }
            }
        }
    }

    @objc func storesWillChange(_ note: Notification?) {
        self.managedObjectContext.performAndWait {
            if self.managedObjectContext.hasChanges {
                do {
                    try self.managedObjectContext.save()
                } catch {}
            }

            self.managedObjectContext.reset()
        }
    }

    func saveContext() {
        do {
            try self.managedObjectContext.save()
        } catch {
            print("PersistenStack: Errore saving:\("\(error), \(error.localizedDescription)\n\((error as NSError).userInfo)")")
        }
    }
}

and this is the modified Home.swift

struct Home: View {
    private let persistentStack: PersistentStack
    private var notes: [Nota]?

    init() {
        let storeUrl = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("iBreviary.sqlite")
        let modelURL = Bundle.main.url(forResource: "iBreviary", withExtension: "momd")

        self.persistentStack = PersistentStack(storeURL: storeUrl!, modelURL: modelURL!)

        let fetchRequest = NSFetchRequest<Nota>()
        fetchRequest.returnsObjectsAsFaults = false
        let entity = NSEntityDescription.entity(forEntityName: "Nota", in: self.persistentStack.managedObjectContext)
        fetchRequest.entity = entity
        let sortDescriptor1 = NSSortDescriptor(key: "timestamp", ascending: false)
        let sortDescriptors = [sortDescriptor1]
        fetchRequest.sortDescriptors = sortDescriptors

        do {
            self.notes = try self.persistentStack.managedObjectContext.fetch(fetchRequest)
            print(self.notes?.count)

        } catch {
            fatalError("Failed to fetch notes: \(error)")
        }
    }

    var body: some View {
        Text("Hello")
    }
}

But count keep returning 0. Any Idea?

Objective C to Swift with CoreData and Cloud Documents
 
 
Q