web-dev-qa-db-ja.com

アプリデリゲートに永続ストア(iCloud対応)を追加するとクラッシュする

これを自分の個人コードの参照として使用しようとしている人を助けるために、これを更新し始めます。

最新の更新

  • デバイスが相互に通信しなくなったら、デバイスを再同期する方法を見つけたと確信しています。以下の私の答えをすべての詳細で更新します。皆様のお役に立てば幸いです。これを理解するには、ほぼ2か月の試行錯誤が必要です。したがって、これを参照して、iCloudを介してデバイスが再び相互に通信するようにする同様の問題を抱えている他の人と共有してください。これをすべて理解するのに私は永遠にかかったので、他の多くの開発者が独自の一時的な修正を作成する必要がないようにできることを嬉しく思います。

正しくセットアップするのに役立つもう1つの追加

  • アカウントに関連付けられたiCloudデータを持つアプリを更新した後、iCloudデータがデバイスにすぐにマージしようとするため(デバイスがまだ永続ストアをセットアップしていない場合)、アプリを開くとクラッシュする可能性があることがわかりました。 @property (nonatomic, readwrite) BOOL unlocked;を_AppDelegate.h_に追加し、_@synthesize unlocked;_を_AppDelegate.m_に追加しました。次に、- (NSPersistentStoreCoordinator *)persistentStoreCoordinatorメソッドと- (void)mergeChangesFrom_iCloudメソッドを変更しました。どちらも以下に表示されます(永続ストアのセットアップの中央とiCloudマージメソッドの下部) )。本質的に、私はアプリが永続ストアをセットアップするまでiCloudがデータをマージしないようにアプリに指示しています。そうしないと、読み取り不能な障害が原因でアプリがクラッシュします。

これが私のpersistentStoreCoordinatorを設定する方法です:

_- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (__persistentStoreCoordinator != nil)
    {
        return __persistentStoreCoordinator;
    }


    // here is where you declare the persistent store is not prepared;
    self.unlocked = NO;

    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Maintain_My_Car.sqlite"];

    __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];   

    NSPersistentStoreCoordinator *psc = __persistentStoreCoordinator; 

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        NSFileManager *fileManager = [NSFileManager defaultManager];
        NSDictionary *options = nil;

        NSURL *cloudURL = [fileManager URLForUbiquityContainerIdentifier:nil];

        NSString *coreDataCloudContent = [[cloudURL path] stringByAppendingPathComponent:@"data"];

        if (coreDataCloudContent.length != 0) {
            // iCloud enabled;

            cloudURL = [NSURL fileURLWithPath:coreDataCloudContent];
            options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, @"<bundleIdentifier>.store", NSPersistentStoreUbiquitousContentNameKey, cloudURL, NSPersistentStoreUbiquitousContentURLKey, nil];

        } else {

            // iCloud not enabled;
            options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];

        }

        NSError *error = nil;

        [psc lock];

        if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {

            NSLog(@"bad things %@ %@", error, [error userInfo]);
            abort();

        }
        [psc unlock];

        // the store is now prepared and ready for iCloud to import data;
        self.unlocked = YES;


        dispatch_async(dispatch_get_main_queue(), ^{

            NSLog(@"iCloud persistent store added");

            [[NSNotificationCenter defaultCenter] postNotificationName:@"RefetchAllDatabaseData" object:self userInfo:nil];

        });
    });

    return __persistentStoreCoordinator;
}
_

もちろん、_<myAppKey>_と_<bundleIdentifier>_は実際の値です。このコードを共有するために、それらをマスクしているだけです。

一部の人々はまだこれに問題を抱えており、独自のiCloud対応Core Dataアプリケーションをセットアップする方法のリファレンスとしてこの質問を使用している可能性があることを知っているので、個人コードを変更するたびにこれを更新して、皆さんは私のために働くコードを使うことができます。この更新では、最初のcloudURLを_[fileManager URLForUbiquityContainerIdentifier:@"<TeamIdentifier>.<bundleIdentifier>"]_から_[fileManager URLForUbiquityContainerIdentifier:nil]_に変更し、コンテナー情報が資格ファイルから収集されるようにしました。

