简体   繁体   English

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

[英]iOS app's archive data doesn't load properly

I'm pretty new to this, so bear with me. 我对此很陌生,所以请耐心等待。

I built an app using iOS Programming: The Big Nerd Ranch Guide 4th ed called Homepwner which utilizes archiving to store information when the app goes into the background and load information when it starts up again, and it works totally fine. 我使用iOS编程构建了一个应用程序:名为Homepwner的Big Nerd Ranch Guide第4版,它利用存档在应用程序进入后台时存储信息,并在再次启动时加载信息,并且它完全正常。 I decided to implement the same strategy for my own app called SBL, but when the app is loaded after having been quit, the data appears to load but doesn't display properly. 我决定为我自己的应用程序SBL实现相同的策略,但是在退出后加载应用程序时,数据似乎加载但无法正常显示。

The app displays an array of "CCIEvent"s in a table and they appear like the example: 该应用程序在表格中显示“CCIEvent”数组,它们看起来像示例:

7:00 AM: Wet and Dirty Diaper 上午7:00:湿和脏的尿布

8:48 AM: Fed - Both - 20 min 上午8:48:美联储 - 两者 - 20分钟

9:48 AM: Napped - 1 hr 0 min 上午9:48:午睡 - 1小时0分钟

But when I quit the app and start it again, the list pulls up like this (I had to identify it as code to make this post, but the info below displays on screen in my table as such): 但是当我退出应用程序并再次启动它时,列表会像这样拉出来(我必须将其识别为代码才能发布此帖子,但下面的信息显示在我的表格中的屏幕上):

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

I really don't know where to look, but here's all the code relevant to saving/loading (I think): 我真的不知道在哪里看,但这里有与保存/加载相关的所有代码(我认为):

In CCIEvent.m 在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;

}

In CCIEventStore.m 在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];

}

In AppDelegate.m 在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");
}

}

This is the some code from the view controller where the table is which I have called CCILogViewController.m: 这是视图控制器中的一些代码,其中我称之为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;

}

Here's how I get the sortedAndFilteredArray which is called in few different places including viewWillAppear: 这是我如何获得在几个不同的地方调用的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];

}

This is what is being called when getting description of a CCIEvent: 这是获取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];

}

Do you have a custom description method in CCIEvent? 你在CCIEvent中有自定义描述方法吗? By default, NSObject description will display the memory location of an object which is exactly what you're seeing. 默认情况下,NSObject description将显示对象的内存位置,这正是您所看到的。 You can do a basic test by creating something very simple like: 您可以通过创建非常简单的内容来进行基本测试:

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

It still seems like something isn't loading somewhere, or I'd expect you to see that problem during general usage. 它似乎仍然没有在某处加载,或者我希望你在一般使用过程中看到这个问题。 If you do have a CCIEvent description method, can you post it? 如果您有CCIEvent description方法,可以发布吗? I'm also curious about sortedAndFilteredArray . 我也对sortedAndFilteredArray感到好奇。

You might try sprinkling your code with NSLog() calls to see if you're getting what you expect at various points. 您可以尝试使用NSLog()调用来NSLog()代码,看看您是否在各个点获得了预期。 Keep an eye on the XCode console when the app runs to see what data you're getting. 当应用程序运行时,请关注XCode控制台以查看您获得的数据。 Ie, in CCIEvent.m initWithCoder you might add something like the following at the end of the if (self) { block: 即,在CCIEvent.m initWithCoder你可以在if (self) { block的末尾添加类似下面的内容:

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

Or even check your description method right there: 或者甚至检查你的描述方法:

NSLog([self description]);

Also be sure to do some NSLog's in your encodeWithCoder method. 另外一定要在encodeWithCoder方法中做一些NSLog。 My suspicion is the problem is occurring on the save side - there's something keeping event details from saving properly, so when they are restored they are empty so in your cellForRowAtIndexPath method it's hitting the default condition and just calling description on CCIEvent, which I suspect may not exist and is doing the default behavior of displaying the memory location. 我怀疑是在保存方面发生的问题 - 保存事件详细信息的东西是正确保存的,所以当它们被恢复时它们是空的,所以在你的cellForRowAtIndexPath方法中它达到了默认条件而只是在CCIEvent上调用描述,我怀疑它可能不存在,并且正在执行显示内存位置的默认行为。 If I'm right, this points to a disconnect between CCIEventStore's privateEvents and the event being edited in your detail view. 如果我是对的,这表明CCIEventStore的privateEvents与您在详细信息视图中编辑的事件之间存在脱节。 Check the object properties in your header files and see if somewhere CCIEvent is being passed around using copy rather than strong. 检查头文件中的对象属性,看看是否正在使用copy而不是strong传递CCIEvent。

Unrelated to the error, but still worth noting - the item in cellForRowAtIndexPath: 与错误无关,但仍值得注意 - cellForRowAtIndexPath中的项:

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

Those lines probably aren't related to the issue, but they contain a redundant accessor. 这些行可能与问题无关,但它们包含冗余访问器。 Since you already have: 既然你已经拥有:

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

any times you want to access data from the event, you should use that variable, so the above textLabel would be: 任何时候你想要从事件中访问数据,你应该使用该变量,所以上面的textLabel将是:

cell.textLabel.text = [event description];

There are a few other places in cellForRowAtIndexPath that could use a similar modification. cellForRowAtIndexPath中还有一些其他地方可以使用类似的修改。

Hope this helps some! 希望这有助于一些!

I sprinkled my code with NSLogs and determined that my CCIEvents were loading just fine. 我在NSLogs上撒了我的代码并确定我的CCIEvents加载得很好。 I found it odd that my special description worked before loading but not after loading. 我发现奇怪的是我的特殊描述在加载之前有效但在加载之后没有。 Before loading it would give me the string I wanted, but after loading it would point to memory. 在加载之前它会给我我想要的字符串,但在加载后它会指向内存。 After inspecting my description method, I noticed that I defaulted to returning the super's description method unless the CCIEvent met certain criteria, which forced me to realize that my CCIEvents have an attribute of eventType which I did not include in the encodeWithCoder: or initWithCoder: blocks. 在检查了我的描述方法之后,我注意到我默认返回超级描述方法,除非CCIEvent满足某些条件,这迫使我意识到我的CCIEvents具有eventType属性,我没有包含在encodeWithCoder:或initWithCoder:blocks中。 All I had to do was add eventType in there, and my description works like it should. 我所要做的只是在那里添加eventType,我的描述就像它应该的那样。 Thanks so much, Chris! 非常感谢,克里斯! I literally left this project alone for months because I was so stumped. 我真的离开了这个项目好几个月,因为我很难过。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM