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

Posted

技术标签:

【中文标题】iOS 应用程序的存档数据无法正确加载【英文标题】:iOS app's archive data doesn't load properly 【发布时间】:2015-12-12 17:25:52 【问题描述】:

我对此很陌生,所以请耐心等待。

我使用 ios Programming: The Big Nerd Ranch Guide 4th ed 构建了一个名为 Homepwner 的应用程序,它利用归档在应用程序进入后台时存储信息并在它再次启动时加载信息,它工作得很好。我决定为我自己的名为 SBL 的应用程序实施相同的策略,但是当应用程序在退出后加载时,数据似乎已加载但无法正确显示。

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

上午 7:00:尿布又湿又脏

8:48 AM:美联储 - 两者 - 20 分钟

上午 9:48:小睡 - 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 已添加。感谢您看一下。 看起来你的 tableView 总是在设置这个单元格:cell.textLabel.text = [self.sortedAndFilteredArray[indexPath.row] description]; 是的,我开始这个项目已经有一段时间了,但我相信这是正确的。你认为这会导致我遇到这个问题吗? 【参考方案1】:

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 上调用描述,我嫌疑人可能不存在并且正在执行显示内存位置的默认行为。如果我是对的,这表明 CCIEventStore 的 privateEvents 与您在详细视图中正在编辑的事件之间存在脱节。检查头文件中的对象属性,看看 CCIEvent 是否使用复制而不是强传递。

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

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

这些行可能与问题无关,但它们包含冗余访问器。既然你已经有了:

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

任何时候你想从事件中访问数据,你应该使用那个变量,所以上面的 textLabel 将是:

cell.textLabel.text = [event description];

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

希望对大家有所帮助!

【讨论】:

迫不及待地想回家看看这个。非常感谢你甚至试图提供帮助。如果它导致解决方案,我会告诉你。【参考方案2】:

我用 NSLogs 喷洒了我的代码,并确定我的 CCIEvents 加载得很好。我发现我的特殊描述在加载之前有效但在加载之后无效,这很奇怪。在加载之前它会给我我想要的字符串,但在加载之后它会指向内存。在检查了我的描述方法后,我注意到我默认返回超级的描述方法,除非 CCIEvent 满足某些条件,这迫使我意识到我的 CCIEvents 有一个 eventType 属性,我没有包含在 encodeWithCoder: 或 initWithCoder: 块中.我所要做的就是在其中添加 eventType,然后我的描述就可以正常工作。非常感谢,克里斯!我真的把这个项目搁置了几个月,因为我很困惑。

【讨论】:

以上是关于iOS 应用程序的存档数据无法正确加载的主要内容,如果未能解决你的问题,请参考以下文章

无法从内存中正确加载位图

无法在 xcode 中生成 iOS App 存档

无法从 xcode 导出 IOS 应用程序存档以进行 testflight

iOS Cordova 应用程序无法在设备上正确加载

Xcode 6/iOS - 缺少存档和 Info.plist

无法存档更新的 iOS PhoneGap 应用程序