追加のメソッド __notificationArray_は次のように定義されます:@property (nonatomice, strong) NSMutableArray *notificationArray; _@synthesize notificationArray = _notificationArray;_

_- (void)mergeChangesFrom_iCloud:(NSNotification *)notification {
    if (self.unlocked) {
        NSManagedObjectContext *moc = [self managedObjectContext];

        if (self.notificationArray.count != 0) {
            for (NSNotification *note in _notificationArray) {
                [moc performBlock:^{
                    [self mergeiCloudChanges:note forContext:moc];
                }];
            }
            [_notificationArray removeAllObjects];
            [moc performBlock:^{
                [self mergeiCloudChanges:notification forContext:moc];
            }];
        } else {
            [moc performBlock:^{
                [self mergeiCloudChanges:notification forContext:moc];
            }];
        }
    } else {
        if (_notificationArray == nil) {
            _notificationArray = [[NSMutableArray alloc] init];
        }
        [_notificationArray addObject:notification];
    }
}

- (void)resetStore {
    [self saveContext];
    __persistentStoreCoordinator = nil;
    __managedObjectContext = nil;
    // reset the managedObjectContext for your program as you would in application:didFinishLaunchingWithOptions:
    myMainView.managedObjectContext = [self managedObjectContext];
    // the example above will rebuild the MOC and PSC for you with the new parameters in mind;
}
_

次に、_mergeiCloudChanges:forContext:_メソッドがあります。

_- (void)mergeiCloudChanges:(NSNotification *)note forContext:(NSManagedObjectContext *)moc {
    // below are a few logs you can run to see what is being done and when;
    NSLog(@"insert %@", [[note userInfo] valueForKey:@"inserted"]);
    NSLog(@"delete %@", [[note userInfo] valueForKey:@"deleted"]);
    NSLog(@"update %@", [[note userInfo] valueForKey:@"updated"]);
    [moc mergeChangesFromContextDidSaveNotification:note];

    NSNotification *refreshNotification = [NSNotification notificationWithName:@"RefreshAllViews" object:self userInfo:[note userInfo]];
    [[NSNotificationCenter defaultCenter] postNotification:refreshNotification];
    // do any additional work here;
}
_

初期の問題

  • IOS 5.0.1でiCloudを使用すると、永続ストアに関連するエラーが発生することがあります。実験で見つけた新しい情報でこれを更新し続けますが、これまでのところ、私が提供したソリューションは、アプリを再び正常に動作させる唯一の方法です(残念ながら、jlstreckerのソリューションは私には機能しませんでした)。次のようなエラーが表示され始めます。

    -NSPersistentStoreCoordinator addPersistentStoreWithType:configuration:URL:options:error :: CoreData:Ubiquity:ユビキタスルートURLを読み取ろうとしてエラーが発生しました:file://localhost/private/var/mobile/Library/Mobile%20Documents/./data/。エラー:エラードメイン= LibrarianErrorDomainコード= 1 "操作を完了できませんでした。(LibrarianErrorDomainエラー1-アイテムのダウンロードを開始できません。)" UserInfo = 0x176000 {NSURL = file:// localhost/private/var/mobile/Library /Mobile%20Documents/./data/、NSDescription=アイテムのダウンロードを開始できません。}

    私の人生では、なぜこれが突然見られるのか、それを止める方法がわかりません。両方のデバイスからアプリを削除し、以前にそれらの間で同期していたiCloudデータを削除し、アプリに関するバックアップからすべてのデータを削除しました。 Xcodeを再起動し、両方のデバイスを再起動し、Xcodeプロジェクトをクリーンアップしましたが、エラーの表示を止めるものは何もありません。私はこれまでこのエラーを見たことがなく、それを特定する方法についてオンラインで何かを見つけることができませんでした。

    アプリがここでクラッシュします:

    _if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
    
        NSLog(@"bad things %@ %@", error, [error userInfo]);
        abort();
    
    }
    _

    ログがヒットすることも、中止されることもありません。上記のエラーが表示され、アプリ自体が応答しなくなります。誰かが私を正しい方向に向けるのを手伝ってくれるなら、私はとても感謝しています。

