阅读背景:

Xcode 6 iOS 8 iCloud核心数据设置

来源:互联网 

Has anyone got iCloud core data syncing setup on Xcode 6 and iOS 8? (hopefully this isn't a duplicate post)

有人在Xcode 6和ios8上安装了iCloud核心数据同步设置吗?(希望这不是一篇重复的文章)

Where did iCloud Core Data storage option go?

iCloud核心数据存储选项将何去何从?

I remember Core Data had an extra storage option called Core Data storage, but now in Xcode 6 it only seems to show key-value and document storage when I enable the iCloud toggle in Xcode 6.

我记得Core Data有一个额外的存储选项叫做Core Data storage,但是现在在Xcode 6中,当我在Xcode 6中启用iCloud toggle时,它似乎只显示键值和文档存储。

Background Info

  • New iPad app
  • 新的iPad应用程序
  • Xcode 6
  • Xcode 6
  • Targeting minimum version iOS 7 but hoping it works for iOS 8 too? (We can set iOS 8 as minimum)
  • 目标最小版本的iOS 7,但希望它也适用于iOS 8 ?(我们可以将ios8设为最小)
  • Want to use iCloud Core Data storage instead of key-value or document storage.
  • 要使用iCloud Core Data storage而不是key-value或document storage。
  • Have logged into the same Apple account in the Settings > iCloud for both Simulator and iPad device
  • 是否已经登录了为模拟器和iPad设备设置的> iCloud的同一个苹果账号
  • My provisioning profile used to code sign the app has iCloud enabled for both development and distribution (was automatically enabled by Xcode)
  • 我的配置文件用于编码应用程序已为开发和分发启用iCloud (Xcode自动启用)

My Setup

So far, I don't know if I've setup Core Data iCloud correctly.

到目前为止,我不知道我是否正确地设置了iCloud核心数据。

Xcode appears to have setup the iCloud containers in the iOS Developer Portal:

Xcode似乎在iOS开发者门户中设置了iCloud容器:

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

My Xcode 6 iCloud "services" list shows no ticks next to:

我的Xcode 6 iCloud“服务”列表显示:

  • Key-value storage
  • 键值存储
  • iCloud documents
  • iCloud文档
  • CloudKit
  • CloudKit

Which one should we be using now since it doesn't list "core data" as a storage option?

既然它没有列出“核心数据”作为存储选项,我们现在应该使用哪个呢?

In the "Containers" directly below the "services", it shows the following options greyed out:

在直接低于“服务”的“容器”中,它显示了以下选项:

  • Use default container (this one ticked by default)
  • 使用默认容器(这个容器被默认选中)
  • Specify custom containers
  • 指定自定义容器
  • iCloud.com.xxxxxxxxxx.xxxxxxxxx (again, substituted the real identifiers with xxxx)
  • icloud .com. xxxxxxxxxxxxxxx(再次,用xxxx替换了真实标识符)

I can't choose any option, it seems to force me to "Use default container".

我不能选择任何选项,它似乎强迫我“使用默认容器”。

Finally, Xcode seems to show ticks for:

最后,Xcode似乎显示了以下几点:

  • Add the "iCloud" entitlement to your App ID
  • 将“iCloud”权限添加到你的App ID。
  • Add the "iCloud containers" entitlement to your App ID
  • 将“iCloud containers”添加到应用ID中
  • Add the "iCloud" entitlemen to your entitlements file
  • 把“iCloud”授权给你的权利文件。
  • Link CloudKit.framework
  • 链接CloudKit.framework

So by Xcode's own automated process, it setup everything for me.

通过Xcode自己的自动化流程,它为我设置了一切。

The Reference Code

OK, so I read around and notice a iCloud stack written here:

我四处看了看,注意到这里写着一个iCloud堆栈:

https://github.com/mluisbrown/iCloudCoreDataStack

https://github.com/mluisbrown/iCloudCoreDataStack

I've taken the necessary code and tried to adapt to my Core Data manager singleton:

我使用了必要的代码,并试图适应我的核心数据管理器singleton:

DataManager.h file

+ (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;

DataManager.m file

@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;
}

My Observations

I tried to run the app on the iPad Simulator (I believe it is the iOS 8 simulator) and on iPad device running iOS 7.x

我试着在iPad模拟器(我相信是iOS 8模拟器)和运行iOS 7.x的iPad设备上运行这个应用程序

I created an album with a user entered name on the simulator, but I am not seeing the iPad device showing the newly created album. I also tried reversing the roles, iPad device create, iOS simulator no results either.

我创建了一个用户在模拟器上输入名字的相册,但是我没有看到iPad设备显示新创建的相册。我还尝试了角色颠倒,iPad设备创建,iOS模拟器也没有结果。

I do see my log messages:

我确实看到了我的日志信息:

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

If I minimise the app in the simulator and open it again (without pressing the Stop button in Xcode), I see these message:

如果我最小化模拟器中的应用程序并再次打开它(不按Xcode中的Stop按钮),我看到以下消息:

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

I read that "Using local storage: 0" is what it should ideally be? and that 1 means local device data store rather than iCloud data store.

我读到“使用本地存储:0”是理想的状态吗?这个1表示本地设备数据存储而不是iCloud数据存储。

When I create an album, save it, stop the simulator, then start the app again, my albums disappears, but immediately after I create a new album, all the previous album reappear magically again. It's a bit odd. If I don't use iCloud and revert my code to previous setup, I can create and see my album fine, regardless of whether I minimise my app or not, or restart app, but then I don't have iCloud sync which I need.

当我创建一个相册时,保存它,停止模拟器,然后再次启动app,我的相册就消失了,但是当我创建一个新相册后,所有之前的相册都会神奇地再次出现。这是有点奇怪。如果我不使用iCloud,并将我的代码恢复到之前的设置,我可以创建并查看我的相册,无论我是否最小化我的应用程序,或者重启应用程序,但是我没有iCloud同步需要。

Have I made any mistakes anywhere?

我在任何地方都犯过错误吗?

Sorry for the long post but has anyone got iCloud working for iOS 8 and Xcode 6 ?

不好意思,很长一段时间了,有人把iCloud用于iOS 8和Xcode 6吗?

I could really use some help.

我真的需要一些帮助。

Extra Questions

1) Does iOS 8 require the use of this container identifier ? (which Xcode 6 generated for me):

