iOS 应用的存档数据无法正确加载

3

我对这个还不是很熟悉,请耐心等待。

我使用《iOS编程指南:大牛们的寻宝之旅》第四版构建了一个名为Homepwner的应用程序,该应用程序利用归档在应用程序进入后台时存储信息,并在再次启动时加载信息,效果非常好。我决定为自己的名为SBL的应用程序实现相同的策略,但是当应用程序在退出后重新加载时,数据似乎已经加载但不能正确显示。

该应用程序在表格中显示“CCIEvent”的数组,它们看起来像这样:

7:00 AM: 湿和脏尿布

8:48 AM: 喂养-两个-20分钟

9:48 AM: 小睡-1小时0分钟

但是当我退出应用程序并重新启动时,列表会像这样拉起(我必须将其标识为代码才能发布此帖子,但下面的信息会在我的表格上屏幕上显示):

<CCIEvent: 0x154e44e20>
<CCIEvent: 0x154e2c7b0>
<CCIEvent: 0x154e77b90>

我很不确定该看哪里,但以下是与保存/加载相关的所有代码(我想):

在CCIEvent.m文件中:

- (void)encodeWithCoder:(NSCoder *)aCoder
{

[aCoder encodeObject:self.startTime forKey:@"startTime"];
[aCoder encodeObject:self.bedTime forKey:@"bedTime"];
[aCoder encodeObject:self.wakeTime forKey:@"wakeTime"];
[aCoder encodeBool:self.isNap forKey:@"isNap"];
[aCoder encodeInt:self.feedDuration forKey:@"feedDuration"];
[aCoder encodeInt:self.sourceIndex forKey:@"sourceIndex"];
[aCoder encodeInt:self.diaperIndex forKey:@"diaperIndex"];
[aCoder encodeObject:self.note forKey:@"note"];

}

- (instancetype)initWithCoder:(NSCoder *)aDecoder
{

self = [super init];

if (self) {
    _startTime = [aDecoder decodeObjectForKey:@"startTime"];
    _bedTime = [aDecoder decodeObjectForKey:@"bedTime"];
    _wakeTime = [aDecoder decodeObjectForKey:@"wakeTime"];
    _isNap = [aDecoder decodeBoolForKey:@"isNap"];
    _feedDuration = [aDecoder decodeIntForKey:@"feedDuration"];
    _sourceIndex = [aDecoder decodeIntForKey:@"sourceIndex"];
    _diaperIndex = [aDecoder decodeIntForKey:@"diaperIndex"];
    _note = [aDecoder decodeObjectForKey:@"note"];
}
return self;

}

在CCIEventStore.m中。
- (instancetype)initPrivate
{

self = [super init];

if (self) {

    NSString *path = [self eventArchivePath];
    _privateEvents = [NSKeyedUnarchiver unarchiveObjectWithFile:path];

    // If the array hadn't been saved previously, create an empty one
    if (!_privateEvents) {
        _privateEvents = [[NSMutableArray alloc] init];
        NSLog(@"Did NOT load events");
    } else {
        NSLog(@"Loaded events");
    }

}
return self;
}

- (CCIEvent *)createEventWithEventType:(NSString *)eventType
                         startTime:(NSDate *)startTime
                           bedTime:(NSDate *)bedTime
                          wakeTime:(NSDate *)wakeTime
                             isNap:(BOOL)isNap
                      feedDuration:(int)feedDuration
                       sourceIndex:(int)sourceIndex
                       diaperIndex:(int)diaperIndex
                              note:(NSString *)note
{

CCIEvent *event = [[CCIEvent alloc] initWithEventType:eventType
                                            startTime:startTime
                                              bedTime:bedTime
                                             wakeTime:wakeTime
                                                isNap:isNap
                                         feedDuration:feedDuration
                                          sourceIndex:sourceIndex
                                          diaperIndex:diaperIndex
                                                 note:note];
[self.privateEvents addObject:event];
return event;

}

- (NSString *)eventArchivePath
{

NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

NSString *documentDirectory = [documentDirectories firstObject];

return [documentDirectory stringByAppendingPathComponent:@"events.archive"];

}

- (BOOL)saveChanges
{

NSString *path = [self eventArchivePath];

return [NSKeyedArchiver archiveRootObject:self.privateEvents
                                   toFile:path];

}

在AppDelegate.m文件中

- (void)applicationDidEnterBackground:(UIApplication *)application {


BOOL success = [[CCIEventStore sharedStore] saveChanges];

if (success) {
    NSLog(@"Saved all of the CCIEvents");
} else {
    NSLog(@"Could not save any of the CCIEvents");
}

}

这是我称之为CCILogViewController.m的视图控制器中包含表格的一些代码:

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setTimeStyle:NSDateFormatterNoStyle];
    [dateFormatter setDateStyle:NSDateFormatterShortStyle];

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"
                                                            forIndexPath:indexPath];

    CCIEvent *event = self.sortedAndFilteredArray[indexPath.row];

    if ([event.eventType isEqualToString:@"sleepEvent"] && event.wakeTime) {

        NSDateComponents *dateComponentsToday = [[NSCalendar currentCalendar] components:NSCalendarUnitYear fromDate:[NSDate date]];
        NSInteger yearToday = [dateComponentsToday year];

        NSInteger dayOfYearToday = [[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitDay
                                                                           inUnit:NSCalendarUnitYear
                                                                          forDate:[NSDate date]];

        NSDateComponents *dateComponentsEvent = [[NSCalendar currentCalendar] components:NSCalendarUnitYear
                                                                                fromDate:event.startTime];
        NSInteger yearEvent = [dateComponentsEvent year];

        NSInteger dayOfYearEvent = [[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitDay
                                                                           inUnit:NSCalendarUnitYear
                                                                          forDate:event.startTime];

        NSInteger dayOfYearWakeEvent;

        dayOfYearWakeEvent = [[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitDay
                                                                     inUnit:NSCalendarUnitYear
                                                                    forDate:event.wakeTime];
        if (event.wakeTime && yearEvent == yearToday && dayOfYearEvent == dayOfYearToday - 1 && dayOfYearWakeEvent == dayOfYearToday) {
            cell.textLabel.text = [NSString stringWithFormat:@"%@ %@", [dateFormatter stringFromDate:event.startTime], [self.sortedAndFilteredArray[indexPath.row] description]];
        } else if (event.wakeTime && yearEvent == yearToday - 1 && dayOfYearEvent == dayOfYearToday - 1 && dayOfYearEvent == 1 && dayOfYearWakeEvent == dayOfYearToday) {
            cell.textLabel.text = [NSString stringWithFormat:@"%@ %@", [dateFormatter stringFromDate:event.startTime], [self.sortedAndFilteredArray[indexPath.row] description]];
        } else {
            cell.textLabel.text = [self.sortedAndFilteredArray[indexPath.row] description];
        }

    } else if (self.dateOptionIndex == 2 || self.dateOptionIndex == 3) {
        cell.textLabel.text = [NSString stringWithFormat:@"%@ %@", [dateFormatter stringFromDate:event.startTime], [self.sortedAndFilteredArray[indexPath.row] description]];
    } else {

        cell.textLabel.text = [self.sortedAndFilteredArray[indexPath.row] description];
    }

    if (event.note) {
        cell.accessoryType = UITableViewCellAccessoryDetailButton;
    } else {
        cell.accessoryType = UITableViewCellAccessoryNone;
    }

    return cell;

}

以下是我获取的sortedAndFilteredArray的方法,它被称为在多个地方包括viewWillAppear中:
- (void)sortAndFilterArray
{

    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"startTime" ascending:self.arrayIsAscending];
    NSArray *sortedArray = [[[CCIEventStore sharedStore] allEvents] sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];

    NSMutableArray *sortedAndFilteredArray = [[NSMutableArray alloc] init];
    NSMutableArray *finalArray = [[NSMutableArray alloc] init];

    for (CCIEvent *event in sortedArray) {

        switch (self.eventOptionIndex) {
            case 1:
                if ([event.eventType isEqualToString:@"sleepEvent"]) {
                    [sortedAndFilteredArray addObject:event];
                }
                break;
            case 2:
                if ([event.eventType isEqualToString:@"feedEvent"]) {
                    [sortedAndFilteredArray addObject:event];
                }
                break;
            case 3:
                if ([event.eventType isEqualToString:@"diaperEvent"]) {
                    [sortedAndFilteredArray addObject:event];
                }
                break;
            default:
                [sortedAndFilteredArray addObject:event];
                break;

        }

    }

    NSDateComponents *dateComponentsToday = [[NSCalendar currentCalendar] components:NSCalendarUnitYear fromDate:[NSDate date]];
    NSInteger yearToday = [dateComponentsToday year];

    NSInteger dayOfYearToday = [[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitDay
                                                                        inUnit:NSCalendarUnitYear
                                                                       forDate:[NSDate date]];

    for (CCIEvent *event in sortedAndFilteredArray) {

        NSDateComponents *dateComponentsEvent = [[NSCalendar currentCalendar] components:NSCalendarUnitYear
                                                                      fromDate:event.startTime];
        NSInteger yearEvent = [dateComponentsEvent year];

        NSInteger dayOfYearEvent = [[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitDay
                                                                            inUnit:NSCalendarUnitYear
                                                                           forDate:event.startTime];

        NSInteger dayOfYearWakeEvent;

        switch (self.dateOptionIndex) {
            case 0:

                dayOfYearWakeEvent = [[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitDay
                                                                             inUnit:NSCalendarUnitYear
                                                                            forDate:event.wakeTime];

                // Filter here for dateIndex 0 (Today)
                if ([event.eventType isEqualToString:@"sleepEvent"] && event.wakeTime && yearEvent == yearToday && dayOfYearEvent == dayOfYearToday - 1 && dayOfYearWakeEvent == dayOfYearToday) {
                    [finalArray addObject:event];
                } else if ([event.eventType isEqualToString:@"sleepEvent"] && event.wakeTime && yearEvent == yearToday - 1 && dayOfYearEvent == dayOfYearToday - 1 && dayOfYearEvent == 1 && dayOfYearWakeEvent == dayOfYearToday) {
                    [finalArray addObject:event];
                } else if (yearEvent == yearToday && dayOfYearEvent == dayOfYearToday) {
                    [finalArray addObject:event];
                }
                break;
            case 1:
                // Filter here for dateIndex 1 (Yesterday)
                if (yearEvent == yearToday && dayOfYearEvent == dayOfYearToday - 1) {
                    [finalArray addObject:event];
                } else if (yearEvent == yearToday - 1 && dayOfYearEvent == dayOfYearToday - 1 && dayOfYearEvent == 1) {
                    [finalArray addObject:event];
                }
                break;
            case 2:
                // Filter here for dateIndex 2 (Past Week)
                if (yearEvent == yearToday && dayOfYearEvent >= dayOfYearEvent - 6) {
                    [finalArray addObject:event];
                } else if (yearEvent == yearToday - 1 && dayOfYearEvent >= dayOfYearToday - 6 && dayOfYearEvent < 7) {
                    [finalArray addObject:event];
                }
                break;
            default:
                // No filter here for dateIndex 3 (All Time)
                [finalArray addObject:event];
                break;

        }

    }

    self.sortedAndFilteredArray = [finalArray copy];

}

获取CCIEvent描述时所调用的内容如下:

- (NSString *)description
{

    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setTimeStyle:NSDateFormatterShortStyle];
    [dateFormatter setDateStyle:NSDateFormatterNoStyle];

    if ([self.eventType isEqualToString:@"sleepEvent"]) {

        NSString *bedTimeString = [dateFormatter stringFromDate:self.bedTime];
        NSString *wakeTimeString = [dateFormatter stringFromDate:self.wakeTime];

        long min = self.sleepDuration/60;
        long hrs = self.sleepDuration/60/60;
        long rMinutes = min - hrs * 60;

        if (!self.wakeTime && self.bedTime) {

            if (self.isNap) {
                return [[NSString alloc] initWithFormat:@"%@: Went Down for Nap", bedTimeString];

            } else {
                return [[NSString alloc] initWithFormat:@"%@: Went Down", bedTimeString];

            }

        } else if (self.wakeTime && !self.bedTime) {

            if (self.isNap) {
                return [[NSString alloc] initWithFormat:@"%@: Woke from Nap", wakeTimeString];
            } else {
                return [[NSString alloc] initWithFormat:@"%@: Woke", wakeTimeString];
            }

        } else if (self.sleepDuration <= 60) {

            if (self.isNap) {
                return [[NSString alloc] initWithFormat:@"%@: Napped - 1 min", bedTimeString];
            } else {
                return [[NSString alloc] initWithFormat:@"%@: Slept - 1 min", bedTimeString];
            }

        } else if (self.sleepDuration > 60 && self.sleepDuration < 60 * 60){

            if (self.isNap) {
                return [[NSString alloc] initWithFormat:@"%@: Napped - %ld min", bedTimeString, min];
            } else {
                return [[NSString alloc] initWithFormat:@"%@: Slept - %ld min", bedTimeString, min];
            }
        } else if (hrs == 1) {

            if (self.isNap) {
                return [[NSString alloc] initWithFormat:@"%@: Napped - 1 hr %ld min", bedTimeString, rMinutes];
            } else {
                return [[NSString alloc] initWithFormat:@"%@: Slept - 1 hr %ld min", bedTimeString, rMinutes];
            }
        } else {

            if (self.isNap) {
                return [[NSString alloc] initWithFormat:@"%@: Napped - %ld hrs %ld min", bedTimeString, hrs, rMinutes];
            } else {
                return [[NSString alloc] initWithFormat:@"%@: Slept - %ld hrs %ld min", bedTimeString, hrs, rMinutes];
            }
        }

    } else if ([self.eventType isEqualToString:@"feedEvent"]) {

        NSString *startTimeString = [dateFormatter stringFromDate:self.startTime];
        NSArray *sourceArray = @[@"Both", @"Left", @"Right", @"Bottle"];
        NSString *sourceString = sourceArray[self.sourceIndex];

        return [[NSString alloc] initWithFormat:@"%@: Fed - %@ - %d min", startTimeString, sourceString, self.feedDuration / 60];

    } else if ([self.eventType isEqualToString:@"diaperEvent"]) {

        NSString *startTimeString = [dateFormatter stringFromDate:self.startTime];
        NSArray *diaperArray = @[@"Wet and Dirty", @"Wet", @"Dirty"];
        NSString *diaperString = diaperArray[self.diaperIndex];

        return [[NSString alloc] initWithFormat:@"%@: %@ Diaper", startTimeString, diaperString];
    }

    return [super description];

}

你可以发布一下你的方法 tableView:cellForRowAtIndexPath 吗? - 3vangelos
已添加。感谢您查看此内容。 - Andrew Boza
看起来你的tableView总是最终设置了这个cell:cell.textLabel.text = [self.sortedAndFilteredArray[indexPath.row] description]; - 3vangelos
是的,我开始这个项目已经有一段时间了,但我相信那是正确的。你认为那会导致问题吗? - Andrew Boza
2个回答

0

在CCIEvent中是否有自定义的描述方法?默认情况下,NSObject description会显示对象的内存位置,这正是您所看到的。您可以通过创建非常简单的内容来进行基本测试,例如:

- (NSString *)description
{
    return [[NSString alloc] initWithFormat:@"%@", self.note];
}

似乎仍然有某些地方没有加载,否则我会期望您在一般使用过程中看到该问题。如果您确实有一个 CCIEvent description 方法,请发布它。我也对 sortedAndFilteredArray 感到好奇。

您可以尝试在代码中添加 NSLog() 调用,以查看您在各个点上是否得到了预期的结果。当应用程序运行时,请注意 XCode 控制台以查看您获取的数据。例如,在 CCIEvent.m 的 initWithCoder 中,您可以在 if (self) { 块的末尾添加以下内容:

NSLog(@"Got %@", _note);

甚至可以在那里检查您的描述方法:

NSLog([self description]);

还有,请确保在您的encodeWithCoder方法中加入一些NSLog。我怀疑问题出现在保存端 - 有些东西阻止事件详情正确保存,所以当它们被恢复时它们是空的,因此在您的cellForRowAtIndexPath方法中,它会命中默认条件并只调用CCIEvent的description,我怀疑CCIEvent可能不存在,并且会显示内存位置的默认行为。如果我是对的,这就指向CCIEventStore的privateEvents和在详细视图中正在编辑的事件之间存在断连。请检查您头文件中的对象属性,并查看是否在某处使用copy而不是strong传递CCIEvent。

与错误无关,但仍值得注意的是-在cellForRowAtIndexPath:中的项:

cell.textLabel.text = [self.sortedAndFilteredArray[indexPath.row] description];

这些代码行可能与问题无关,但它们包含了一个冗余的访问器。因为你已经有:

CCIEvent *event = self.sortedAndFilteredArray[indexPath.row];

任何时候您想要从事件中访问数据,都应该使用该变量,因此上述的textLabel将是:
cell.textLabel.text = [event description];

cellForRowAtIndexPath中还有一些其他地方也可以使用类似的修改。

希望这能帮到你!


迫不及待地想回家并研究这个问题。非常感谢您的尝试帮助。如果有解决方案,我会告诉您的。 - Andrew Boza

0
我在代码中撒了一些 NSLog 并确定我的 CCIEvents 加载得很好。我觉得奇怪的是,在加载之前,我的特殊描述能够正常工作,但在加载之后却不能。加载之前,它会给我想要的字符串,但加载之后它会指向内存。在检查我的描述方法时,我注意到除非 CCIEvent 满足某些条件,否则我会默认返回超类的描述方法,这迫使我意识到我的 CCIEvents 拥有一个 eventType 属性,而我没有在 encodeWithCoder: 或 initWithCoder: 块中包含它。我只需要将 eventType 添加进去,我的描述就能正常工作了。非常感谢你,Chris!因为我被困住了,所以我实际上把这个项目放置了几个月。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接