さまざまなシフト ローテーションを保存して表示するカレンダー アプリを開発しています。最近の原因不明のエラーが奇妙にも X-Code の ver. 4.5.2. Google や Stackoveflow のどこにも同様の問題は見つかりませんでした。
シフトの回転は、ユーザーによって設定および保存されます。シフト ローテーションを作成するプロセスでは、ユーザーに日付の範囲を提示し、ユーザーは各日付を特定のシフトに関連付けます。
保存されたデータを管理するために、sqliteストアでCore Dataを使用しています。上記の要素のオブジェクト グラフには、シフトの詳細 (タイトル、開始時刻と終了時刻、最も重要な日付など) を含む複数の "Shift" NSManagedObject エンティティが含まれ、後でシフト ローテーションを表示できます。「Shift」エンティティは、「Shifts」と多対 1 の関係を持つ「ShiftRotation」と呼ばれる別の NSManagedObject によって一緒に保持されます。
これらの「Shift」オブジェクトの使用方法にほとんど変更を加えなかった最近まで、私のアプリは適切に機能していました。
私の問題はこれです。ユーザーは「シフト」を日付に関連付けます。日付は、「Shift」NSManagedObject 内の「date」プロパティ内に保存されます。コンテキストが保存されます。後で「Shift」にアクセスすると、何も変更せずに「Shift」に割り当てられた日付が変更されて表示されます。
コードを 1 行ずつ調べ、多数の NSLog を追加して、どこで、またはなぜ変更されているかを確認しましたが、結果はありませんでした。保存が行われているクラス内でコンテキストを保存する直前と直後の日付に変更がないことに気付きました。カレンダーのメイン画面からアクセスしても変化はありません。ただし、後ですべてのシフトローテーションを管理する別の画面から「シフト」を取得すると、変更されたように見えます。
日付の変更は、約 31 ~ 32 日後です。これは Core Data のバグなのか、何かが足りないのではないかと思わずにはいられません。
sqlite 永続ストアから日付にアクセスすると、重大で予測不可能な日付の変更が発生する理由や説明はありますか?
更新: NSNotificationCenter を使用して、何が起こっているのか、どこで起こっているのかをさらに絞り込もうとしました。
iOS6を使用しています。
管理対象オブジェクト コンテキストの変更を監視するために、ルート ビュー コントローラーを登録しました。特に、オブジェクトへの変更 (つまり、NSManagedObjectContextObjectDidChange)。
「Shift」NSManagedObject のプロパティを更新した後、「Shift」オブジェクトが更新されたことを示す通知を受け取りました。ここにいくつかのコードとログがあります。
これは、「Shift」エンティティが作成され、日付が指定される場所です。
- (NSMutableArray *)arrayWithDateStartingAtDate:(NSDate *)anyDate forNumberOfDays:(NSUInteger)days;
{
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSMutableArray *monthArray = [[NSMutableArray alloc] init];
NSDateComponents *comps = [[NSDateComponents alloc] init];
NSDate *tempDate;
NSManagedObjectContext *moc = [[CSEventStore sharedStore] managedObjectContext];
for (NSUInteger i = 0; i < days; i++) {
[comps setDay:i];
tempDate = [gregorian dateByAddingComponents:comps toDate:anyDate options:0];
Shift *shiftRDO = [Shift initShiftEntityRDOWithDate:tempDate InContext:moc];
[shiftRDO setShiftRotation:_shiftRotation];
NSMutableDictionary *dateDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:tempDate, @"date", shiftRDO, @"shift", nil];
[monthArray addObject:dateDict];
}
return monthArray;
}
これは、それを更新するための呼び出しが行われる場所です。
[shiftEntity updateWithShiftType:_selectedShiftType inContext:context];
メソッドを呼び出すもの:
- (void)updateWithShiftType:(ShiftType *)shiftType inContext:(NSManagedObjectContext *)context
{
self.title = [NSString stringWithFormat:@"%@",shiftType.title];
self.symbol = [NSString stringWithFormat:@"%@",shiftType.symbol];
self.startTime = [shiftType.startTime dateByAddingTimeInterval:0];
self.endTime = [shiftType.endTime dateByAddingTimeInterval:0];
self.splitStartTime = [shiftType.splitStartTime dateByAddingTimeInterval:0];
self.splitEndTime = [shiftType.splitEndTime dateByAddingTimeInterval:0];
self.isWorkday = [shiftType.isWorkday copy];
self.isTimeOff = [shiftType.isTimeOff copy];
self.typeID = [shiftType typeID];
}
上記のコードが実行された後、最初の通知がログに投稿されます。
<Shift: 0x8140b70> (entity: Shift; id: 0x81409e0 <x-coredata:///Shift/tE59A199A-444E-4079-BD8C-0D3E734607783> ; data: {
date = "2012-10-29 04:00:00 +0000";
endTime = "2012-11-02 19:35:38 +0000";
isTimeOff = 0;
isWorkday = 1;
shiftRotation = "0x81406a0 <x-coredata:///ShiftRotation/tE59A199A-444E-4079-BD8C-0D3E734607782>";
splitEndTime = nil;
splitStartTime = nil;
startTime = "2012-11-02 09:35:34 +0000";
symbol = none;
title = Days;
typeID = "991ACC8C-ECE0-4555-9002-7AC233F26CBF";
" date " プロパティには適切な日付が割り当てられています。
次の方法でコンテキストを保存するとすぐに:
- (BOOL)saveContext
{
NSError *error = nil;
if (__managedObjectContext) {
if ([__managedObjectContext hasChanges] && ![__managedObjectContext save:&error]) {
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
// More details on error
NSArray* detailedErrors = [error userInfo][NSDetailedErrorsKey];
if(detailedErrors != nil && [detailedErrors count] > 0) {
for(NSError* detailedError in detailedErrors) {
NSLog(@" DetailedError: %@", [detailedError userInfo]);
}
}
else {
NSLog(@" %@", [error userInfo]);
}
return NO;
abort();
}
}
return YES;
}
次のログで変更の 2 番目の通知を受け取ります。
<Shift: 0x8140b70> (entity: Shift; id: 0x82c9dd0 <x-coredata://7CE834F9-5056-457A-BB2C-B8BA638086F1/Shift/p23> ; data: {
date = "2012-12-02 05:00:00 +0000";
endTime = "2012-11-02 19:35:38 +0000";
isTimeOff = 0;
isWorkday = 1;
shiftRotation = "0x8260dc0 <x-coredata://7CE834F9-5056-457A-BB2C-B8BA638086F1/ShiftRotation/p11>";
splitEndTime = nil;
splitStartTime = nil;
startTime = "2012-11-02 09:35:34 +0000";
symbol = none;
title = Days;
typeID = "991ACC8C-ECE0-4555-9002-7AC233F26CBF";
}),
お気づきのように、保存しただけで「日付」が 2012-12-02 に変更されました。
更新:説明のつかない変化の原因をついに追跡することができました。コード内の別の場所にコンテキストを保存した後、誤って「日付」プロパティを変更していたようです。おそらく、私が carmin の慣習に従っていれば、それは起こらなかったでしょう (評判がよければ +1 を与えたでしょう)。結局、値は永続ストアで変更されていませんでした。
おそらく、私がこのバグをどのように追跡したかを共有することは、必ずしも Core Data に関連するものではなく、同様の問題を抱えている可能性のある他の人に役立つかもしれません.
「日付」プロパティを観察するために、キー値観察を使用しました。「Shift」エンティティを作成した直後に観察を開始しました。
[_shiftEntity addObserver:self forKeyPath:@"date" options:NSKeyValueObservingOptionNew context:NULL];
...そして、変更が既に発生したコード内のポイントの後に観察を停止しました:
[_shiftEntity removeObserver:self forKeyPath:@"date" context:NULL];
次に、KVO メソッドを実装しました。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(@"Object being observed: %@", [object description]);
NSLog(@"Value changed to: %@", change[NSKeyValueChangeNewKey]);
}
上記のメソッドにブレークポイントを配置しました。変更が発生すると、この時点で停止し、何が変更されたかを教えてくれました。最も重要なことは、コードの実行が停止したときに、コール スタックを調べて、変更の原因となったコードのパスをたどったことです。コードが変更を加えている場所を正確に見つけることができました。
私は KVO の使用についてあまり知識がありませんが、この経験から、デバッグ目的でも KVO が役立つことがわかりました。