1)ios8是否需要使用这个容器标识符?(Xcode 6为我生成):

com.apple.developer.icloud-container-identifiers

That's not what the iOS 7 one looks like right? iOS 7 one is more like:

这不是ios7的样子,对吧?iOS 7更像是:

com.apple.developer.ubiquity-container-identifiers

2) Do I need an iCloud Drive account before it works?

2)我需要一个iCloud驱动器账户吗?

Super confused @_@

超级困惑@_@

2 个解决方案

#1


21  

iOS 8 Solution:

OK...then....lol. I think I solved it.

好吧……然后.... lol。我想我解决了。

New discovery. After skimming through this page:

新发现。浏览本页后:

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

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

It says:

它说:

iCloud Drive is Apple's new and improved iCloud syncing and file storage feature that allows you to share documents between your iOS 8 devices and your Mac running OS X 10 Yosemite.

iCloud Drive是苹果最新改进的iCloud同步和文件存储功能,可以让你在iOS 8设备和运行OS X 10 Yosemite的Mac之间共享文件。

So, I decided to bite the bullet and upgrade my iCloud account to iCloud drive (free to upgrade).

所以,我决定咬紧牙关,把我的iCloud账户升级到iCloud drive(免费升级)。

After upgrading to iCloud drive, and re-ran my app with a few Xcode 6 changes, it's working now.

升级到iCloud drive后,我重新运行了我的应用,并做了一些Xcode 6的修改,现在可以运行了。

Some Important Things to note:

需要注意的一些重要事项:

  • iCloud Drive is incompatible with previous iCloud Document & Data storage. So if you're going to test, make sure all your devices are using iCloud drive and iOS 8.
  • iCloud驱动器与之前的iCloud文档和数据存储不兼容。因此,如果你要进行测试,请确保所有设备都使用了iCloud drive和ios8。
  • Simulator only seems to sync once, after launching app while device continuously syncs every interval. Not sure if it's a simulator bug or not. Or maybe my configuration is not perfect.
  • 模拟器似乎只同步一次,在启动app之后,设备不断地同步每一个间隔。不确定这是否是一个模拟器错误。或者可能我的构型不完美。
  • Using "Use default containers" doesn't work in the simulator for me (but on the device it does work) on the first try, maybe need to delete the previous copy of the app and reinstall. Try using default containers first and see if it works, otherwise, read the next point below.
  • 在第一次尝试时,在模拟器中使用“使用默认容器”对我来说行不通(但在设备上确实管用),可能需要删除之前的应用程序副本并重新安装。首先尝试使用默认容器,并查看它是否有效,否则,请阅读下面的下一个要点。
  • For the above reason, I changed to using a Ubiquity container with this pattern:

    基于上述原因,我改用了一个无所不在的容器,该模式如下:

    iCloud.$(CFBundleIdentifier)

    iCloud。$(CFBundleIdentifier)