以前の問題/質問

  • これは、ベータ版から5.0.1のパブリックリリースに更新された後も続くようです。私に最後に起こったのは、マネージドコンテキストデータモデルを変更した後です。私はまだアプリをリリースしていないことを考えると、モデルの新しいバージョンをマージすることはしませんでした。アプリを削除してデバイスに再インストールしたところ、iCloudコンテナに保存されているデータとの連携が拒否されたため、ストアがアイテムをダウンロードできないというエラーが表示されました。これは、データモデルタイプの競合が原因であると思います。これは完全に理にかなっています。したがって、コンテナを削除せずに、iCloudコンテナ内のデータを削除する必要があるようです。 iCloudデータを削除すると、すべてが停止するように見えます。つまり、コンテナとアプリIDが無効になります。簡単そうだったので、jlstreckerの提案に従って新しいコンテナを作成してみましたが、残念ながら、これはまったく役に立ちませんでした。それで、もう一度、私は私の答えで概説したステップを踏まなければなりませんでした、そしてそれは再びトリックをしました。しかし、毎回新しいアプリIDを作成し、プロビジョニングプロファイルを更新する必要があることを考えると、原因を絞り込んでより迅速な解決策を見つけるために、学んだことを更新するのが最善だと思いました。

    ICloud>ストレージとバックアップ>ストレージの管理を通過し、アプリを削除することがデータを空にするための最良の解決策のように見えますが、これを行うとコンテナが破損し、上記のエラーが発生するようです。そして、これを正常に実行した後、アプリを削除してデバイスに再インストールしても(デバイスに初めて表示されるように見せるため、できればコンテナを再作成するため)、アプリを表示させることはできません。ドキュメントとデータリストに再度表示されます。これは、そのようにiCloudからデータを削除した人が、iCloudがアプリで二度と機能しなくなることを意味するかどうかについていくらか懸念しています。これまでのところ、アプリで開発プロファイルのみを使用しているため、配布プロファイルを使用すると多少の違いが生じる可能性がありますが、確実に何かを言う前に、それをテストする必要があります。

これらの新しいアップデートが、ストアのセットアップに問題がある可能性のある人に役立つことを願っています。これまでのところ、うまく機能しています。より良い修正や、プロセスをより見苦しくするものを見つけたら、必ずもっと更新します。

38
justin

デバイスを再同期するための更新された回答何ヶ月にもわたっていじくり回して、根本的な問題が何であるか(私は信じています)を理解するようになりました。問題は、デバイスが同期しなくなった後、デバイスが再び相互に通信するようになることです。何が原因かはわかりませんが、トランザクションログが破損しているか、(おそらく)ログ自体のコンテナが再作成されているのではないかと疑っています。これは、ログの読み取り/書き込みが可能なコンテナCへの両方の投稿とは対照的に、デバイスAがコンテナAとデバイスBに変更を投稿するのと同じようになります。

問題がわかったので、解決策を作成する必要があります。さらにいじくり回すと、次のようになりました。 resetiCloudSync:(BOOL)isSourceというメソッドがあります。これは、元の質問の上記のメソッドの修正バージョンです。

- (void)resetiCloudSync:(BOOL)isSource {
    NSLog(@"reset sync source %d", isSource);
    NSManagedObjectContext *moc = self.managedObjectContext;

    if (isSource) {
        // remove data from app's cloud account, then repopulate with copy of existing data;

        // find your log transaction container;
        NSURL *cloudURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
        NSString *coreDataCloudContent = [[cloudURL path] stringByAppendingPathComponent:@"store"];
        cloudURL = [NSURL fileURLWithPath:coreDataCloudContent];
        NSError *error = nil;

        // remove the old log transaction container and it's logs;
        [[NSFileManager defaultManager] removeItemAtURL:cloudURL error:&error];

        // rebuild the container to insert the "new" data into;
        if ([[NSFileManager defaultManager] createFileAtPath:coreDataCloudContent contents:nil attributes:nil]) {

            // this will differ for everyone else. here i set up an array that stores the core data objects that are to-many relationships; 
            NSArray *keyArray = [NSArray arrayWithObjects:@"addedFields", @"mileages", @"parts", @"repairEvents", nil];

            // create a request to temporarily store the objects you need to replicate;
            // my heirarchy starts with vehicles as parent entities with many attributes and relationships (both to-one and to-many);
            // as this format is a mix of just about everything, it works great for example purposes;
            NSFetchRequest *request = [[NSFetchRequest alloc] init];
            NSEntityDescription *entity = [NSEntityDescription entityForName:@"Vehicle" inManagedObjectContext:moc];
            [request setEntity:entity];
            NSError *error = nil;
            NSArray *vehicles = [moc executeFetchRequest:request error:&error];

            for (NSManagedObject *object in vehicles) {
                NSManagedObject *newObject = [NSEntityDescription insertNewObjectForEntityForName:object.entity.name inManagedObjectContext:moc];
                // check regular values;
                for (NSString *key in object.entity.attributesByName.allKeys) {
                    [newObject setValue:[object valueForKey:key] forKey:key];
                }

                // check relationships;
                NSMutableSet *relSet = [[NSMutableSet alloc] init];
                for (NSString *key in object.entity.relationshipsByName.allKeys) {
                    [relSet removeAllObjects];

                    // check to see relationship exists;
                    if ([object valueForKey:key] != nil) {

                        // check to see if relationship is to-many;
                        if ([keyArray containsObject:key]) {
                            for (NSManagedObject *toManyObject in [object valueForKey:key]) {
                                [relSet addObject:toManyObject];
                            }
                        } else {
                            [relSet addObject:[object valueForKey:key]];
                        }

                        // cycle through objects;
                        for (NSManagedObject *subObject in relSet) {
                            NSManagedObject *newSubObject = [NSEntityDescription insertNewObjectForEntityForName:subObject.entity.name inManagedObjectContext:moc];
                            // check sub values;
                            for (NSString *subKey in subObject.entity.attributesByName.allKeys) {
                                NSLog(@"subkey %@", subKey);
                                [newSubObject setValue:[subObject valueForKey:subKey] forKey:subKey];
                            }
                            // check sub relationships;
                            for (NSString *subRel in subObject.entity.relationshipsByName.allKeys) {
                                NSLog(@"sub relationship %@", subRel);
                                // set up any additional checks if necessary;
                                [newSubObject setValue:newObject forKey:subRel];
                            }
                        }
                    }
                }   
                [moc deleteObject:object];
            }
            [self resetStore];
        }
    } else {
        // here we remove all data from the current device to populate with data pushed to cloud from other device;
        for (NSManagedObject *object in moc.registeredObjects) {
            [moc deleteObject:object];
        }
    }
    [[[UIAlertView alloc] initWithTitle:@"Sync has been reset" message:nil delegate:nil cancelButtonTitle:@"Dismiss" otherButtonTitles:nil] show];
}

このコードでは、2つの異なるパスを取る必要があります。 1つは、同期しておらず、ソースデバイスからデータをインポートする必要があるデバイス用です。そのパスが行うのは、メモリをクリアして、そこにあるはずのデータ用にメモリを準備することだけです。

もう1つの(isSource = YES)パスは、いくつかのことを行います。通常、破損したコンテナを削除します。次に、新しいコンテナを作成します(ログに常駐する場所を設定するため)。最後に、親エンティティを検索してコピーします。これは、トランザクションログコンテナに、そこにあるはずの情報を再入力します。次に、重複がないように元のエンティティを削除する必要があります。最後に、永続ストアをリセットしてアプリのコアデータを「更新」し、すべてのビューとfetchedResultsControllersを更新します。

