UIManagedDocument和iCloud:“提供的普遍名称已经在使用中。”

时间:2013-11-16 18:02:47

标签: objective-c core-data ios7 icloud uimanageddocument

我正在撰写一款针对iOS7的应用。

我的测试设备是:

1)iPhone 5 16GB

2)iPad第3代16GB Wi-fi + Cellular。

我对UIManagedDocument和iCloud感到生气。

我阅读了我找到的所有文档,我观看了斯坦福CS193P视频,我正在关注Erica Sadun的一些代码。

我无法解决的问题是,在创建本地CoreData文件后,我无法重新打开或保存它,因为它位于[已关闭] SavingError] state。

让我打开并准备就绪非常重要,因为:

1)用户第一次启动应用程序时,它会使用演示数据填充数据库。

2)创建演示数据后,用户可以创建一些新数据,但如果文件处于此状态,则无法保存创建的数据。

3)如果文档处于SavingError状态,则不会保存演示数据。

应用程序中的第一个TableViewController调用fetchDataWithBlock:。是一个简单的标准NSFetchRequest。

这是代码我用来为UIManagedDocument设置PSC选项,并按照Erica的建议(创建,关闭,重新打开)创建和打开它。其中有大量的日志记录,以便更好地了解正在发生的事情。

#define DB_LOCAL_FILE_NAME @"CoreDataLocalFile"
#define DB_TRANSACTIONS_LOG_FILE_NAME @"TransactionsLog"


-(NSURL *) dbLocalDirectory
{
    //Returns the application's document directory
    _dbLocalDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
                                                                inDomains:NSUserDomainMask] lastObject];
    return  _dbLocalDirectory;
}


-(NSURL*) iCloudURL
{
    _iCloudURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
    return  _iCloudURL;
}

-(NSURL*) iCloudDataLogFilesURL
{
    _iCloudDataLogFilesURL = [self.iCloudURL URLByAppendingPathComponent:@"CoreDataTransactionsLog"];

    //If the folder is not present, create it.
    if(![[NSFileManager defaultManager] fileExistsAtPath:_iCloudDataLogFilesURL.path]){

        NSLog(@"Creating the iCloudDataLogFilesURL: %@", _iCloudDataLogFilesURL);

        NSError *error;
        if(![[NSFileManager defaultManager] createDirectoryAtPath:_iCloudDataLogFilesURL.path withIntermediateDirectories:YES attributes:nil error:&error]){
            NSLog(@"ERROR creating iCloud folder: %@", error.localizedFailureReason);
        }
    }

    return _iCloudDataLogFilesURL;
}

-(UIManagedDocument *) managedDocument
{
    //Returns the database ManagedDocument

    if (!_managedDocument){

        //Init the document
        _managedDocument = [[UIManagedDocument alloc] initWithFileURL:
            [self.dbLocalDirectory URLByAppendingPathComponent:DB_LOCAL_FILE_NAME]];

    }
    return _managedDocument;
}


-(void) setPersistentStoreOptionsInDocument: (UIManagedDocument*) document
{

    if(self.iCloudDataLogFilesURL){

        NSMutableDictionary *options = [NSMutableDictionary dictionary];

        [options setObject:DB_TRANSACTIONS_LOG_FILE_NAME
                    forKey:NSPersistentStoreUbiquitousContentNameKey];

        [options setObject:self.iCloudDataLogFilesURL
                    forKey:NSPersistentStoreUbiquitousContentURLKey];

        [options setObject:[NSNumber numberWithBool:YES]
                    forKey:NSMigratePersistentStoresAutomaticallyOption];

        [options setObject:[NSNumber numberWithBool:YES]
                    forKey:NSInferMappingModelAutomaticallyOption];

        document.persistentStoreOptions = options;

        //if file exists, use contents of document.fileURL/@"DocumentsMetadata.plist" instead

        NSLog(@"Using iCLoud with PSC Options: %@", document.persistentStoreOptions);
    }else{
        NSLog(@"ERROR. Can't add iCloud options because I don't have a valid iCloudDataLogFilesURL.");
    }
}