So something like:

所以类似:

iCloud.com.xxxxxxxx.iCloudCoreDataDemo

Where "xxxxxxxx" is my company name identifier.

其中“xxxxxxxx”是我的公司名称标识符。

I made the above iCloud container by logging into my iOS Developer Center, perhaps you could just press the "+" sign inside Xcode 6 and enter one there, Xcode should automagically setup everything for you.

我通过登陆iOS开发者中心,把上面的iCloud容器做了个遍,也许你可以在Xcode 6中按下“+”号,然后在那里输入一个,Xcode应该自动为你设置一切。

One block of code I used to test to see if it's working is this:

我用来测试它是否有效的代码块是:

- (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;
}

If you see "hurray?" then it's fine, you should also see this pattern of text in your Xcode console output:

如果您看到了“hurray?”,那么也可以在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

Notice the two important lines:

注意这两条重要的线:

Using local storage: 1

later becomes:

后来就变成:

Using local storage: 0

Local storage 1 means it's currently using local storage, while local storage 0 means it has moved the data to iCloud storage.

本地存储1表示当前正在使用本地存储,而本地存储0表示已将数据转移到iCloud存储中。

I hope this benefits everyone else.

我希望这对其他人都有好处。

iOS 7 Only Solution:

OK, so I've just discovered something and managed to get it working for iOS 7 only. I still haven't figured out how to do it in iOS 8 but I have noticed something important.

好的,我刚刚发现了一些东西并设法让它只适用于iOS 7。我还没有在ios8中弄明白怎么做,但是我注意到了一些重要的事情。

On my iPhone 5 running iOS 8.0.2, I don't have the "Document & Data" option inside the iCloud settings menu anymore.

在运行iOS 8.0.2的iPhone 5上,我再也没有iCloud设置菜单中的“文档和数据”选项了。

However, on my iPad running iOS 7, I DO see the "Document & Data" options.

然而,在运行iOS 7的iPad上,我确实看到了“文档和数据”选项。

Perhaps this is the reason why it doesn't work on iOS 8, we no longer have Document & Data storage ?

也许这就是为什么它不能在iOS 8上运行,我们不再有文档和数据存储的原因吧?

Anyhow, here's what I discovered for iOS 7 only solution.

总之,这是我发现的ios7唯一解决方案。

I found this page here

我在这里找到这一页

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

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

and one of the line says:

其中一行写道

  • iCloud document storage is for user-visible file-based content, Core Data storage, or for other complex file-based content.
  • iCloud文档存储用于基于用户可见文件的内容、核心数据存储或其他基于文件的复杂内容。

Sure enough, I went into my Xcode 6 project file and ticked the "iCloud Documents" option. This un-greyed the radio buttons, but I still left it at "Use default Containers".

果然,我进入我的Xcode 6项目文件,勾选了“iCloud文档”选项。这是单选按钮,但我仍然把它留在“使用默认容器”。

One thing I learned is that I need to init my PersistentStack in the appDelegate. Previously, I tried to init the persistent stack inside the +(id)sharedInstance method but it caused the iCloud to only sync the first time, so after initial load and sync, adding new record doesn't get synced afterwards.

我学到的一件事是,我需要在appDelegate中初始化我的PersistentStack。之前,我尝试在+(id)sharedInstance方法中初始化持久堆栈,但它导致iCloud第一次同步,所以在初始加载和同步之后,添加新记录之后不会同步。

I rewrote a basic app and modified the persistent stack slightly:

我重写了一个基本的app,稍微修改了一下持久化栈:

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

Persistent Stack.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

#2


2  

I struggled with a similar issue. I would see:

我也遇到过类似的问题。我想看到:

Using local storage: 1

but no other output. And if I rebuilt the app I would get something like:

但是没有其他的输出。如果我重新构建这个应用,我会得到这样的结果:

Error adding store for new account:

One thing to note is that I would only get this output if I first presses the "home button" on the iPhone, and then reopened the app.

需要注意的是,只有当我首先在iPhone上按下“home”按钮,然后重新打开应用程序时,我才能得到这个输出。

A key thing to note is that I had no services selected. To fix this issue, I selected "iCloud Documents".

需要注意的一个关键问题是,我没有选择任何服务。为了解决这个问题,我选择了“iCloud文档”。

You may need to delete the app before rebuilding.

在重新构建之前,您可能需要删除该应用程序。


分享到: