5

Core Data アプリに iCloud 同期を実装しようとしています。私はプログラミングのプロではなく、これは実際に私が学んだ高度なトピックです... Drew McCormack による Core Data sync Framework "Ensembles" を見つけました。これにより、iCloud 同期がはるかに簡単になるようです。

アプリに統合しましたが、新しいオブジェクトを Core Data モデルに追加する限り、同期はうまく機能します。しかし、オブジェクトを削除すると、重複が作成されます。そして、重複からの重複。3〜4回のように同じエントリ(オブジェクト)を持つことになりました...

何故ですか?私は何を間違っていますか?私はいくつかの調査を行いましたが、グローバル識別子がこれを解決できると思いますか?

グローバル識別子とは何ですか? 私の推測では、重複を避けるのに役立つと思います!? しかし、どうすればこれを設定できますか?よくわからないので、いろいろ調べましたが、答えが見つかりませんでした。

手伝ってくれてありがとう!

更新: 助けてくれてありがとう! 私は readme と本を読みましたが、私は初心者なので、すべてがはっきりしているわけではありません。

Ensembles でのグローバル識別子の使用については理解できたと思いますが、それが正しく行われているかどうかはわかりません。

正しく理解できれば、各オブジェクトに識別子を割り当てる必要があります。属性に格納することでこれを行うことができます。この識別子は、一意であり、NSString である限り、何でもかまいません。

私のアプリでは、ユーザーは名前、テキスト、タイトル、日付など、さまざまなものを保存できます。このアプリは、Xcode の Master-Detail-View テンプレートに基づいており、Core Data を使用します。私の Core Data モデルには、いくつかの属性を持つエンティティが 1 つしかなく、ほとんどが文字列と NSDate です。関係も何もありません。ユーザーが「+」を押すと、新しいオブジェクトが作成され、ユーザーが入力したものを属性に保存します。

グローバル識別子を追加するために行ったことは、それを格納する新しい属性を追加することです。だから、新しいオブジェクトが作成されたとき、私はそうします

/// I did find that to use as identifier !?

NSString *taskUniqueStringKey = newManagedObject.objectID.URIRepresentation.absoluteString;

/// and store it in the attribute.

[newManagedObject setValue:taskUniqueStringKey forKey:@"coreDataObjectID"]; 

それから私はこれを使用します:

- (NSArray *)persistentStoreEnsemble:(CDEPersistentStoreEnsemble *)ensemble globalIdentifiersForManagedObjects:(NSArray *)objects
{

return [objects valueForKeyPath:@"coreDataObjectID"];;

}

これは私にとってはうまくいくようです。しかし、私はそれを正しくやっていますか?これは、グローバル識別子を割り当てる適切な場所ですか? 私は awakeFromInsert を持っていません!?

これが機能する場合、次の問題が発生しました。私のアプリは既に稼働しており、更新前にユーザーが保存した古いエントリにはグローバル識別子がありません。それについて私は何ができますか?私がすでに持っているものとユニークなものと私が考えることができる唯一のものは、オブジェクトが作成されたときに [NSDate date] を保存する属性だと思いました。

これを使用しようとしましたが、Ensembles は NSString のみを受け入れ、NSDate を受け入れないため失敗しました!? この日付属性を使用できますか?これは十分に一意であり、gloabl 識別子として機能しますか? はいの場合、これを日付から文字列に変換する方法のコード例を教えてください。

Ensembles との同期は非常にうまく機能します。重複はもうありません。iCloud をオフにするだけでエントリが保持され、再度オンにすると、ローカルに保存されたオブジェクトなどを失うことなく、本来のように同期されます。アンサンブルは本当にかっこいいです!同期に時間がかかることもあれば、非常に速いこともあり、2 つの異なるデバイスで短期間に編集すると、削除したばかりのオブジェクトが再び表示されるように少し混乱するなど、いくつかのマイナーな奇妙な動作が見られます。でも、それが普通だと思いませんか?異なるデバイスでアプリを使用するまでに時間がかかると、すべて正常に動作します。

私はそれを正しく理解していますか?同期のために呼び出すメソッドは1つだけです:

- (void)syncWithCompletion:(void(^)(void))completion
{
if (self.ensemble.isMerging) return;


if (!self.ensemble.isLeeched) {
    [self.ensemble leechPersistentStoreWithCompletion:^(NSError *error) {
        if (error) NSLog(@"Error in leech: %@", error);

        if (completion) completion();
    }];
}
else {
    [self.ensemble mergeWithCompletion:^(NSError *error) {

        if (completion) completion();
    }];
}

必要に応じて呼び出すだけですか?前にリーチせずにマージを行う、または「これが実際のステータスです。今のように保存します」のような方法は他にありませんか?

アプリには、同期したいさまざまなポイントがあります。アプリの開始時と終了時が良い点になります。私のアプリでは、オブジェクトを追加して Core Data に保存するときと、オブジェクトへの変更を保存するときの 2 つの時点で同期する必要があると思います。「今すぐ同期」などのボタンを提供することもできます。これは良いアプローチですか、私はいつも電話するだけですか

[self syncWithCompletion:NULL];

出てきた別の質問。アンサンブルとの同期からオブジェクトを除外できますか? 私のアプリは、最初のアプリの起動時にチュートリアル エントリをオブジェクトとして 1 回読み込みます。どうにかしてそれが可能な場合、それらを同期したくありませんか?

助けてくれてどうもありがとう!ドイツ語のローカライズなどでお手伝いできることがあれば、お知らせください。;)