-(void) fetchDataWithBlock: (void (^) (void)) fetchingDataBlock
{

    //If the CoreData local file exists then open it and perform the query
    if([[NSFileManager defaultManager] fileExistsAtPath:[self.managedDocument.fileURL path]]){
        NSLog(@"The CoreData local file in the application sandbox already exists. I am opening it.");
        [self.managedDocument openWithCompletionHandler:^(BOOL success) {
            if(success){
                NSLog(@"SUCCESS: The CoreData local file has been opened succesfully. Fetching data.");
                fetchingDataBlock();
            }else{
                NSLog(@"ERROR: Can't open the CoreData local file. Can't fetch the data.");
                NSLog(@"%@", self.managedDocument);
                return;
            }
        }];


    }else{
        NSLog(@"The CoreData local file in the application sandbox did not exist.");
        //1. Create the Core Data local File

            //----> Set the iCloud options
        [self setPersistentStoreOptionsInDocument:_managedDocument];


        [self.managedDocument saveToURL:self.managedDocument.fileURL
                       forSaveOperation:UIDocumentSaveForCreating
                      completionHandler:^(BOOL success) {

            if(success){
                //2. Close the Core Data local File
                NSLog(@"SUCCESS: I created the CoreData local file in the application sandbox. Now I am closing it.");
                [self.managedDocument closeWithCompletionHandler:^(BOOL success) {

                    if(success){
                        //3. Reopen the Core Data local File
                        NSLog(@"SUCCESS: I closed the CoreData local file just created. Now I am reopening it.");

                        [self.managedDocument openWithCompletionHandler:^(BOOL success) {

                            if(success){
                                NSLog(@"SUCCESS: I reopened the CoreData local file just created. Fetching data.");
                                fetchingDataBlock();
                            }else{
                                NSLog(@"ERROR: Can't reopen the CoreData local file just created and then closed. Can't fetch the data.");
                                NSLog(@"%@", self.managedDocument);
                                return;
                            }
                        }];

                    }else{
                        NSLog(@"ERROR: Can't close the CoreData local file just created. Can't fetch the data.");
                        NSLog(@"%@", self.managedDocument);
                        return;
                    }

                }];

            }else{
                NSLog(@"ERROR: Can't create the CoreData local file in the application sandbox. Can't fetch the data.");
                NSLog(@"%@", self.managedDocument);
                return;
            }

        }];

    }
}

这些是日志

2013-11-16 18:19:18.731当前的iCloud令牌:

