web-dev-qa-db-fra.com

Configuration des données de base Xcode 6 iOS 8 iCloud

Quelqu'un a-t-il une configuration de synchronisation des données de base iCloud sur Xcode 6 et iOS 8? (j'espère que ce n'est pas un article en double)

Où est passée l'option de stockage iCloud Core Data?

Je me souviens que Core Data avait une option de stockage supplémentaire appelée stockage de données de base, mais maintenant dans Xcode 6, il ne semble afficher la valeur-clé et le stockage de documents que lorsque j'active la bascule iCloud dans Xcode 6.

Informations de fond

  • Nouvelle application iPad
  • Xcode 6
  • Cibler la version minimale d'iOS 7 mais en espérant que cela fonctionne aussi pour iOS 8? (Nous pouvons définir iOS 8 au minimum)
  • Vous souhaitez utiliser le stockage iCloud Core Data au lieu du stockage de valeurs-clés ou de documents.
  • Vous êtes connecté au même compte Apple dans Paramètres> iCloud pour le simulateur et l'appareil iPad
  • Mon profil d'approvisionnement utilisé pour coder signe que l'application a activé iCloud pour le développement et la distribution (a été automatiquement activé par Xcode)

Ma configuration

Jusqu'à présent, je ne sais pas si j'ai correctement configuré Core Data iCloud.

Xcode semble avoir configuré les conteneurs iCloud dans le portail des développeurs iOS:

iCloud.com.xxxxxx.xxxxxxxx   (note: I've replaced the actual strings with xxxx here)

Ma liste de "services" Xcode 6 iCloud ne montre aucune coche à côté de:

  • Stockage de valeurs-clés
  • documents iCloud
  • CloudKit

Lequel devrions-nous utiliser maintenant car il ne répertorie pas les "données de base" comme option de stockage?

Dans les "Conteneurs" directement sous les "services", il affiche les options suivantes grisées:

  • Utiliser le conteneur par défaut (celui-ci coché par défaut)
  • Spécifiez des conteneurs personnalisés
  • iCloud.com.xxxxxxxxxx.xxxxxxxxx (encore une fois, substitué les vrais identifiants par xxxx)

Je ne peux choisir aucune option, cela semble me forcer à "Utiliser le conteneur par défaut".

Enfin, Xcode semble montrer des graduations pour:

  • Ajoutez le droit "iCloud" à votre ID d'application
  • Ajoutez le droit "Conteneurs iCloud" à votre ID d'application
  • Ajoutez les droits "iCloud" à votre fichier de droits
  • Lien CloudKit.framework

Donc, par le processus automatisé de Xcode, il a tout configuré pour moi.

Le code de référence

OK, j'ai donc lu et remarqué une pile iCloud écrite ici:

https://github.com/mluisbrown/iCloudCoreDataStack

J'ai pris le code nécessaire et essayé de m'adapter à mon singleton Core Data Manager:

Fichier DataManager.h

+ (id)sharedModel;
+ (ALAssetsLibrary *)sharedLibrary;

@property (nonatomic, readonly) NSManagedObjectContext *mainContext;
@property (nonatomic, readonly) NSPersistentStoreCoordinator *storeCoordinator;

- (NSString *)modelName;
- (NSString *)pathToModel;
- (NSString *)storeFilename;
- (NSString *)pathToLocalStore;


#pragma mark - Entity Fetching Methods -

-(NSArray *)fetchEntityOfType:(NSString *)entityType UsingPredicated:(NSPredicate *)predicate sortBy:(NSString *)sortKey ascendingOrder:(BOOL)ascendingOrder;

Fichier DataManager.m

@property (nonatomic, strong) NSManagedObjectModel *managedObjectModel;
- (NSString *)documentsDirectory;

@end

@implementation MLSAlbumsDataModel
@synthesize managedObjectModel = _managedObjectModel;
@synthesize storeCoordinator = _storeCoordinator;
@synthesize mainContext = _mainContext;

+ (id)sharedModel {
    static MLSAlbumsDataModel *__instance = nil;
    if (__instance == nil) {
        __instance = [[MLSAlbumsDataModel alloc] init];
    }
    return __instance;
}

+ (ALAssetsLibrary *)sharedLibrary {
    static ALAssetsLibrary *__instance = nil;
    if (__instance == nil) {
        __instance = [[ALAssetsLibrary alloc] init];
    }
    return __instance;

}

- (NSString *)modelName {
    return @"Albums";
}

- (NSString *)pathToModel {
    return [[NSBundle mainBundle] pathForResource:[self modelName] ofType:@"momd"];
}

- (NSString *)storeFilename {
    return [[self modelName] stringByAppendingPathExtension:@"sqlite"];
}

- (NSString *)pathToLocalStore {
    return [[self documentsDirectory] stringByAppendingPathComponent:[self storeFilename]];
}

- (NSString *)documentsDirectory {
    NSString *documentsDirectory = nil;
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    documentsDirectory = [paths objectAtIndex:0];
    return documentsDirectory;
}

- (NSManagedObjectContext *)mainContext {
    if(_mainContext == nil) {
        _mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        _mainContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;        

        // setup persistent store coordinator

        DLog(@"SQLITE STORE PATH: %@", [self pathToLocalStore]);
        NSURL *storeURL = [NSURL fileURLWithPath:[self pathToLocalStore]];


        //_mainContext.persistentStoreCoordinator = [self storeCoordinator];

        _mainContext.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];



        __weak NSPersistentStoreCoordinator *psc = self.mainContext.persistentStoreCoordinator;

        // iCloud notification subscriptions
        NSNotificationCenter *dc = [NSNotificationCenter defaultCenter];
        [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];

        NSError* error;
        // the only difference in this call that makes the store an iCloud enabled store
        // is the NSPersistentStoreUbiquitousContentNameKey in options. I use "iCloudStore"
        // but you can use what you like. For a non-iCloud enabled store, I pass "nil" for options.

        // Note that the store URL is the same regardless of whether you're using iCloud or not.
        // If you create a non-iCloud enabled store, it will be created in the App's Documents directory.
        // An iCloud enabled store will be created below a directory called CoreDataUbiquitySupport
        // in your App's Documents directory
        [self.mainContext.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                                           configuration:nil
                                                                            URL:storeURL
                                                                        options:@{ NSPersistentStoreUbiquitousContentNameKey : @"iCloudStore" }
                                                                          error:&error];
        if (error) {
            NSLog(@"error: %@", error);
        }

        _storeCoordinator = self.mainContext.persistentStoreCoordinator;

    }
    return _mainContext;
}

- (NSManagedObjectModel *)managedObjectModel {
    if(_managedObjectModel == nil) {
        NSURL *storeURL = [NSURL fileURLWithPath:[self pathToModel]];
        _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:storeURL];
    }
    return _managedObjectModel;
}

- (NSPersistentStoreCoordinator *)storeCoordinator {
    if (_storeCoordinator == nil) {
        // -----------------------------------------------------------------------------------------------------------------------------
        // Code moved to managed object context code above
        // -----------------------------------------------------------------------------------------------------------------------------
        /*

        DLog(@"SQLITE STORE PATH: %@", [self pathToLocalStore]);
        NSURL *storeURL = [NSURL fileURLWithPath:[self pathToLocalStore]];

        NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];

        NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                                 [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                                 [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
        NSError *error = nil;




        if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
            NSDictionary *userInfo = [NSDictionary dictionaryWithObject:error forKey:NSUnderlyingErrorKey];
            NSString *reason = @"Could not create persistent store";
            NSException *exc = [NSException exceptionWithName:NSInternalInconsistencyException reason:reason userInfo:userInfo];
            @throw exc;
        }

        _storeCoordinator = psc;

         */

    }
    return _storeCoordinator;
}


#pragma mark - iCloud Related Methods -

// Subscribe to NSPersistentStoreDidImportUbiquitousContentChangesNotification
- (void)persistentStoreDidImportUbiquitousContentChanges:(NSNotification*)note
{
    NSLog(@"%s", __PRETTY_FUNCTION__);
    NSLog(@"%@", note.userInfo.description);

    NSManagedObjectContext *moc = self.mainContext;
    [moc performBlock:^{
        [moc mergeChangesFromContextDidSaveNotification:note];

        DLog(@"NSPersistentStoreDidImportUbiquitousContentChangesNotification executed");
        /*

        // you may want to post a notification here so that which ever part of your app
        // needs to can react appropriately to what was merged.
        // An exmaple of how to iterate over what was merged follows, although I wouldn't
        // recommend doing it here. Better handle it in a delegate or use notifications.
        // Note that the notification contains NSManagedObjectIDs
        // and not NSManagedObjects.
        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) {
            // do whatever you need to with the NSManagedObjectID
            // you can retrieve the object from with [moc objectWithID:objID]
        }

        */

    }];
}