これがうまく機能していることを証明できます。プライマリデバイス(データが保持されている場所)と数か月間通信していないデバイス(isSource = NO)からデータを消去しました。次に、プライマリデバイスからデータをプッシュし、すべてのデータが数秒以内に表示されるのを楽しみに見ました。

繰り返しになりますが、iCloudとの同期に問題があったすべての人にこれを参照して共有してください。

元の質問への回答。iOS5.1がリリースされた後は影響を受けなくなり、設定でアプリのiCloudストレージを削除した後のクラッシュが修正されました

これを整理するために何でも何時間も試した後、新しいアプリIDを作成し、アプリに関連付けられたプロビジョニングプロファイルを更新し、新しいプロファイルに一致するようにiCloudコンテナフィールドを変更すると、すべてが再び機能します。なぜこれが起こったのかまだわかりませんが、そのアプリIDに関連付けられているiCloudストレージが破損しているようです。

つまり、これが他の誰かに起こった場合は、次の手順に従ってください。

  1. プロビジョニングポータルで新しいアプリIDを作成します。
  2. アプリに関連付けられているプロビジョニングプロファイルを見つけます。 [編集]-> [変更]をクリックし、アプリIDを作成したものに変更します。
  3. 変更を送信してから、Xcodeの既存のプロファイルを作成したプロファイルに置き換えます。
  4. <bundleIdentifier>のすべてのインスタンスを新しいアプリIDに合うように変更します(これらはメインアプリの概要ページ、iCloudコンテナとiCloud Key-Valueストアの資格、および永続ストアを作成しているAppDelegateファイルにあります、上記の私のコードのように)。
  5. プロビジョニングプロファイルに関する情報を変更したため、Xcodeを再起動します(それ以外の場合は文句を言い、デバイスでの実行を拒否します)。
  6. アプリをインストールするデバイスに新しいプロファイルがあることを確認してから、ビルドして実行します。この時点では、すべてが正常に機能するはずです。
13
justin

別の説明:iOS6.0.1と6.1ベータ2でデバイスをテストしているときに同様の状況が発生しました。

@Slevが述べているように、iOS5.1では完全には修正されていません。 1つのデバイスがiCloudの永続ストアにアクセスしようとすると、約80秒間完全にフリーズしますが、実際にはそこに保存されている情報にアクセスすることはありません。

これは、デバイスのOSのログファイルが破損していることが原因だと思います。デバイス上のアプリまたはiCloudデータを削除しても、フリーズ/ iCloudストアにアクセスできない問題は修正されませんでした。

私が見つけた1つの修正は、reset all settings(すべてのコンテンツの消去も機能します)デバイス上。 settings->general->reset.

そうして初めて、そのデバイス上のアプリでiCloud上のデータに再びアクセスできました。これが、非常に苛立たしいバグの解決策を探してここに来た他の人の助けになることを願っています。

7
Eclectic DNA

5.0.1ベータ1で1つのデバイスを使用し、5.0.0で1つのデバイスを使用しているときに、このエラーが発生しました。

ICloudコンテナの名前を変更してエラーを取り除きました。 iCloudコンテナリストでは、最初のコンテナはアプリIDと一致する必要がありますが、異なる名前のコンテナを追加できます。

(アプリIDを変更するslevのソリューションと同様に、これはアプリがまだリリースされていない場合にのみ適切なソリューションです。)

3
jlstrecker

更新:

誰もが本当にWWDC2012のiCloudCore Dataセッション227を見る必要があります。彼らが提供するソースコードは、iCloudベースのソリューションのexcellent出発点です。 。本当に時間をかけて彼らがしていることを経験してください。あるストアから別のストアにオブジェクトをコピーしたり、重複排除したりするなど、いくつかの穴を埋める必要があります。そうは言っても、ローカルストアとiCloudストアの間を移動するために以下で説明するmigratePersistentStoreアプローチを使用しなくなりました。