2013-11-16 18:19:19.001将iCLoud与PSC选项配合使用:{     NSInferMappingModelAutomaticallyOption = 1;     NSMigratePersistentStoresAutomaticallyOption = 1;     NSPersistentStoreUbiquitousContentNameKey =" TransactionsLog&#34 ;;     NSPersistentStoreUbiquitousContentURLKey =" file:/// private / var / mobile / Library / Mobile%20Documents / XXX / CoreDataTransactionsLog /&#34 ;; }

2013-11-16 18:19:19.003应用程序沙箱中的CoreData本地文件不存在。

2013-11-16 18:19:19.032 ApplicationDidBecomeActive

2013-11-16 18:19:19.313 -PFUbiquitySwitchboardEntryMetadata setUseLocalStorage :: CoreData:Ubiquity:mobile~D5AEDBB6-EEFC-455C-A52C-91ADDC1BE081:TransactionsLog 使用本地存储:1

2013-11-16 18:19:19.771成功:我在应用程序沙箱中创建了CoreData本地文件。现在我关闭它。

2013-11-16 18:19:19.778成功:我关闭刚刚创建的CoreData本地文件。现在我重新打开它。

2013-11-16 18:19:20.073错误:无法重新打开刚刚创建的CoreData本地文件,然后关闭。无法获取数据。

2013-11-16 18:19:20.074 fileURL:file:/// var / mobile / Applications / 07702036-765D-414C-9E8B-D4C2B4055CB8 / Documents / CoreDataLocalFile documentState:[Closed | SavingError]

如您所见,documentState为:[Closed | SavingError]

如果我重新启动应用程序,它会毫无问题地打开数据库,文档状态为NORMAL。 问题是我需要它在第一次运行应用程序时打开并准备就绪并创建演示数据。然后必须使用UIDocumentsaveForOverwriting选项立即保存文件。

我无法要求用户退出应用,然后重新启动或丢失所有的dem数据,因为该文件无法保存!

为了更好地理解发生了什么,我将UIManagedDocument子类化为覆盖handleError:userInteractionPermitted:。

它告诉我文档具有此错误状态,因为"错误域= NSCocoaErrorDomain代码= 134311"提供的普遍名称已被使用。"。

2013-11-17 18:33:50.214 [370:1803] UIManagedDocument中的错误:错误域= NSCocoaErrorDomain代码= 134311"提供的普遍名称已被使用。" UserInfo = 0x14edadf0 {NSLocalizedDescription =提供的普遍名称已被使用。,NSURL = file:///var/mobile/Applications/A0B371E0-C992-4D57-895A-E2CCB8A35367/Documents/CoreDataLocalFile/StoreContent.nosync/CoreDataUbiquitySupport/mobile ~0EDD3A67-63F4-439F-A055-A13808949BBD / TransactionsLog / A9728F87-0F79-4FE3-9B76-AABD3950BB67 / store / persistentStore,NSPersistentStoreUbiquitousContentNameKey = TransactionsLog}

其他信息

如果我搬家:

//设置iCloud选项

[self setPersistentStoreOptionsInDocument:_managedDocument];

使用方法saveToURL创建本地文件之前和之后的

:forSaveOperation:completionHandler:然后UIManagedDocument正确重新打开,但每当我尝试使用与创建相同的方法保存它时,它会触发错误代码134030,但值为UIDocumentSaveForOverwriting for forSaveOperation选项。如果用saveToURL覆盖文件,也会发生同样的情况:forSaveOperation:completionHandler:我使用[self.managedDocument.managedObjectContext save:nil]

注1 :我注释掉了所有的演示数据创建代码,以确保它不是应该责怪的。只要空白的UIManagedDocument在创建后成功重新打开,我就会尝试保存它覆盖。

注2 :从UIManagedDocument(本地文件)获取选项DocumentMetaData.plist显示唯一的选项集是NSPersistentStoreUbiquitousContentNameKey。这很奇怪,因为我在创建文件后设置了4个不同的选项。

有关如何解决此问题的任何提示?

由于

尼古拉

3 个答案:

答案 0 :(得分:0)

很难理解你的代码在做什么,但是使用UIManagedDocument包装器打开一个新的或现有的核心数据文件似乎要复杂得多。

以下是一件奇怪的事情:

NSPersistentStoreUbiquitousContentNameKey = TransactionsLog (见最后一行)

您不应将此设置为事务日志路径。它应该是一个独特的iCloud商店名称。

此外还不清楚为什么需要一个到事务日志路径的URL - 我从不使用它,也不需要设置事务日志路径,因为Core Data会为您执行此操作。如果您的遗留应用程序使用自定义日志路径,则只需要它。

我会在几分钟内发布一些用于创建UIManagedDocument的代码。

答案 1 :(得分:0)

我想我曾经发过一次,但是又来了。有几点需要注意:

  1. 此应用程序允许用户创建多个文档,因此需要UUID以确保每个iCloud文档名称的唯一性。
  2. 打开文件有两种方案,一种是用户在设备上启动文件,另一种是元数据扫描在iCloud中本地不存在的新文件。
  3. 我没有在iCloud容器中创建任何内容,所有内容都由Core Data为我设置。我必须使用元数据扫描来比较iCloud中的文件和本地目录中的文件,并触发创建本地存储(如果不存在)。
  4. 只有当用户创建的新文件才会将初始设置数据添加到商店时,在其他所有情况下,此设置数据都已由创建文件第一个实例的设备创建
  5. 在从iCloud导入这些初始数据之前,用户无法在应用程序中执行任何操作,但通常这不是问题,因为这只会是从iCloud同步的文件的问题。
  6. 在某些情况下,用户可能会导致出现问题,因此需要提供大量文件管理选项,包括从备份中恢复(我的文档存储在应备份的/ Documents中)或重建来自iCloud。不适合胆小的人!
  7. 目前尚未在生产应用中进行测试。我们有一个Mac版本的应用程序,它具有复杂的文件管理功能,不会建议使用iPad或iPhone来管理文件。
  8. //当用户完成以下操作之一时,将调用此方法: // 1.创建一个新文件并输入一个新文件名。然后我们创建了fileURL //使用/ Documents目录,文件名并附加' UUID '+ uuid以确保 //如果用户在另一台设备上使用相同的文件名,请避免重复的文件名。 // 2.从文件浏览器中选择现有文件 // - (void)createNewFile:(NSURL *)fileURL {

    //FLOG(@"createNewFile called with url %@", fileURL);
    
    _creatingNewFile = YES; // Ignore any file metadata scan events coming in while we do this because some iCloud
                            // files get created by Core Data before the local files are created and our scanning
                            // picks up new iCloud files and attempts to create local copies and we don't want this
                            // if this devices is busy creating the new iCloud file
    
    _document = [[OSManagedDocument alloc] initWithFileURL:fileURL];
    
    // Set oberving on this file to monitor the state (we don't use it for anything other than debugging)
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self
               selector:@selector(documentStateChanged:)
                   name:UIDocumentStateChangedNotification
                 object:_document];
    
    _openedVersion = [NSFileVersion currentVersionOfItemAtURL:fileURL];
    _openedVersionDate = _openedVersion.modificationDate;
    _openedVersionDevice = _openedVersion.localizedNameOfSavingComputer;
    //FLOG(@" file  version date: %@", _openedVersionDate);
    //FLOG(@" file  version device: %@", _openedVersionDevice);
    
    NSString *fileName = [[fileURL URLByDeletingPathExtension] lastPathComponent];
    
    [_document setPersistentStoreOptions:@{NSPersistentStoreUbiquitousContentNameKey:fileName,
                                           NSMigratePersistentStoresAutomaticallyOption:@YES,
                                           NSInferMappingModelAutomaticallyOption:@YES,
                                           NSSQLitePragmasOption:@{ @"journal_mode" : @"DELETE" }}];
    
    _managedObjectContext = _document.managedObjectContext;
    
    if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) {
        //FLOG(@" file exists so open it: %@",fileURL);
        [_document openWithCompletionHandler:^(BOOL success){
            if (!success) {
                // Handle the error.
                LOG(@" error creating file");
            } else {
                //LOG(@" file opened");
                [self fileOpened];  // Now initialise the UI and let the user continue...
            }
        }];
    
    }
    else {
        // File does not exist so that means the user has created a new one and we need to
        // load some initialisation data into the Core Data store (codes tables, etc.)
        //
        // At this stage we have a database in memory so we can just use the _document.managedObjectContext
        // to add objects prior to attempting to write to disk.
    
        //LOG(@" file DOES NOT exist so add initial data");
        [self addInitialData];
    
        // Just checking if anything has been written to disk, nothing should not exist on disk yet.
        // Debugging use only
        if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]])
            LOG(@" file exists but keep going anyway :-(");
        else
            LOG(@" file still does not exist :-)");
    
    
        // OK now save a copy to disk using UIManagedDocument
        // NOTE: the iCloud files are written before the UIManagedDocument.fileURL, presumably because Core Data does this setup
        // in response to the [moc save:].  Make sure we don't pick this up in our iCloud metaData scan and attempt to create
        // it as if it were a new iCloud file created by some other device.
        //
        [_document saveToURL:_document.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success){
            if (!success) {
                // Handle the error.
                LOG(@" error saving file :-(");
            }
    
            // We close the file and wait for it to appear in the file browser and then
            // let the user select it from the browser to open it and start using it.
            // Skip this if you want to open it directly and [self fileopened] but
            // bear in mind the fileListController will not be correctly set up when we return to it.
    
            [_document closeWithCompletionHandler:^(BOOL success){
                if (!success) {
                    // Handle the error.
                    LOG(@" error closing file after creation :-(");
                    FLOG(@" file URL is %@", [fileURL path]);
                } else {
                    FLOG(@" file closed %@", fileName);
    
                    _creatingNewFile = NO;  // OK we are done, so let metaData scanning go ahead as normal
    
                    // Tell our UITableView file list that we are done and trigger scanning of local and iCloud files
                    // The fileListController will the add the new file itself and the user will then pick the
                    // file from this list in order to open it.
    
                    // To open the file automatically use the callback in the fileListController
                    // to select and then open the file so it looks seamless to the user.
                    [self.fileListController fileHasBeenCreated:fileURL];
    
    
                    // Stop observing now
                    [center removeObserver:self
                                      name:UIDocumentStateChangedNotification
                                    object:_document];
    
                }
            }];
        }];
    }
    

    }