// Subscribe to NSPersistentStoreCoordinatorStoresWillChangeNotification
// most likely to be called if the user enables / disables iCloud
// (either globally, or just for your app) or if the user changes
// iCloud accounts.
- (void)storesWillChange:(NSNotification *)note {
    NSManagedObjectContext *moc = self.mainContext;
    [moc performBlockAndWait:^{
        NSError *error = nil;
        if ([moc hasChanges]) {
            [moc save:&error];
        }

        [moc reset];
    }];

    // now reset your UI to be prepared for a totally different
    // set of data (eg, popToRootViewControllerAnimated:)
    // but don't load any new data yet.

    [[NSNotificationCenter defaultCenter] postNotificationName:@"notifCoreDataStoreWillChange" object:nil];

    DLog(@"storeWillChange notification fire");
}

// Subscribe to NSPersistentStoreCoordinatorStoresDidChangeNotification
- (void)storesDidChange:(NSNotification *)note
{
    // here is when you can refresh your UI and
    // load new data from the new store


    [[NSNotificationCenter defaultCenter] postNotificationName:@"notifCoreDataStoreDidChange" object:nil];

    DLog(@"storeDidChange notification fire");
}



#pragma mark - Entity Fetching Methods -

-(NSArray *)fetchEntityOfType:(NSString *)entityType UsingPredicated:(NSPredicate *)predicate sortBy:(NSString *)sortKey ascendingOrder:(BOOL)ascendingOrder
{
    NSEntityDescription *entityDescription = [NSEntityDescription entityForName:entityType inManagedObjectContext:[[MLSAlbumsDataModel sharedModel] mainContext]];


    NSSortDescriptor *sortDescriptor = nil;

    if(sortKey)
    {
        sortDescriptor = [[NSSortDescriptor alloc] initWithKey:sortKey ascending:ascendingOrder];
    }
    else
    {
        sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"updatedAt" ascending:ascendingOrder];
    }


    NSFetchRequest *request = [[NSFetchRequest alloc] init];

    request.entity = entityDescription;

    if(predicate)
    {
        request.predicate = predicate;
    }

    request.sortDescriptors = @[sortDescriptor];

    NSError *error = nil;

    NSArray *results = [[[MLSAlbumsDataModel sharedModel] mainContext] executeFetchRequest:request error:&error];

    if(results == nil)
    {
        DLog(@"Error getting entity of type '%@' using predicate '%@', sortKey '%@' ascendingOrder %d", entityType, predicate, sortKey, ascendingOrder);
    }

    return results;
}

Mes observations

J'ai essayé d'exécuter l'application sur le simulateur iPad (je pense que c'est le simulateur iOS 8) et sur un appareil iPad exécutant iOS 7.x

J'ai créé un album avec un nom entré par l'utilisateur sur le simulateur, mais je ne vois pas l'appareil iPad afficher l'album nouvellement créé. J'ai également essayé d'inverser les rôles, de créer un appareil iPad, de simuler le résultat iOS non plus.

Je vois mes messages de journal:

storeDidChange notification fire

SQLITE STORE PATH: /Users/xxxxxxx/Library/Developer/CoreSimulator/Devices/3DC17576-92E9-4EAF-B77A-41340AE28F92/data/Containers/Data/Application/E51085CE-3772-4DF1-A503-1C243497091A/Documents/Albums.sqlite

Si je minimise l'application dans le simulateur et l'ouvre à nouveau (sans appuyer sur le bouton Stop dans Xcode), je vois ces messages:

-[PFUbiquitySwitchboardEntryMetadata setUseLocalStorage:](808): CoreData: Ubiquity:  nobody~sim301AE3E8-16B2-5A08-917D-7B55D1879BE4:iCloudStore
Using local storage: 1

J'ai lu que "Utiliser le stockage local: 0" est-ce que cela devrait être idéalement? et que 1 signifie magasin de données d'appareil local plutôt que magasin de données iCloud.

Lorsque je crée un album, l'enregistre, arrête le simulateur, puis redémarre l'application, mes albums disparaissent, mais immédiatement après avoir créé un nouvel album, tout l'album précédent réapparaît comme par magie. C'est un peu bizarre. Si je n'utilise pas iCloud et que je rétablis mon code dans la configuration précédente, je peux créer et voir correctement mon album, que je minimise ou non mon application, ou que je redémarre, mais je n'ai pas la synchronisation iCloud dont j'ai besoin .

Ai-je fait des erreurs quelque part?

Désolé pour le long post, mais est-ce que quelqu'un a fait fonctionner iCloud pour iOS 8 et Xcode 6?

J'ai vraiment besoin d'aide.

Questions supplémentaires

1) iOS 8 nécessite-t-il l'utilisation de cet identifiant de conteneur? (que Xcode 6 a généré pour moi):

com.Apple.developer.icloud-container-identifiers

Ce n'est pas ce à quoi ressemble l'iOS 7, n'est-ce pas? iOS 7 one ressemble plus à:

com.Apple.developer.ubiquity-container-identifiers

2) Ai-je besoin d'un compte iCloud Drive avant de fonctionner?

Super confus @ _ @

42
Zhang

solution iOS 8:

OK ... alors .... lol. Je pense que je l'ai résolu.

Nouvelle découverte. Après avoir parcouru cette page:

http://www.tuaw.com/2014/09/17/psa-do-not-upgrade-to-icloud-drive-during-ios-8-installation/

Ça dit:

iCloud Drive est la nouvelle fonctionnalité améliorée de synchronisation et de stockage de fichiers iCloud d'Apple qui vous permet de partager des documents entre vos appareils iOS 8 et votre Mac exécutant OS X 10 Yosemite.

J'ai donc décidé de mordre la balle et de mettre à niveau mon compte iCloud vers le lecteur iCloud (gratuit pour la mise à niveau).

Après la mise à niveau vers le lecteur iCloud et la relance de mon application avec quelques modifications Xcode 6, cela fonctionne maintenant.

Quelques points importants à noter:

  • iCloud Drive est incompatible avec le stockage de données de document iCloud précédent. Donc, si vous allez tester, assurez-vous que tous vos appareils utilisent le lecteur iCloud et iOS 8.
  • Le simulateur ne semble se synchroniser qu'une seule fois, après avoir lancé l'application tandis que l'appareil synchronise en continu chaque intervalle. Je ne sais pas si c'est un bug du simulateur ou non. Ou peut-être que ma configuration n'est pas parfaite.
  • L'utilisation de "Utiliser les conteneurs par défaut" ne fonctionne pas pour moi dans le simulateur (mais sur l'appareil, cela fonctionne) du premier coup, il peut être nécessaire de supprimer la copie précédente de l'application et de la réinstaller. Essayez d'abord d'utiliser des conteneurs par défaut et voyez si cela fonctionne, sinon lisez le point suivant ci-dessous.
  • Pour la raison ci-dessus, j'ai changé pour utiliser un conteneur Ubiquity avec ce modèle:

    iCloud. $ (CFBundleIdentifier)

Donc quelque chose comme:

iCloud.com.xxxxxxxx.iCloudCoreDataDemo

Où "xxxxxxxx" est l'identifiant du nom de mon entreprise.

J'ai créé le conteneur iCloud ci-dessus en me connectant à mon iOS Developer Center, peut-être pourriez-vous simplement appuyer sur le signe "+" à l'intérieur de Xcode 6 et en entrer un là-bas, Xcode devrait tout configurer automatiquement pour vous.

Un bloc de code que j'ai utilisé pour tester pour voir si cela fonctionne est le suivant:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

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

    NSURL *containerURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:@"iCloud.com.xxxxxxxxxx.iCloudCoreDataDemo"];

    if(containerURL == nil)
    {
        NSLog(@"containerURL == nil");
    }
    else
    {
        NSLog(@"hurray?");
    }

    return YES;
}

Si vous voyez "hourra?" alors ça va, vous devriez également voir ce modèle de texte dans la sortie de votre console Xcode:

2014-10-07 17:37:23.196 iCloudCoreDataDemo[8104:130250] documentsDirectory = file:///Users/xxxxxxxx/Library/Developer/CoreSimulator/Devices/9FAFE881-13CA-4608-8BE6-728C793FAFFB/data/Containers/Data/Application/BC6CA07D-605A-4927-94AF-E9E21E204D2B/Documents/
2014-10-07 17:37:23.386 iCloudCoreDataDemo[8104:130250] storeDidChange
2014-10-07 17:37:23.390 iCloudCoreDataDemo[8104:130250] -[PFUbiquitySwitchboardEntryMetadata setUseLocalStorage:](808): CoreData: Ubiquity:  nobody~sim301AE3E8-16B2-5A08-917D-7B55D1879BE4:iCloudStore
Using local storage: 1
2014-10-07 17:37:23.402 iCloudCoreDataDemo[8104:130250] hurray?
2014-10-07 17:37:33.909 iCloudCoreDataDemo[8104:130250] storeWillChange
2014-10-07 17:37:33.933 iCloudCoreDataDemo[8104:130250] storeDidChange
2014-10-07 17:37:33.933 iCloudCoreDataDemo[8104:130330] -[PFUbiquitySwitchboardEntryMetadata setUseLocalStorage:](808): CoreData: Ubiquity:  nobody~sim301AE3E8-16B2-5A08-917D-7B55D1879BE4:iCloudStore
Using local storage: 0

Remarquez les deux lignes importantes:

Using local storage: 1

devient plus tard:

Using local storage: 0

Le stockage local 1 signifie qu'il utilise actuellement le stockage local, tandis que le stockage local 0 signifie qu'il a déplacé les données vers le stockage iCloud.

J'espère que cela profite à tout le monde.

solution iOS 7 uniquement:

OK, je viens de découvrir quelque chose et j'ai réussi à le faire fonctionner pour iOS 7 uniquement. Je n'ai toujours pas compris comment le faire dans iOS 8, mais j'ai remarqué quelque chose d'important.

Sur mon iPhone 5 exécutant iOS 8.0.2, je n'ai plus l'option " Données du document " dans le menu des paramètres iCloud.

Cependant, sur mon iPad exécutant iOS 7, je vois les options "Données du document".

C'est peut-être la raison pour laquelle cela ne fonctionne pas sur iOS 8, nous n'avons plus de stockage de données de document?

Quoi qu'il en soit, voici ce que j'ai découvert pour la seule solution iOS 7.

J'ai trouvé cette page ici

https://developer.Apple.com/library/ios/documentation/General/Conceptual/iCloudDesignGuide/Chapters/iCloudFundametals.html

et l'une des lignes dit:

  • Le stockage de documents iCloud est destiné au contenu basé sur des fichiers visible par l'utilisateur, Stockage de données de base , ou pour tout autre contenu complexe basé sur des fichiers.

Effectivement, je suis allé dans mon fichier de projet Xcode 6 et j'ai coché l'option "Documents iCloud". Cela a grisé les boutons radio, mais je l'ai quand même laissé à "Utiliser les conteneurs par défaut".