私の元の答え:

Slevは、ストアをローカルコピーからiCloudに移行し、また元に戻すためのコードを投稿するように私に依頼しました。 このコードは実験的なものであり、本番環境では使用しないでください。ここでは、共有および移動するための参照としてのみ提供されています。フォワード。 Appleが適切なリファレンスアプリケーションをリリースしたら、パターンとプラクティスについてはおそらくそれを参照する必要があります。

-(void) onChangeiCloudSync
{
    YourAppDelegate* appDelegate = (YourAppDelegate*) [[UIApplication sharedApplication] delegate];
    NSFileManager *fileManager = [NSFileManager defaultManager];

    if ([iCloudUtility iCloudEnabled])
    {
        NSURL *storeUrl = [[appDelegate applicationDocumentsDirectory] URLByAppendingPathComponent:@"YourApp2.sqlite"];
        NSURL *cloudURL = [fileManager URLForUbiquityContainerIdentifier:nil];
        NSString* coreDataCloudContent = [[cloudURL path] stringByAppendingPathComponent:@"data"];
        cloudURL = [NSURL fileURLWithPath:coreDataCloudContent];

        //  The API to turn on Core Data iCloud support here.
        NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys:@"com.yourcompany.yourapp.coredata", NSPersistentStoreUbiquitousContentNameKey, cloudURL, NSPersistentStoreUbiquitousContentURLKey, [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,nil];

        NSPersistentStore* store = [appDelegate.persistentStoreCoordinator.persistentStores objectAtIndex:0];
        NSError* error;
        if (![appDelegate.persistentStoreCoordinator migratePersistentStore:store toURL:storeUrl options:options withType:NSSQLiteStoreType error:&error])
        {
            NSLog(@"Error migrating data: %@, %@", error, [error userInfo]);
            //abort();
        }
        [fileManager removeItemAtURL:[[appDelegate applicationDocumentsDirectory] URLByAppendingPathComponent:@"YourApp.sqlite"] error:nil];
        [appDelegate resetStore];
    }
    else
    {
        NSURL *storeUrl = [[appDelegate applicationDocumentsDirectory] URLByAppendingPathComponent:@"YourApp.sqlite"];

        //  The API to turn on Core Data iCloud support here.
        NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                                 [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                                 [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
                                 nil];

        NSPersistentStore* store = [appDelegate.persistentStoreCoordinator.persistentStores objectAtIndex:0];
        NSError* error;
        if (![appDelegate.persistentStoreCoordinator migratePersistentStore:store toURL:storeUrl options:options withType:NSSQLiteStoreType error:&error])
        {
            NSLog(@"Error migrating data: %@, %@", error, [error userInfo]);
            //abort();
        }
        [fileManager removeItemAtURL:[[appDelegate applicationDocumentsDirectory] URLByAppendingPathComponent:@"YourApp2.sqlite"] error:nil];
        [appDelegate resetStore];
    }
}
0
kurtzmarc

すみません、Slevさん、アプリがクラッシュしていると言ったときと同じ問題が発生します。これは、iCloudデータがデバイスにすぐにマージされようとするためです(デバイスがまだ永続ストアを設定していない場合)が、これをどのように解決したのかわかりません。

あなたのコードは:

- (void)mergeChangesFrom_iCloud:(NSNotification *)notification {
    if (self.unlocked) {
       NSManagedObjectContext *moc = [self managedObjectContext];
        [moc performBlock:^{
            [self mergeiCloudChanges:notification forContext:moc];
        }];
    }
}

私はまだ試していませんが、このコードを見ると、「ロック解除」がfalseの場合に到着する通知はどうなるのでしょうか。あなたはそれらを失いますか?

「ロック解除された」プロパティをテストし、プロパティがtrueになるまでしばらく時間を費やすwhileループを作成する方がよいのではないでしょうか。

私の非常に悪い英語を理解してくれることを願っています... :)ありがとう
デイブ

0
mastro35