且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

Xcode 6 iOS 8 iCloud 核心数据设置

更新时间:2022-11-21 21:12:01

iOS 8 解决方案:

好的...那么....lol.我想我解决了.

iOS 8 Solution:

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

新发现.浏览此页面后:

New discovery. After skimming through this page:

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

它说:

iCloud Drive 是 Apple 新的和改进的 iCloud 同步和文件允许您在 iOS 8 之间共享文档的存储功能设备和运行 OS X 10 Yosemite 的 Mac.

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 帐户升级到 iCloud Drive(免费升级).

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

在升级到 iCloud 驱动器并通过一些 Xcode 6 更改重新运行我的应用后,它现在可以工作了.

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

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

Some Important Things to note:

  • iCloud Drive 与之前的 iCloud Document & 不兼容数据存储.因此,如果您要进行测试,请确保您的所有设备都使用 iCloud 驱动器和 iOS 8.
  • 模拟器似乎只同步一次,在启动应用程序后,而设备每间隔持续同步一次.不确定这是不是模拟器错误.或者我的配置不完美.
  • 第一次尝试使用使用默认容器"在模拟器中对我不起作用(但在设备上它确实有效),可能需要删除应用程序的先前副本并重新安装.首先尝试使用默认容器,看看它是否有效,否则,请阅读下面的下一点.
  • 由于上述原因,我改为使用具有这种模式的 Ubiquity 容器:

  • 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.
  • 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.
  • 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)

比如:

iCloud.com.xxxxxxxx.iCloudCoreDataDemo

其中xxxxxxxx"是我的公司名称标识符.

Where "xxxxxxxx" is my company name identifier.

我通过登录我的 iOS 开发者中心制作了上面的 iCloud 容器,也许你可以在 Xcode 6 中按+"号并在那里输入一个,Xcode 应该会自动为你设置一切.

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.

我用来测试它是否工作的一个代码块是这样的:

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

如果你看到欢呼?"那么就可以了,您还应该在 Xcode 控制台输出中看到这种文本模式:

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

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

注意两个重要的行:

Using local storage: 1

后来变成:

Using local storage: 0

本地存储 1 表示它当前正在使用本地存储,而本地存储 0 表示它已将数据移至 iCloud 存储.

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

我希望这能让其他人受益.

I hope this benefits everyone else.

好的,所以我刚刚发现了一些东西并设法让它仅适用于 iOS 7.我仍然没有想出如何在 iOS 8 中做到这一点,但我注意到了一些重要的事情.

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 8.0.2 的 iPhone 5 上,iCloud 设置菜单中不再有文档和数据"选项.

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

但是,在运行 iOS 7 的 iPad 上,我确实看到了文档和数据"选项.

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

也许这就是它在 iOS 8 上不起作用的原因,我们不再有 Document &数据存储?

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

无论如何,这是我发现的仅适用于 iOS 7 的解决方案.

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

我在这里找到了这个页面

I found this page here

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

其中一行说:

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

果然,我进入了我的 Xcode 6 项目文件并勾选了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".

我学到的一件事是我需要在 appDelegate 中初始化我的 PersistentStack.以前,我尝试在 +(id)sharedInstance 方法中初始化持久堆栈,但它导致 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.

我重写了一个基本的应用程序并稍微修改了持久化堆栈:

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

#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

应用委托.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"];
}

持久堆栈.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

持久化堆栈.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