Une chose que j'ai apprise, c'est que j'ai besoin d'initier mon PersistentStack dans l'appDelegate. Auparavant, j'essayais d'initier la pile persistante à l'intérieur de la méthode + (id) sharedInstance, mais cela empêchait iCloud de se synchroniser uniquement la première fois, donc après le chargement et la synchronisation initiaux, l'ajout d'un nouvel enregistrement n'est pas synchronisé par la suite.

J'ai réécrit une application de base et modifié légèrement la pile persistante:

App Delegate.h

#import <UIKit/UIKit.h>
#import "PersistentStack.h"

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@property (nonatomic, strong) NSManagedObjectContext* managedObjectContext;
@property (nonatomic, strong) PersistentStack* persistentStack;


@end

App Delegate.m

@interface AppDelegate ()

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

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

    return YES;
}

...

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

- (NSURL*)modelURL
{
    return [[NSBundle mainBundle] URLForResource:@"MyApp" withExtension:@"momd"];
}

Persistent Stack.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

#import "Book.h"
#import <UIKit/UIKit.h>

@interface PersistentStack : NSObject

+(id)sharedInstance;

- (id)initWithStoreURL:(NSURL *)storeURL modelURL:(NSURL *)modelURL;

@property (nonatomic,strong,readonly) NSManagedObjectContext *managedObjectContext;

#pragma mark - Regular Methods -

-(Book *)insertNewBookWithDate:(NSDate *)newDate;
-(void)deleteBook:(Book *)book;
-(NSArray *)fetchEntityOfType:(NSString *)entityType withPredicate:(NSPredicate *)predicate andSortKey:(NSString *)sortKey;

@end

Pile persistante.m

#import "PersistentStack.h"
#import "AppDelegate.h"

@interface PersistentStack ()

@property (nonatomic,strong,readwrite) NSManagedObjectContext* managedObjectContext;
@property (nonatomic,strong) NSURL* modelURL;
@property (nonatomic,strong) NSURL* storeURL;

@end

@implementation PersistentStack

+(id)sharedInstance
{
    static PersistentStack *sharedInstance = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;

        sharedInstance = appDelegate.persistentStack;
    });

    return sharedInstance;
}

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

- (void)setupManagedObjectContext
{
    self.managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    self.managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
    self.managedObjectContext.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];


    //__weak NSPersistentStoreCoordinator *psc = self.managedObjectContext.persistentStoreCoordinator;

    // iCloud notification subscriptions
    NSNotificationCenter *dc = [NSNotificationCenter defaultCenter];
    [dc addObserver:self
           selector:@selector(storesWillChange:)
               name:NSPersistentStoreCoordinatorStoresWillChangeNotification
             object:self.managedObjectContext.persistentStoreCoordinator];

    [dc addObserver:self
           selector:@selector(storesDidChange:)
               name:NSPersistentStoreCoordinatorStoresDidChangeNotification
             object:self.managedObjectContext.persistentStoreCoordinator];

    [dc addObserver:self
           selector:@selector(persistentStoreDidImportUbiquitousContentChanges:)
               name:NSPersistentStoreDidImportUbiquitousContentChangesNotification
             object:self.managedObjectContext.persistentStoreCoordinator];

    NSError* error;
    // the only difference in this call that makes the store an iCloud enabled store
    // is the NSPersistentStoreUbiquitousContentNameKey in options. I use "iCloudStore"
    // but you can use what you like. For a non-iCloud enabled store, I pass "nil" for options.

    // Note that the store URL is the same regardless of whether you're using iCloud or not.
    // If you create a non-iCloud enabled store, it will be created in the App's Documents directory.
    // An iCloud enabled store will be created below a directory called CoreDataUbiquitySupport
    // in your App's Documents directory
    [self.managedObjectContext.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                                       configuration:nil
                                                                                 URL:self.storeURL
                                                                             options:@{ NSPersistentStoreUbiquitousContentNameKey : @"iCloudStore" }
                                                                               error:&error];
    if (error) {
        NSLog(@"error: %@", error);
    }
}

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