4

2 に答える 2

6

はい、これはほぼ確実に、オブジェクトのグローバル識別子を設定していないか、少なくとも適切に設定していないことが原因です。

アンサンブルをリーチすると、ローカル永続ストアが同期データにインポートされます。グローバル識別子がない場合、Ensembles はオブジェクトにランダムな ID を割り当てるため、デバイス間でオブジェクトを追跡できます。

同じデータを持つ 2 番目のデバイスをリーチすると、重複が発生します。Ensembles は、データが他のデバイスと同じ論理オブジェクトを表していることを認識する方法がないため、再びランダムな ID を割り当てます。事実上、各デバイス上のオブジェクトは完全に独立しているものとして扱われるため、同期後にすべてのオブジェクトがデータ セットに格納されます。

解決策はグローバル識別子です。デリゲート メソッドを実装することによりCDEPersistentStoreEnsemble、Ensembles にグローバル ID を提供できます。これを使用して、異なるデバイス上のどのオブジェクトが一緒に属しているかを識別できます。

グローバル ID には何を使用する必要がありますか? 多くの場合、UUID だけですが、シングルトンのようなオブジェクトの場合は、ID を選択する必要があります。

で初期化できますawakeFromInsert。エンティティの属性にグローバル ID を保存できます。(既存のアプリを移行する場合は、同期のためにストアをリーチする前に、グローバル ID が生成されているかどうかをフェッチで確認する必要があることに注意してください。)

詳細については、GitHub の READMEと、leanpubの本を参照してください。

アップデート

更新に関する質問に答えるには:

はい、識別子は文字列であり、不変でなければなりません。割り当てられた後は変更しないでください。

NSManagedObjectID は、デバイスによって異なるという点で、あまり優れたグローバル識別子ではありません。デバイス間でグローバルに使えるものを本当に求めています。

ゼロから始める場合は、 を使用することをお勧めしますNSUUID。一意の ID を作成し、オブジェクトに格納するだけです。

既存のアプリがあり、別のメカニズムを介して同期している場合は、各デバイスで同じグローバル識別子を提供する方法を考え出す必要があります。これを行う 1 つの方法は、何らかの方法でオブジェクトのプロパティをマッシュアップすることです。通常、これは非常にユニークに近い値を提供し、移行には十分です。

例として、クイック フェッチを行ったところ、オブジェクトがまだグローバル ID を持っていないことがわかりました。オブジェクトを調べて、グローバル ID を creationDate + テキストで構成される文字列に設定します。(ハッシュを取得することでこれを短縮することもできますが、おそらくそれほど重要ではありません。) グローバル識別子へのこの最初の「移行」の後、新しく作成されたオブジェクトには UUID を使用するだけです。

を使用する必要はありませんawakeFromInsert。それは単に置くのに便利な場所です。オブジェクトを保存する前にグローバル識別子を割り当てる限り、問題はありません。

から文字列を取得する最も簡単な方法NSDateは、 メソッドを呼び出すことですdescriptionが、別の方法は、doubleを使用して を取得しtimeIntervalSince1970、それを文字列に変換することです。(日付はそれ自体が一意の識別子であることに注意してください。多くの場合、一緒に作成されたオブジェクトは同じ作成日になります。)

同期を行う方法については正しいです。単に呼び出すことができますsyncWithCompletion:

オブジェクトの除外に関する質問に答えるには: 個々のオブジェクトを除外することはできません。これは主に、それらのオブジェクトが同期されたオブジェクトと関係を持つ場合に扱いにくくなる可能性があるためです。これらのオブジェクトは、次の 2 つの方法のいずれかで処理できます。

  1. それらを別の永続ストアに配置し、そのストアを同じ永続ストア コーディネーターに追加します。
  2. オブジェクトを同期しますが、オブジェクトが各デバイスで同じように扱われるように、手動でグローバル ID を付与します。例えば。「Sample1」、「Sample2」などのようにグローバル ID を指定できます。
于 2015-01-23T13:48:06.727 に答える
3

ドリューの答えを統合するには、次の2つのステップがあると思います。

1デリゲート メソッドを実装CDEPersistentStoreEnsembleする (README を参照)

- (NSArray *)persistentStoreEnsemble:(CDEPersistentStoreEnsemble *)ensemble 
    globalIdentifiersForManagedObjects:(NSArray *)objects {
    return [objects valueForKeyPath:@"yourUniqueIdentifier"];
}

2NSManagedObjectサブクラスの一意の識別子を生成する

- (void)awakeFromInsert {
    [super awakeFromInsert];

    if (!self.yourUniqueIdentifier) {
        self.yourUniqueIdentifier = [[NSUUID UUID] UUIDString];
    }
}

ではawakeFromInsert、識別子などの特別なデフォルト プロパティ値を初期化できます。

親子コンテキストがある場合などにチェックが必要です。そうしないと、以前に設定した識別子が上書きされます。awakeFromInsert が 2 回呼び出される理由を参照してください。.

于 2015-01-23T14:05:22.833 に答える