A synchronization framework for Core Data.

Last update: Jun 12, 2022

Core Data Ensembles

Author: Drew McCormack
Created: 29th September, 2013
Last Updated: 15th February, 2017

Ensembles 2 is now available for purchase at ensembles.io. It has performance improvements, extra backends (eg CloudKit, Dropbox Sync), and other features. This version of Ensembles continues to be maintained and supported.

There is a Google Group for discussing best practices with other developers.

Ensembles is an Objective-C framework — with Swift support — that extends Apple's Core Data framework to add peer-to-peer synchronization for Mac OS and iOS. Multiple SQLite persistent stores can be coupled together via a file synchronization platform like iCloud or Dropbox. The framework can be readily extended to support any service capable of moving files between devices, including custom servers.

Downloading Ensembles

To clone Ensembles to your local drive, use the command

git clone https://github.com/drewmccormack/ensembles.git

Ensembles makes use of Git submodules. To retrieve these, change to the ensembles root directory

cd ensembles

and issue this command

git submodule update --init

Incorporating Ensembles in an iOS Project

To add Ensembles to your App's Xcode Project with CocoaPods...

  1. Add the following to your Podfile

     platform :ios, '7.0'
     pod "Ensembles", "~> 1.0"
    

If you would like to use the Ensembles framework (module), try this...

  1. In Finder, drag the Ensembles iOS.xcodeproj project from the Framework directory into your Xcode project.
  2. Select your App's project root in the source list on the left, and then select the App's target.
  3. In the General tab, click the + button in the Embedded Binaries section.
  4. Choose Ensembles.framework in the iOS section. (Don't accidentally choose the Mac framework.)
  5. Import Ensembles in your source.

For Objective-C

    #import <Ensembles/Ensembles.h>

For Swift

    import Ensembles

To manually add the Ensembles static library instead of the module...

  1. In Finder, drag the Ensembles iOS.xcodeproj project from the Framework directory into your Xcode project.

  2. Select your App's project root in the source list on the left, and then select the App's target.

  3. In the General tab, click the + button in the Linked Frameworks and Libraries section.

  4. Choose the libensembles.a library and add it.

  5. Select the Build Settings tab. Locate the Other Linker Flags setting, and add the flag -ObjC.

  6. Select the Build Phases tab. Open Target Dependencies, and click the + button.

  7. Locate the Ensembles Resources iOS product, and add that as a dependency.

  8. Open the Ensembles iOS.xcodeproj project in the source list, and open the Products group.

  9. Drag the Ensembles.bundle product into the Copy Bundle Resources build phase of your app.

  10. Add the following import in your precompiled header file, or in any files using Ensembles.

    #import <Ensembles/Ensembles.h>
    

Incorporating Ensembles in an macOS Project

To add Ensembles to your App's Xcode Project with CocoaPods...

  1. Add the following to your Podfile

     platform :osx, '10.9'
     pod "Ensembles", "~> 1.0"
    

If you want to manually add Ensembles to your App's Xcode Project...

  1. In Finder, drag the Ensembles Mac.xcodeproj project from the Framework directory into your Xcode project.

  2. Select your App's project root in the source list on the left, and then select the App's target.

  3. In the General tab, click the + button in the Embedded Binaries section.

  4. Choose Ensembles.framework and add it.

  5. Add the following import in your precompiled header file, or in any files using Ensembles.

     #import <Ensembles/Ensembles.h>
    

For Swift use

    import Ensembles

Including Optional Cloud Services

By default, Ensembles only includes support for iCloud. To use other cloud services, such as Dropbox, you may need to add a few steps to the procedure above.

If you are using CocoaPods, add the optional subspec to the Podfile. For example, to include Dropbox, include

	pod "Ensembles/Dropbox", "~> 1.0"

If you are installing Ensembles manually, rather than with CocoaPods, you need to locate the source files and frameworks relevant to the service you want to support. You can find frameworks in the Vendor folder, and source files in Framework/Extensions.

By way of example, if you want to support Dropbox, you need to add the DropboxSDK Xcode project as a dependency, link to the appropriate product library, and include the files CDEDropboxCloudFileSystem.h and CDEDropboxCloudFileSystem.m in your project.

Idiomatic App

Idiomatic is a relatively simple example app which incorporates Ensembles and works with iCloud or Dropbox to sync across devices. The app allows you to record your ideas, include a photo, and add tags to group them. The Core Data model of the app has three entities, including a many-to-many relationship.

The Idiomatic project is a good way to get acquainted with Ensembles, and how it is integrated in a Core Data app. Idiomatic can be downloaded from the App Store if you want to see how it works. If you want to build and run it yourself, you need to follow a few preparatory steps.

  1. Select the Idiomatic Project in the source list of the Xcode project, and then select the Idiomatic target.
  2. Select the Capabilities section, turn on the iCloud switch.
  3. Build and install on devices and simulators that are logged into the same iCloud account.

Add notes, and tag them as desired. The app will sync when it becomes active, but you can force a sync by tapping the button under the Groups table.

Dropbox sync should work via The Mental Faculty account, but if you want to use your own developer account, you need to do the following:

  1. Sign up for a Dropbox developer account at developer.dropbox.com

  2. In the App Console, click the Create app button.

  3. Choose the Dropbox API app type.

  4. Choose to store 'Files and Datastores'

  5. Choose 'Yes — My app only needs access to files it creates'

  6. Name the app (eg Idiomatic)

  7. Click on Create app

  8. At the top of the IDMSyncManager class, locate this code, and replace the values with the strings you just created on the Dropbox site.

     NSString * const IDMDropboxAppKey = @"xxxxxxxxxxxxxxx";
     NSString * const IDMDropboxAppSecret = @"xxxxxxxxxxxxxxx";
    
  9. Select the Idiomatic project in Xcode, and then the Idiomatic iOS target.

  10. Select the Info tab.

  11. Open the URL Types section, and change the URL Schemes entry to

    db-<Your Dropbox App Key>
    

Idiomatic includes one more sync service: IdioSync. This is a custom service based on a Node.js server, and Amazon S3 storage. The source code for the server is provided to those purchasing a Priority Support Package at ensembles.io.

Getting to Know Ensembles

Before using Ensembles in any file, you should import the framework header, either in your precompiled header file, or in individual source code files.

#import <Ensembles/Ensembles.h>

The most important class in the Ensembles framework is CDEPersistentStoreEnsemble. You create one instance of this class for each NSPersistentStore that you want to sync. This class monitors saves to your SQLite store, and merges in changes from other devices as they arrive.

You typically initialize a CDEPersistentStoreEnsemble around the same point in your code that your Core Data stack is initialized. It is important that the ensemble is initialized before data is saved.

There is one other family of classes that you need to be familiar with. These are classes that conform to the CDECloudFileSystem protocol. Any class conforming to this protocol can serve as the file syncing backend of an ensemble, allowing data to be transferred between devices. You can use one of the existing classes (eg CDEICloudFileSystem), or develop your own.

The initialization of an ensemble is typically only a few lines long.

// Setup Ensemble
cloudFileSystem = [[CDEICloudFileSystem alloc] 
	initWithUbiquityContainerIdentifier:@"P7BXV6PHLD.com.mentalfaculty.idiomatic"];
ensemble = [[CDEPersistentStoreEnsemble alloc] initWithEnsembleIdentifier:@"MainStore" 
	persistentStoreURL:storeURL 
	managedObjectModelURL:modelURL
	cloudFileSystem:cloudFileSystem];
ensemble.delegate = self;

After the cloud file system is initialized, it is passed to the CDEPersistentStoreEnsemble initializer, together with the URL of a file containing the NSManagedObjectModel, and the path to the NSPersistentStore. An ensemble identifier is used to match stores across devices. It is important that this be the same for each store in the ensemble.

Once a CDEPersistentStoreEnsemble has been initialized, it can be leeched. This step typically only needs to take place once, to setup the ensemble and perform an initial import of data in the local persistent store. Once an ensemble has been leeched, it remains leeched even after a relaunch. The ensemble only gets deleeched if you explicitly request it, or if a serious problem arises in the cloud file system, such as an account switch.

You can query an ensemble for whether it is already leeched using the isLeeched property, and initiate the leeching process with leechPersistentStoreWithCompletion:. (Attempting to leech an ensemble that is already leeched will cause an error.)

if (!ensemble.isLeeched) {
    [ensemble leechPersistentStoreWithCompletion:^(NSError *error) {
        if (error) NSLog(@"Could not leech to ensemble: %@", error);
    }];
}

Because many tasks in Ensembles can involve networking or long operations, most methods are asynchronous and include a block callback which is called on completion of the task with an error parameter. If the error is nil, the task completed successfully. Methods should only be initiated on the main thread, and completion callbacks are sent to the main queue.

With the ensemble leeched, sync operations can be initiated using the mergeWithCompletion: method.

[ensemble mergeWithCompletion:^(NSError *error) {
    if (error) NSLog(@"Error merging: %@", error);
}];	

A merge involves retrieving new changes for other devices from the cloud file system, integrating them in a background NSManagedObjectContext, merging with new local changes, and saving the result to the NSPersistentStore.

When a merge occurs, it is important to merge the changes into your main NSManagedObjectContext. You can do this in the persistentStoreEnsemble:didSaveMergeChangesWithNotification: delegate method.

- (void)persistentStoreEnsemble:(CDEPersistentStoreEnsemble *)ensemble didSaveMergeChangesWithNotification:(NSNotification *)notification
{
    [managedObjectContext performBlock:^{
        [managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
    }];
}

Note that this is invoked on the thread of the background context used for merging the changes. You need to make sure the mergeChangesFromContextDidSaveNotification: method is invoked on the thread corresponding to the main context.

There is one other delegate method that you will probably want to implement, in order to provide global identifiers for managed objects.

- (NSArray *)persistentStoreEnsemble:(CDEPersistentStoreEnsemble *)ensemble 
	globalIdentifiersForManagedObjects:(NSArray *)objects
{
    return [objects valueForKeyPath:@"uniqueIdentifier"];
} 

This method is also invoked on a background thread. Care should be taken to only access the objects passed on this thread.

It is not compulsory to provide global identifiers, but if you do, the framework will automatically ensure that no objects get duplicated due to multiple imports on different devices. If you don't provide global identifiers, the framework has no way to identify a new object, and will assign it a new unique identifier.

If you do decide to provide global identifiers, it is up to you how you generate them, and where you store them. A common choice is to add an extra attribute to entities in your data model, and set that to a uuid on insertion into the store.

Troubleshooting

Ensembles has a built-in logging system, but by default only logs errors. It is often useful during development to see what the framework is doing, using the verbose logging setting. Simply make this call somewhere early in the launch process:

CDESetCurrentLoggingLevel(CDELoggingLevelVerbose);

Unit Tests

Unit tests are included for the Ensembles framework on each platform. To run the tests, open the Xcode workspace, choose the Ensembles Mac or Ensembles iOS target in the toolbar at the top, and select the menu item Product > Test.

GitHub

https://github.com/drewmccormack/ensembles
Comments
  • 1. Synchronization doesn't work on diferent devices

    I've installed the ensambles on my iOS project and every works fine, my main problem is that sync occurs only per device, if I install my application on other devices doesn't sync the data of the first device it creates its own store in the iCloud documents, and that happen for every device with the same app and same iCloud account.

    They didn't share the same iCloud db, every single device creates its own, and all devices has the same iCloud account.

    Reviewed by garanda21 at 2016-04-08 06:33
  • 2. CDEEEventIntegrator mergeEventsWithCompletion:_block_invoke.91 Causing App Crash

    My project was already up and running using Ensembles however once I migrated to Swift 3 my app now crashes. However the errors all point to the framework when debugging. I keep seeing the following error in the console when syncing;

    Could not load any Objective-C class information. This will significantly reduce the quality of type information available.

    or when I leave the app running I get the following memory issue

    Message from debugger: Terminated due to memory issue

    And when I dig further using instruments I found that the CPU over 100%, and memory and energy impact both high. When the app crashes It takes me to CDEEventIntegrator class, mergeEventsWithCompletion function and the dispatch line for `"integrate on background queue".

    Im unsure how to resolve this error, and I even turned iCloud drive for the app off in settings however the app still crashes sometime after launching. I have found with iCloud ManagedObjects would keep duplicating.

    Here's my Swift 3 functions in the appDelegate.

        // MARK: Ensembles
    
        var cloudFileSystem: CDECloudFileSystem!
        var ensemble: CDEPersistentStoreEnsemble!
    
        func syncWithCompletion(completion:@escaping (_ completed:Bool) -> Void) {
    
            if !ensemble.isLeeched {
                ensemble.leechPersistentStore { error in
                    if error != nil {
                        print("cannot leech \(error!.localizedDescription)")
                        completion(false)
                    }
                    else {
                        print("leached!!")
                        completion(true)
                    }
                }
            }
            else {
                ensemble.merge{ error in
                    if error != nil {
                        print("cannot merge \(error!.localizedDescription)")
                        completion(false)
                    }
                    else {
                        print("merged!!")
                        completion(true)
                        //NSNotificationCenter.defaultCenter().postNotificationName("Updated-DB", object: nil)
                    }
                }
            }
        }
    
        func persistentStoreEnsemble(_ ensemble: CDEPersistentStoreEnsemble, didSaveMergeChangesWith notification: Notification) {
            managedObjectContext.performAndWait {
                print("Database was updated from iCloud")
                self.managedObjectContext.mergeChanges(fromContextDidSave: notification as Notification)
                NotificationCenter.default.post(name: NSNotification.Name(rawValue: "Updated-DB"), object: nil)
            }
        }
    
        private func persistentStoreEnsemble(ensemble: CDEPersistentStoreEnsemble!, globalIdentifiersForManagedObjects objects: [AnyObject]!) -> [AnyObject]! {
            return (objects as NSArray).value(forKeyPath: "uniqueIdentifier") as! [AnyObject]
        }
    

    This is what I am seeing in the console

    screen shot 2016-09-12 at 17 16 51

    As mentioned the project was working in previous versions of Swift, but now that my functions have been migrated to Swift 3 I am getting the errors mentioned. Does anyone have any ideas why this might be happening ?

    PS: I was actually running final tests before submitting my app for iOS 10 and unfortunately this issue has prevented that. Weird how I haven't had any issues until today

    Reviewed by JDSX at 2016-09-12 17:11
  • 3. Multiple NSEntityDescriptions claim the NSManagedObject subclass ...

    Hi Drew,

    In Xcode 10 I'm getting this warning in the console when I save new objects to Core Data. (Context: I have an NSManagedObject subclass called CDEvent which I'm creating and saving in this case, and my Core Data stack lives in a shared framework called FeedingKit.)

    2018-07-26 16:34:30.268969-0400 Feeding[2936:3350281] [error] warning: Multiple NSEntityDescriptions claim the NSManagedObject subclass 'FeedingKit.CDEvent' so +entity is unable to disambiguate.
    CoreData: warning: Multiple NSEntityDescriptions claim the NSManagedObject subclass 'FeedingKit.CDEvent' so +entity is unable to disambiguate.
    2018-07-26 16:34:30.269522-0400 Feeding[2936:3350281] [error] warning:  	 'CDEvent' (0x7ffcb082c570) from NSManagedObjectModel (0x7ffcae58a4c0) claims 'FeedingKit.CDEvent'.
    CoreData: warning:  	 'CDEvent' (0x7ffcb082c570) from NSManagedObjectModel (0x7ffcae58a4c0) claims 'FeedingKit.CDEvent'.
    2018-07-26 16:34:30.269984-0400 Feeding[2936:3350281] [error] warning:  	 'CDEvent' (0x7ffcb0c09380) from NSManagedObjectModel (0x7ffcb0c06530) claims 'FeedingKit.CDEvent'.
    CoreData: warning:  	 'CDEvent' (0x7ffcb0c09380) from NSManagedObjectModel (0x7ffcb0c06530) claims 'FeedingKit.CDEvent'.
    

    I investigated a little bit using the memory graph debugger and noticed that there are three NSManagedObjectModels while my app is running. One of them comes from my own Core Data stack, and two of them come from Ensembles (CDEPersistentStoreEnsemble has a managedObjectModel, and CDEEventStore has a managedObjectContext that points to a persistentStoreEnsemble which points to a managedObjectModel). The two managed object models that the warnings reference correspond to the one from my own Core Data stack, and the CDEPersistentStoreEnsemble's directly-owned managedObjectModel.

    So far, I haven't noticed this warning actually causing any problems - my app and Ensembles work as desired. The warning is disconcerting, though, and I'm not sure if you're already aware of it and/or have an easy solution to fix or suppress it. Any ideas?

    Reviewed by UberJason at 2018-07-26 20:43
  • 4. Not leeching, but no error is thrown

    I have setup Ensembles according to the book and the MagicalRecord example. This is my code:

     //  determine if iCloud is configured
    id token = [[NSFileManager defaultManager] ubiquityIdentityToken];
    if (token == nil)
    {
    //        NSLog(@"iCloud is NOT available");
    
        //  set default NSManagedObjectContext for MagicalRecord
        [MagicalRecord   setupCoreDataStackWithAutoMigratingSqliteStoreNamed:@"saori.sqlite"];  //  enable Core    Data migration
        defaultContext = [NSManagedObjectContext MR_contextForCurrentThread]; 
    }
    else  {  //  no iCloud, no sync'ing
    
    //        NSLog(@"iCloud IS available");
    
        //  Ensemble: load the model
        NSManagedObjectModel *model = [NSManagedObjectModel MR_newManagedObjectModelNamed:@"salonbook.momd"];
        [NSManagedObjectModel MR_setDefaultManagedObjectModel:model];
    
        //  setup CoreData stack
        [MagicalRecord setShouldAutoCreateManagedObjectModel:NO];
        [MagicalRecord setupCoreDataStackWithAutoMigratingSqliteStoreNamed:@"saori.sqlite"];  //  enable Core Data migration
    
        //  setup Ensemble
        NSURL *modelURL = [[NSBundle mainBundle] URLForResource: @"SalonBook" withExtension:@"momd"];
        NSURL *storeURL = [NSPersistentStore MR_urlForStoreName: [MagicalRecord defaultStoreName]];
        cloudFileSystem = [[CDEICloudFileSystem alloc] initWithUbiquityContainerIdentifier:@"JRSD9A598D.com.pragerphoneapps.salonbook"];
        ensemble = [[CDEPersistentStoreEnsemble alloc] initWithEnsembleIdentifier:@"SalonBook"
                                                               persistentStoreURL: storeURL
                                                            managedObjectModelURL: modelURL
                                                                  cloudFileSystem: cloudFileSystem];
        ensemble.delegate = self;
    
        //  set default NSManagedObjectContext for MagicalRecord
        defaultContext = [NSManagedObjectContext MR_contextForCurrentThread];
    
        //  now leech it...
        if(!ensemble.isLeeched)  {
            [ensemble leechPersistentStoreWithCompletion: ^(NSError *error)  {
                if(error) NSLog(@"Could not leech to ensemble: %@", error);
            }];
        }
        else  {  //   merge it...
            [ensemble mergeWithCompletion:  ^(NSError *error)  {
                if(error) NSLog(@"Error during merge: %@", error);
            }];
        } 
    

    When I look at the ensemble symbols in the Debugger, after the above code is executed, this is what I see: screen shot 2014-06-19 at 9 06 07 am

    Any ideas why it's not working?

    Reviewed by Spokane-Dude at 2014-06-19 16:08
  • 5. Manually installation: libensembles_ios.a library missing

    Following the instructions for Manually installing in XCode, the library libensembles_ios.a is missing.

    In addition, during the build, I'm getting an error from this script:

    PhaseScriptExecution Run\ Script /Users/rm/Library/Developer/Xcode/DerivedData/Ensembles_iOS-fkcndmyqteddajevypymrtsohxfv/Build/Intermediates/Ensembles\ iOS.build/Debug-iphoneos/Documentation.build/Script-072C2F3618C9FBD900453E78.sh
    

    giving me the following build error:

    /Users/rm/Library/Developer/Xcode/DerivedData/Ensembles_iOS-fkcndmyqteddajevypymrtsohxfv/Build/Intermediates/Ensembles iOS.build/Debug-iphoneos/Documentation.build/Script-072C2F3618C9FBD900453E78.sh: line 2: /usr/local/bin/appledoc: No such file or directory
    
    Reviewed by Spokane-Dude at 2014-04-25 16:19
  • 6. Add WiFi/Multipeer cloud file system

    Should have a local cache on each device. Would locate another device using bonjour, and should go through a pairing procedure. Each device would then store a 4 digit number that is used to handshake.

    The sync procedure would simply involve each device sending the files that it currently has. The other device would then send back any new files it has. This sync would not necessarily have to happen at the same time as a ensembles merge. Usually, it would be wise to do the file sync first, and after that trigger a merge.

    Reviewed by drewmccormack at 2013-10-08 11:10
  • 7. Deleting objects in reparationContext

    Hi,

    I am using

    - (BOOL)persistentStoreEnsemble:(CDEPersistentStoreEnsemble *)ensemble shouldSaveMergedChangesInManagedObjectContext:(NSManagedObjectContext *)savingContext reparationManagedObjectContext:(NSManagedObjectContext *)reparationContext
    

    to de-duplicate and merge objects before they make it to Core Data store.

    Merge looks as following (sourceObject is a duplicate object, and mergedObject is the object that I will retain in database after de-duplication):

    I find all objects in database matching certain value, e.g. Category with name Groceries. Then I choose the object that I would like to keep in database and start merging other objects with it, after merge completed, I delete all duplicates.

    NSManagedObject *retainedObject = [self retainedObjectFromDuplicateObjects:similarObjects];
    
    NSParameterAssert(retainedObject);
    
    // remove retained object from duplicates
    NSMutableArray *duplicateObjects = [similarObjects mutableCopy];
    
    [duplicateObjects removeObject:retainedObject];
    
    [duplicateObjects enumerateObjectsUsingBlock:^(NSManagedObject *duplicateObject, __unused NSUInteger idx, __unused BOOL *stop) {
        [mergeResolver mergeObject:duplicateObject intoObject:retainedObject];
    }];
    
    [duplicateObjects MR_deleteEntitiesInContext:reparationContext];
    
    [entityDescription.attributesByName enumerateKeysAndObjectsUsingBlock:^(NSString *key, __unused NSAttributeDescription *attr, __unused BOOL *stop) {
        id oldValue = [mergedObject valueForKey:key];
        id newValue = [sourceObject valueForKey:key];
    
        // because CoreData produces change even when we set the same value
        // it's a good practice to at least check equality with isEqual:
        // before using setter
        if(![oldValue isEqual:newValue]) {
            [mergedObject setValue:newValue forKey:key];
        }
    }];
    
    [entityDescription.relationshipsByName enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSRelationshipDescription *relation, __unused BOOL *stop) {        
        if(relation.isToMany) { // to-many
            if(relation.isOrdered) {
                // ... not used
            } else {
                NSSet *oldRelatedObjects = [mergedObject valueForKey:key];
                NSSet *newRelatedObjects = [sourceObject valueForKey:key];
                NSMutableSet *combinedRelatedObjects = [[NSMutableSet alloc] init];
    
                [combinedRelatedObjects unionSet:oldRelatedObjects];
                [combinedRelatedObjects unionSet:newRelatedObjects];
    
                // reset relationship to avoid any delete rules to trigger
                [sourceObject setValue:[NSSet set] forKey:key];
    
                if(![oldRelatedObjects isEqualToSet:combinedRelatedObjects]) {
                    [mergedObject setValue:[combinedRelatedObjects copy] forKey:key];
                }
            }
        } else { // to-one
            NSManagedObject *oldRelatedObject = [mergedObject valueForKey:key];
            NSManagedObject *newRelatedObject = [sourceObject valueForKey:key];
    
            // reset relationship to avoid any delete rules to trigger
            [sourceObject setValue:nil forKey:key];
    
            if(oldRelatedObject != newRelatedObject) {
                [mergedObject setValue:newRelatedObject forKey:key];
            }
        }
    }];
    

    All of it happens in reparation context using performBlockAndWait.

    However what happens is that all relationships in duplicated objects are being deleted too (as a part CASCADE deletion rule I suppose), even though I copy all of relationships over to the single object (mergedObject) that I would like to keep in database after de-duplication and nullify the relationship on duplicates.

    If I do not remove duplicates then everything is fine and all relationships are properly reassigned to the single object, however I end up with a bunch of duplicates dangling around...

    I have a feeling that Ensembles picks up deletions but does not save changes and so instead all of objects go through revision as being deleted.

    Reviewed by pronebird at 2016-07-05 14:06
  • 8. Multipeer does not sync both ways...

    I've been playing around multipeer sync and it seems that if I create a CD record on remote device, local device is being notified about it via status message.

    However when status message arrives with the list of files on remote device, local device compares two sets of files, but it only checks for files missing on remote device, regardless whether remote device has more files / more changes.

    Therefore the change is being unnoticed because remote device has more files than local device and NSSet simply returns empty intersection against local files.

    This logic is located in handleFileRetrievalRequestFromPeerWithID of CDEMultipeerCloudFileSystem.m.

    It would be awesome to actually download new stuff from remote device and send CDEMultipeerCloudFileSystemDidImportFilesNotification to perform the merge once data is available.

    Reviewed by pronebird at 2016-07-04 00:31
  • 9. Crash: [NSNull globalIdentifier]

    Debugger stopped today in CDEEventBuilder:

    - (void)convertToOneRelationshipValuesToGlobalIdentifiersInPropertyChangeValue:(CDEPropertyChangeValue *)propertyChange withGlobalIdentifiersByObjectID:(NSDictionary *)globalIdentifiersByObjectID
    {
        CDEGlobalIdentifier *globalId = nil;
        globalId = globalIdentifiersByObjectID[propertyChange.relatedIdentifier];
        if (propertyChange.relatedIdentifier && !globalId) {
            CDELog(CDELoggingLevelError, @"No global id found for to-one relationship with target objectID: %@", propertyChange.relatedIdentifier);
        }
        propertyChange.relatedIdentifier = globalId.globalIdentifier; // <--- HERE
    }
    

    It looked like I've got an exception or something, or maybe Xcode is under weather. Says globalId is NSNull.

    Printing description of globalIdentifiersByObjectID:
    {
        "0xd0000000000c0020 <x-coredata://8AFDDF3F-D70F-4A99-A022-A522EF526ED9/EntityA/p3>" = "<null>";
        "0xd0000000001c0024 <x-coredata://8AFDDF3F-D70F-4A99-A022-A522EF526ED9/EntityB/p7>" = "<null>";
    }
    

    This is the code that I use to return global IDs:

    - (NSArray *)persistentStoreEnsemble:(CDEPersistentStoreEnsemble *)ensemble globalIdentifiersForManagedObjects:(NSArray *)objects {
        return [objects valueForKeyPath:@"uniqueIdentifier"];
    }
    

    and then app enters foreground and I get this:

    2016-03-08 12:30:24.127 App[2323:1141855] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSNull globalIdentifier]: unrecognized selector sent to instance 0x1a0d4cea8'
    *** First throw call stack:
    (0x182c65900 0x1822d3f80 0x182c6c61c 0x182c695b8 0x182b6d68c 0x100867b18 0x10086789c 0x1008676a4 0x100862b3c 0x1008893c8 0x1008629b8 0x184765080 0x184764f48 0x100862804 0x1008bf7a4 0x184765080 0x100c5dbb0 0x100c6a6c8 0x100c618a0 0x100c5dbb0 0x100c6ce10 0x100c6c4d8 0x1828cd470 0x1828cd020)
    libc++abi.dylib: terminating with uncaught exception of type NSException
    

    Each object I have in my Model implements "uniqueIdentifier" key. Any clues why that happens?`

    Reviewed by pronebird at 2016-03-08 12:14
  • 10. Linker error when using Ensembles/Dropbox in Swift project

    I try to use Ensembles with Dropbox on my Swift project, but got this error

    Undefined symbols for architecture x86_64:
      "_OBJC_CLASS_$_DBRestClient", referenced from:
          objc-class-ref in CDEDropboxCloudFileSystem.o
    ld: symbol(s) not found for architecture x86_64
    clang: error: linker command failed with exit code 1 (use -v to see invocation)
    

    I have created sample project to reproduce this error SwiftEnsemble.zip

    But any Podfile looking like this would cause the error

    # Uncomment this line to define a global platform for your project
    # platform :ios, '8.0'
    # Uncomment this line if you're using Swift
    use_frameworks!
    
    target 'SwiftEnsemble' do
      pod 'Ensembles', '~> 1.0'
      pod 'Ensembles/Dropbox', '~> 1.0'
    end
    
    Reviewed by sarunw at 2016-04-06 17:02
  • 11. Error in leech

    Hi Drew... First run, trying to get past the initial leech... I'm getting this error:

    Error in leech: Error Domain=CDEErrorDomain Code=1003 "User is not logged into iCloud." UserInfo={NSLocalizedDescription=User is not logged into iCloud.}

    • I am logged in to iCloud
    • running on the latest iOS (9.1), Objective-C and XCode 7.1 and the latest version of ensembles 1.0
    • This is the ubiquityContainerIdentifier from the debug run:
      ubiquityContainerIdentifier __NSCFConstantString * @"iCloud.com.pragerphoneapps.BookstoreInventoryManager" 0x00155768
    • no build errors (I have -lc and -ObjC parameters in my app, ensembles and pods targets)

    What do you think the problem is?

    Reviewed by Spokane-Dude at 2015-12-07 17:14
  • 12. [general] 'NSKeyedUnarchiveFromData' should not be used to for un-archiving and will be removed in a future release

    Xcode 12.3, built for iOS 12 target. I'm getting the warning message '[general] 'NSKeyedUnarchiveFromData' should not be used to for un-archiving and will be removed in a future release' - repeated in the console many many times.

    Tracking this down, this post: https://www.kairadiagne.com/2020/01/13/nssecurecoding-and-transformable-properties-in-core-data.html

    Looking into the ensembles model I've found this: image

    This would also explain the other warnings I'm getting at build time. CDEObjectChange.propertyChangeValues is using a nil or insecure value transformer. Please switch to NSSecureUnarchiveFromDataTransformerName or a custom NSValueTransformer subclass of NSSecureUnarchiveFromDataTransformer [2]

    ...... ensembles_master/Framework/Resources/CDEEventStoreModel.xcdatamodeld/CDEEventStoreModel_2.xcdatamodel: warning: Misconfigured Property: CDEObjectChange.propertyChangeValues is using a nil or insecure value transformer. Please switch to NSSecureUnarchiveFromDataTransformerName or a custom NSValueTransformer subclass of NSSecureUnarchiveFromDataTransformer

    Is the change as simple as adding 'NSSecureUnarchiveFromData' to the 'Transformer' field on the propertyChangeValues field of CDEObjectChange ? - Should the CDEEventStoreModel get a version bump? nope... I've tried that and have a crash on run... something does not confirm.... will work on tracking it down.

    Reviewed by dgwilson at 2021-01-05 20:16
  • 13. Build Warnings - iOS 12 and above

    Hi Drew...

    I have a bunch of build warnings with Ensembles for iOS. My Deployment target is 12.0. Perhaps it shouldn't be? Are these planned to be cleaned up? What's the best way to get these addressed? I can attempt to offer some assistance if that's the right thing to do?

    image
    Reviewed by dgwilson at 2020-12-29 03:22
  • 14. Crash in persistentStoreEnsemble: globalIdentifiersForManagedObjects: - uniqueIdentifier - entity is not KeyValueCoding compliant

    I have a macOS desktop app and an iOS app that I wish to sync the data between. They are apps that already existing their respective app stores. And I'm trying to switch to ensembles free.

    In the macOS app I'm getting this crash (ensembles free was downloaded about the beginning of June 2020). I think I've followed all of the instructions for integration... wouldn't surprise me if I missed something.

    The crash will happen on first run, not on the second. Then crash on the 3rd (same crash point/error) and not on the forth. Screenshot of the crash is below. Zombie objects is also enabled.

    Screen Shot 2020-06-13 at 11 27 34 AM

    Ultimately I'm not sure if I should load my existing data and migrate to a new store? I'm don't know what the correct approach is. First thing is to get past this crash though.

    • David
    Reviewed by dgwilson at 2020-06-12 23:31
  • 15. CloudKit Working? Does Anyone have actually have Subscription, etc. working?

    I have been trying to get CloudKit Subscriptions working, basically it doesn't do anything after you attempt to send the invitation. I came across this little note in CKSubscriptions Apple doc:

    Overview Note: CKQuerySubscription is not supported in a sharedCloudDatabase.

    Can anyone enlighten me and/or describe how to get the subscription workflow working? Or a least confirm that it does work?

    Many thanks!

    Reviewed by johnheb at 2020-01-03 13:24
  • 16. Removing from set doesn't remove from other devices, causes re-add

    I have a countdown app that allows you to put countdowns into different categories. The Category class has a to-many relationship with a Countdown, so a countdown can be in more than one category, or none.

    When I add a countdown to a category, it gets added to the category on other devices as well, just as I'd expect.

    However removing a countdown from a category doesn't work correctly. Removing a countdown from a category triggers a sync. On the other device, a sync is triggered, but the countdown is not removed from the category on the other device. Another sync then occurs on the other device, which then triggers a sync on the first device. The countdown gets re-added to the category on the first device.

    I'm not sure what I'm doing wrong or what's causing this.

    Videos of this happening: https://imgur.com/a/5759Ae6

    Reviewed by joshbirnholz at 2019-04-18 20:58
  • 17. iOS 12 users can no longer sync

    I didn't make any changes to the code that worked in iOS 11, but now I get the following errors when attempting to sync:

    [19293:5031258] [error] error: -addPersistentStoreWithType:Binary configuration:(null) URL:file:///var/mobile/Containers/Data/Application/3958D3AC-BED2-437B-AD3A-AB375BA4CF6B/Library/Application%20Support/com.mycompany.myapp/com.mentalfaculty.ensembles.eventdata/transitcache/MainStore/download/0_BF7C72BC-E135-4DA4-9DEE-858446A3D9A3-20796-00000B0B1A8C21B5_66DC6A8F.cdeevent options:{ NSInferMappingModelAutomaticallyOption = 1; NSMigratePersistentStoresAutomaticallyOption = 1; } ... returned error Error Domain=NSCocoaErrorDomain Code=259 "The file couldn’t be opened because it isn’t in the correct format." UserInfo={NSUnderlyingException=Can't read binary data from file, NSUnderlyingError=0x281c1be40 {Error Domain=NSCocoaErrorDomain Code=259 "The file “0_BF7C72BC-E135-4DA4-9DEE-858446A3D9A3-20796-00000B0B1A8C21B5_66DC6A8F.cdeevent” couldn’t be opened because it isn’t in the correct format." UserInfo={NSFilePath=/var/mobile/Containers/Data/Application/3958D3AC-BED2-437B-AD3A-AB375BA4CF6B/Library/Application Support/com.mycompany.myapp/com.mentalfaculty.ensembles.eventdata/transitcache/MainStore/download/0_BF7C72BC-E135-4DA4-9DEE-858446A3D9A3-20796-00000B0B1A8C21B5_66DC6A8F.cdeevent, NSUnderlyingException=value for key 'NS.objects' was of unexpected class 'CDEPropertyChangeValue'. Allowed classes are '{( NSDictionaryMapNode, NSOrderedSet, NSString, NSNumber, NSArray, NSDictionary, NSNull, NSDate, NSSet, NSData, NSUUID, NSDecimalNumber, NSURL )}'.}}} with userInfo dictionary { NSUnderlyingError = "Error Domain=NSCocoaErrorDomain Code=259 \"The file \U201c0_BF7C72BC-E135-4DA4-9DEE-858446A3D9A3-20796-00000B0B1A8C21B5_66DC6A8F.cdeevent\U201d couldn\U2019t be opened because it isn\U2019t in the correct format.\" UserInfo={NSFilePath=/var/mobile/Containers/Data/Application/3958D3AC-BED2-437B-AD3A-AB375BA4CF6B/Library/Application Support/com.mycompany.myapp/com.mentalfaculty.ensembles.eventdata/transitcache/MainStore/download/0_BF7C72BC-E135-4DA4-9DEE-858446A3D9A3-20796-00000B0B1A8C21B5_66DC6A8F.cdeevent, NSUnderlyingException=value for key 'NS.objects' was of unexpected class 'CDEPropertyChangeValue'. Allowed classes are '{(\n NSDictionaryMapNode,\n NSOrderedSet,\n NSString,\n NSNumber,\n NSArray,\n NSDictionary,\n NSNull,\n NSDate,\n NSSet,\n NSData,\n NSUUID,\n NSDecimalNumber,\n NSURL\n)}'.}"; NSUnderlyingException = "Can't read binary data from file"; }

    Reviewed by willocdev at 2018-10-20 19:31
Related tags
JSON to Core Data and back. Swift Core Data Sync.
JSON to Core Data and back. Swift Core Data Sync.

Notice: Sync was supported from it's creation back in 2014 until March 2021 Moving forward I won't be able to support this project since I'm no longer

Jun 8, 2022
Robust CloudKit synchronization: offline editing, relationships, shared and public databases, field-level deltas, and more.
Robust CloudKit synchronization: offline editing, relationships, shared and public databases, field-level deltas, and more.

CloudCore CloudCore is a framework that manages syncing between iCloud (CloudKit) and Core Data written on native Swift. Features Leveraging NSPersist

Jun 23, 2022
A powerful and elegant Core Data framework for Swift.
A powerful and elegant Core Data framework for Swift.

A powerful and elegant Core Data framework for Swift. Usage Beta version. New docs soon... Simple do that: let query = persistentContainer.viewContext

May 23, 2022
CloudCore is a framework that manages syncing between iCloud (CloudKit) and Core Data written on native Swift.
CloudCore is a framework that manages syncing between iCloud (CloudKit) and Core Data written on native Swift.

CloudCore CloudCore is a framework that manages syncing between iCloud (CloudKit) and Core Data written on native Swift. Features Leveraging NSPersist

Jun 23, 2022
Super awesome Swift minion for Core Data (iOS, macOS, tvOS)

⚠️ Since this repository is going to be archived soon, I suggest migrating to NSPersistentContainer instead (available since iOS 10). For other conven

Jan 29, 2022
Unleashing the real power of Core Data with the elegance and safety of Swift
 Unleashing the real power of Core Data with the elegance and safety of Swift

Unleashing the real power of Core Data with the elegance and safety of Swift Dependency managers Contact Swift 5.4: iOS 11+ / macOS 10.13+ / watchOS 4

Jun 25, 2022
JSQCoreDataKit - A swifter Core Data stack

JSQCoreDataKit A swifter Core Data stack About This library aims to do the following: Encode Core Data best practices, so you don't have to think "is

Jun 17, 2022
JustPersist is the easiest and safest way to do persistence on iOS with Core Data support out of the box.
JustPersist is the easiest and safest way to do persistence on iOS with Core Data support out of the box.

JustPersist JustPersist is the easiest and safest way to do persistence on iOS with Core Data support out of the box. It also allows you to migrate to

Mar 13, 2022
QueryKit, a simple type-safe Core Data query language.
QueryKit, a simple type-safe Core Data query language.

QueryKit QueryKit, a simple type-safe Core Data query language. Usage QuerySet<Person>(context, "Person")

Jun 16, 2022
A minimalistic, thread safe, non-boilerplate and super easy to use version of Active Record on Core Data.
A minimalistic, thread safe, non-boilerplate and super easy to use version of Active Record on Core Data.

Skopelos A minimalistic, thread-safe, non-boilerplate and super easy to use version of Active Record on Core Data. Simply all you need for doing Core

May 11, 2022
HitList is a Swift App shows the implementation of Core Data.
HitList is a Swift App shows the implementation of Core Data.

HitList HitList is a Swift App shows the implementation of Core Data. It is the demo app of Ray Wenderlich's tech blog. For details please reference G

Dec 17, 2021
Core Data code generation

mogenerator Visit the project's pretty homepage. Here's mogenerator's elevator pitch: mogenerator is a command-line tool that, given an .xcdatamodel f

Jun 21, 2022
Super Awesome Easy Fetching for Core Data!

MagicalRecord In software engineering, the active record pattern is a design pattern found in software that stores its data in relational databases. I

Jun 21, 2022
A type-safe, fluent Swift library for working with Core Data
A type-safe, fluent Swift library for working with Core Data

Core Data Query Interface (CDQI) is a type-safe, fluent, intuitive library for working with Core Data in Swift. CDQI tremendously reduces the amount o

Jun 27, 2020
A feature-light wrapper around Core Data that simplifies common database operations.
A feature-light wrapper around Core Data that simplifies common database operations.

Introduction Core Data Dandy is a feature-light wrapper around Core Data that simplifies common database operations. Feature summary Initializes and m

May 11, 2022
The Big Nerd Ranch Core Data Stack
The Big Nerd Ranch Core Data Stack

BNR Core Data Stack The BNR Core Data Stack is a small Swift framework that makes it both easier and safer to use Core Data. A better fetched results

Jun 6, 2022
100% Swift Simple Boilerplate Free Core Data Stack. NSPersistentContainer
100% Swift Simple Boilerplate Free Core Data Stack. NSPersistentContainer

DATAStack helps you to alleviate the Core Data boilerplate. Now you can go to your AppDelegate remove all the Core Data related code and replace it wi

Jun 7, 2022
Example repo of working with Core Data in a SwiftUI application

CoreData in SwiftUI This repository serves many purpose. But mostly so I can experiment with Core Data in SwiftUI, and also so I can use it show my ap

Jan 30, 2022
This project server as a demo for anyone who wishes to learn Core Data in Swift.

CoreDataDemo This project server as a demo for anyone who wishes to learn Core Data in Swift. The purpose of this project is to help someone new to Co

May 3, 2022