// Subscribe to NSPersistentStoreDidImportUbiquitousContentChangesNotification
- (void)persistentStoreDidImportUbiquitousContentChanges:(NSNotification*)note
{
    NSLog(@"%s", __PRETTY_FUNCTION__);
    NSLog(@"%@", note.userInfo.description);

    NSManagedObjectContext *moc = self.managedObjectContext;
    [moc performBlock:^{
        [moc mergeChangesFromContextDidSaveNotification:note];

        [[NSNotificationCenter defaultCenter] postNotificationName:@"notifiCloudStoreDidChange" object:nil];

        /*
        // you may want to post a notification here so that which ever part of your app
        // needs to can react appropriately to what was merged.
        // An exmaple of how to iterate over what was merged follows, although I wouldn't
        // recommend doing it here. Better handle it in a delegate or use notifications.
        // Note that the notification contains NSManagedObjectIDs
        // and not NSManagedObjects.
        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) {
            // do whatever you need to with the NSManagedObjectID
            // you can retrieve the object from with [moc objectWithID:objID]
        }
         */

    }];
}

// Subscribe to NSPersistentStoreCoordinatorStoresWillChangeNotification
// most likely to be called if the user enables / disables iCloud
// (either globally, or just for your app) or if the user changes
// iCloud accounts.
- (void)storesWillChange:(NSNotification *)note {

    NSLog(@"storeWillChange");

    NSManagedObjectContext *moc = self.managedObjectContext;

    //[moc performBlockAndWait:^{
    [moc performBlock:^{
        NSError *error = nil;
        if ([moc hasChanges]) {
            [moc save:&error];
        }

        [moc reset];
    }];

    // now reset your UI to be prepared for a totally different
    // set of data (eg, popToRootViewControllerAnimated:)
    // but don't load any new data yet.
}

// Subscribe to NSPersistentStoreCoordinatorStoresDidChangeNotification
- (void)storesDidChange:(NSNotification *)note {
    // here is when you can refresh your UI and
    // load new data from the new store

    NSLog(@"storeDidChange");

    [[NSNotificationCenter defaultCenter] postNotificationName:@"notifiCloudStoreDidChange" object:nil];
}

#pragma mark - Regular Methods -

-(Book *)insertNewBookWithDate:(NSDate *)newDate
{
    Book *newBook = [NSEntityDescription insertNewObjectForEntityForName:@"Book" inManagedObjectContext:self.managedObjectContext];

    newBook.bookName = @"Book";
    newBook.publishDate = newDate;

    [self.managedObjectContext save:nil];

    return newBook;
}

-(void)deleteBook:(Book *)book
{
    [self.managedObjectContext deleteObject:book];

    [self.managedObjectContext save:nil];
}

-(NSArray *)fetchEntityOfType:(NSString *)entityType withPredicate:(NSPredicate *)predicate andSortKey:(NSString *)sortKey
{
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:entityType inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];
    // Specify criteria for filtering which objects to fetch
    [fetchRequest setPredicate:predicate];
    // Specify how the fetched objects should be sorted
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:sortKey
                                                                   ascending:YES];
    [fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sortDescriptor, nil]];

    NSError *error = nil;
    NSArray *fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];

    if (fetchedObjects == nil)
    {
        NSLog(@"couldn't fetch entity of type '%@', error: %@", entityType, error.localizedDescription);
    }

    return fetchedObjects;
}

@end
21
Zhang

J'ai eu du mal avec un problème similaire. Je voudrais voir:

Using local storage: 1

mais pas d'autre sortie. Et si je reconstruisais l'application, j'obtiendrais quelque chose comme:

Error adding store for new account:

Une chose à noter est que je n'obtiendrais cette sortie que si j'appuie d'abord sur le "bouton d'accueil" de l'iPhone, puis que je rouvrais l'application.

Une chose clé à noter est que je n'avais aucun service sélectionné. Pour résoudre ce problème, j'ai sélectionné "Documents iCloud".

Vous devrez peut-être supprimer l'application avant de la reconstruire.

2
David Poxon