答案 2 :(得分:0)

最后我修好了!自从Erica的演示代码以来,CoreData / UIManagedDocument内部的内容可能在过去两年内发生了变化。我不使用关闭文档和随后的重新打开例程简化了数据的创建/打开/获取。现在代码可以工作了。

-(void) fetchDataWithBlock: (void (^) (void)) fetchingDataBlock
{

    //If the CoreData local file exists then open it and perform the query
    if([[NSFileManager defaultManager] fileExistsAtPath:[self.managedDocument.fileURL path]]){
        NSLog(@"The CoreData local file in the application sandbox already exists.");

        if (self.managedDocument.documentState == UIDocumentStateNormal){
            NSLog(@"The CoreData local file it's in Normal state. Fetching data.");
            fetchingDataBlock();
        }else if (self.managedDocument.documentState == UIDocumentStateClosed){
            NSLog(@"The CoreData local file it's in Closed state. I am opening it.");

            [self.managedDocument openWithCompletionHandler:^(BOOL success) {
                if(success){
                    NSLog(@"SUCCESS: The CoreData local file has been opened succesfully. Fetching data.");
                    fetchingDataBlock();
                }else{
                    NSLog(@"ERROR: Can't open the CoreData local file. Can't fetch the data.");
                    NSLog(@"%@", self.managedDocument);
                    return;
                }
            }];
        }else{
            NSLog(@"ERROR: The CoreData local file has an unexpected documentState: %@", self.managedDocument);
        }
    }else{
        NSLog(@"The CoreData local file in the application sandbox did not exist.");
             NSLog(@"Setting the UIManagedDocument PSC options.");
        [self setPersistentStoreOptionsInDocument:self.managedDocument];

        //Create the Core Data local File
        [self.managedDocument saveToURL:self.managedDocument.fileURL
                       forSaveOperation:UIDocumentSaveForCreating
                      completionHandler:^(BOOL success) {

            if(success){

                NSLog(@"SUCCESS: The CoreData local file has been created. Fetching data.");
                fetchingDataBlock();

            }else{
                NSLog(@"ERROR: Can't create the CoreData local file in the application sandbox. Can't fetch the data.");
                NSLog(@"%@", self.managedDocument);
                return;
            }

        }];

    }
}