【发布时间】:2014-07-23 14:30:10
【问题描述】:
我对 Objective-C(和一般编码)还很陌生,我正在开发一个 iOS 项目以熟悉自定义对象的持久存储。
在开发的第一阶段一切都很顺利(也感谢我在 StackOverflow 上找到的许多答案)直到几天前,当我遇到一个我无法理解或解决的奇怪行为时。我会尽力描述应用程序并解释我遇到的问题(英语不是我的主要语言,抱歉)。
应用的结构已经有:
- 重量对象的自定义类(每个对象都有一些属性:
NSDate,int 表示公斤,int 表示克,float 表示公斤等); - 一个 MainViewController,包含一个带有两个组件(公斤和克)的 UIPickerView,一个“保存选定的重量”按钮,用于将当前重量对象(实际上是它的精确副本 - 我实现了 NSCoping 协议)添加到 @987654322 @ 在索引 0 处插入它,一个“历史”按钮,触发到 FlipSideViewController 的 segue 和几个标签,显示最后记录的重量及其值的日期;
-
FlipSideViewController包含一个 TableView,我在其中加载 NSMutableArray 的值。 - 还有一个 SettingsViewController,但它还没有做任何事情,并且与此问题无关(我之所以提到它只是因为我稍后包含的代码中有一些对其的引用)。
我通过NSUserDefaults 实现了数据持久化,并在我的自定义权重类中添加了NSCoding 方法(initWithCoder 和 encodeWithCoder):我对包含自定义权重对象(NSKeyedArchiver archivedDataWithRootObject:obj)的数组进行编码和解码,因为,如果我理解正确,则 NSUserDefaults 不允许存储未在 NSData 中编码的自定义对象。
基本的交互已经起作用了:
- 如果我开始清理,而不恢复以前存储的数据,我可以创建许多新的权重对象,这些对象被添加到 NSMutableArray 并显示在 FlipSideViewController 的 TableView 中。
- 即使我恢复之前记录的数据,一切似乎都很好(但事实并非如此,我稍后会解释):
MainViewController中的标签显示之前记录的最后一个对象的值,PickerView 的行匹配最后选择的值,FlipSideViewController中的 TableView 显示了数组的恢复值...
但是有一个但是:当我恢复以前的数据(上面的第 2 点)时,即使一切看起来都正确,当我点击“保存按钮”来添加新记录时,数组中的值也会被弄乱。
用一个例子来解释他们是如何搞砸的可能更容易:
假设在第一个会话中,我保存了 3 个不同的重量值:第一个 12.0 公斤,然后 25.0 公斤,最后 70.0 公斤。 然后 TableView 在顶部显示 70,0 kg,25,0 kg 作为第二个值,12,0 作为第三个值; MainViewController 上的标签显然显示 70,0 kg,因为它是记录的最后一个值。 现在,如果我退出应用程序并再次启动它,在它加载后一切看起来都与我退出前完全一样:标签显示 70,0 公斤,而 TableView 具有相同的 3 个值以相同的顺序。 如果我想通过按保存按钮添加一个新值,比如 100.5 公斤,问题就开始了:之后,TableView 中显示的值会以我不理解的方式发生变化: 从上到下,现在的值是 100,5 - 100,5(是的,它是重复的,而不是显示 70,0)、25,0、12,0。
我不知道这个问题;重新创建数组的索引时可能会发生某些事情,但我不明白为什么以及如何。
为了清楚起见,我认为最好也提供代码;如果我的变量名和方法名通常是意大利语,请原谅(我不会再这样做了,我现在意识到,如果我必须用英语问一些东西,最好我的代码也不包含意大利语的痕迹)。
我不知道是否最好包含所有代码;由于这种行为对我来说看起来很奇怪,我想问题出在我在其他地方所做的事情上,所以我希望我可以包含所有内容:
我的自定义类:Peso.h
#import <Foundation/Foundation.h>
@interface Peso : NSObject <NSCopying,NSCoding>
@property int pesoChili;
@property int pesoGrammi;
@property float pesoInChiloGrammi;
@property NSDate * dataPesatura;
@property NSString * pesoChilogrammi;
@property NSString * DataPesaturaStringa;
-(id)initWithCoder:(NSCoder *)aDecoder;
-(void)encodeWithCoder:(NSCoder *)aCoder;
@end
我的自定义类:Peso.m
#import "Peso.h"
@implementation Peso
-(id)copyWithZone:(NSZone *)zone{
Peso *copy = [[Peso alloc] init];
if (copy)
{
copy.PesoChili = self.pesoChili;
copy.pesoGrammi = self.pesoGrammi;
copy.pesoInChiloGrammi = self.pesoInChiloGrammi;
copy.dataPesatura = self.dataPesatura;
copy.pesoChilogrammi = self.pesoChilogrammi;
copy.DataPesaturaStringa = self.DataPesaturaStringa;
}
return copy;
}
-(id)initWithCoder:(NSCoder *)aDecoder{
_pesoChili = [aDecoder decodeIntForKey:@"pesoChili"];
_pesoGrammi = [aDecoder decodeIntForKey:@"pesoGrammi"];
_pesoInChiloGrammi = [aDecoder decodeFloatForKey:@"pesoInChiloGrammi"];
_dataPesatura = [aDecoder decodeObjectForKey:@"dataPesatura"];
_pesoChilogrammi = [aDecoder decodeObjectForKey:@"pesoChilogrammi"];
_DataPesaturaStringa = [aDecoder decodeObjectForKey:@"DataPesaturaStringa"];
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeInt:_pesoChili forKey:@"pesoChili"];
[aCoder encodeInt:_pesoGrammi forKey:@"pesoGrammi"];
[aCoder encodeFloat:_pesoInChiloGrammi forKey:@"pesoInChiloGrammi"];
[aCoder encodeObject:_dataPesatura forKey:@"dataPesatura"];
[aCoder encodeObject:_pesoChilogrammi forKey:@"pesoChilogrammi"];
[aCoder encodeObject:_DataPesaturaStringa forKey:@"DataPesaturaStringa"];
}
@end
MainViewController.h
#import "FlipsideViewController.h"
#import "SettingsViewController.h"
#import "Peso.h"
@interface MainViewController : UIViewController <FlipsideViewControllerDelegate,UIPickerViewDataSource,UIPickerViewDelegate,SettingsViewControllerDelegate>
@end
MainViewController.m
#define kPercorso @"percorsoArrayCronologiaPeso30"
#import "MainViewController.h"
@interface MainViewController ()
@property (weak, nonatomic) IBOutlet UILabel *dataPesoCorrente;
@property (weak, nonatomic) IBOutlet UILabel *labelPesoCorrente;
@property (weak, nonatomic) IBOutlet UIButton *bottoneRegistraPeso;
@property (weak, nonatomic) IBOutlet UIButton *bottoneHistory;
@property (weak, nonatomic) IBOutlet UIPickerView *thePicker;
@end
@implementation MainViewController {
NSMutableArray *cronologiaPeso;
Peso * pesoRilevato;
NSUserDefaults *defaults;
}
- (IBAction)salvaPesoCorrente:(id)sender {
pesoRilevato.pesoChilogrammi = [NSString stringWithFormat:@"%d,%d kg",pesoRilevato.pesoChili,pesoRilevato.pesoGrammi];
pesoRilevato.dataPesatura = [NSDate date];
NSDateFormatter * formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"EEEE, MMMM dd yyyy - hh:mm:ss a"];
pesoRilevato.DataPesaturaStringa = [formatter stringFromDate:pesoRilevato.dataPesatura];
self.labelPesoCorrente.text = pesoRilevato.pesoChilogrammi;
self.dataPesoCorrente.text = pesoRilevato.DataPesaturaStringa;
self.bottoneHistory.hidden = NO;
[self nuoviValori];
}
-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{
return 2;
}
-(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{
switch (component) {
case 0:
return 201;
break;
case 1:
return 10;
break;
default:
return 0;
}
}
-(NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{
switch (component) {
case 0:
return [NSString stringWithFormat:@"%d", row];
break;
case 1:
return [NSString stringWithFormat:@"%d", row];
break;
default:
return @"error";
break;
}
}
-(void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{
if (component == 0) {
pesoRilevato.pesoChili = row;
}
if (component == 1) {
pesoRilevato.pesoGrammi = row;
}
[self combinaChiliEGrammi];
}
-(void)combinaChiliEGrammi{
pesoRilevato.pesoInChiloGrammi = pesoRilevato.pesoGrammi;
while (pesoRilevato.pesoInChiloGrammi >= 1) {
pesoRilevato.pesoInChiloGrammi /= 10;
}
pesoRilevato.pesoInChiloGrammi += pesoRilevato.pesoChili;
self.bottoneRegistraPeso.hidden = NO;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.bottoneRegistraPeso.hidden = YES;
self.bottoneHistory.hidden = YES;
if ([[[NSUserDefaults standardUserDefaults] dictionaryRepresentation].allKeys containsObject:kPercorso]) {
cronologiaPeso = [[NSMutableArray alloc]initWithArray:[self caricaCronologiaPesoCodificatoConKey:kPercorso]];
self.bottoneHistory.hidden = NO;
self.bottoneRegistraPeso.hidden = NO;
self.labelPesoCorrente.text = [[cronologiaPeso objectAtIndex:0]pesoChilogrammi];
self.dataPesoCorrente.text = [[cronologiaPeso objectAtIndex:0]DataPesaturaStringa];
[self.thePicker selectRow:[[cronologiaPeso objectAtIndex:0]pesoChili] inComponent:0 animated:YES];
[self.thePicker selectRow:[[cronologiaPeso objectAtIndex:0]pesoGrammi] inComponent:1 animated:YES];
if (!pesoRilevato){
pesoRilevato = [cronologiaPeso objectAtIndex:0];
}
} else {
pesoRilevato = [[Peso alloc]init];
cronologiaPeso = [[NSMutableArray alloc]init];
}
}
-(void)nuoviValori{
[cronologiaPeso insertObject:[pesoRilevato copy] atIndex:0];
// [cronologiaPeso insertObject:[pesoRilevato copy] atIndex:pesoRilevato.contatoreOggettiPeso];
[self salvaArrayCronologiaPesoCodificandolo:cronologiaPeso];
}
-(void)salvaArrayCronologiaPesoCodificandolo:(NSMutableArray*)obj{
defaults = [NSUserDefaults standardUserDefaults];
NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:obj];
[defaults setObject:myEncodedObject forKey:kPercorso];
[defaults synchronize];
}
-(NSArray*)caricaCronologiaPesoCodificatoConKey:(NSString*)key{
defaults = [NSUserDefaults standardUserDefaults];
NSData *myEncodedObject = [defaults objectForKey: key];
NSArray* obj = (NSArray*)[NSKeyedUnarchiver unarchiveObjectWithData: myEncodedObject];
return obj;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#pragma mark - Flipside View
- (void)flipsideViewControllerDidFinish:(FlipsideViewController *)controller
{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)settingsViewControllerDidFinish:(SettingsViewController *)controller
{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:@"showAlternate"]) {
[[segue destinationViewController] setDelegate:self];
FlipsideViewController * nextViewController = [segue destinationViewController];
[nextViewController setCronologiaPeso:cronologiaPeso];
}
if ([[segue identifier] isEqualToString:@"showSettings"]) {
[[segue destinationViewController] setDelegate:self];
}
}
@end
** FlipsideViewController.h**
#import <UIKit/UIKit.h>
#import "Peso.h"
@class FlipsideViewController;
@protocol FlipsideViewControllerDelegate
- (void)flipsideViewControllerDidFinish:(FlipsideViewController *)controller;
@end
@interface FlipsideViewController : UIViewController <UITableViewDataSource,UITableViewDelegate>
@property (weak, nonatomic) id <FlipsideViewControllerDelegate> delegate;
@property NSMutableArray *cronologiaPeso;
- (IBAction)done:(id)sender;
@end
** FlipsideViewController.m**
#import "FlipsideViewController.h"
@interface FlipsideViewController ()
@end
@implementation FlipsideViewController{
NSUserDefaults *defaults;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.cronologiaPeso.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cella" forIndexPath:indexPath];
Peso * currentPeso = [self.cronologiaPeso objectAtIndex:indexPath.row];
cell.textLabel.text = currentPeso.pesoChilogrammi;
cell.detailTextLabel.text = currentPeso.DataPesaturaStringa;
return cell;
}
// NOT YET IMPLEMENTED
//-(BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
// return YES;
//}
//
//
//-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{
// if (editingStyle == UITableViewCellEditingStyleDelete) {
// [self.cronologiaPeso removeObjectAtIndex:indexPath.row];
// [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
//// [defaults synchronize]
// }
//}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#pragma mark - Actions
- (IBAction)done:(id)sender
{
[self.delegate flipsideViewControllerDidFinish:self];
}
@end
如果这个问题太长了,也许不是很清楚,如果我已经包含了所有的代码,请原谅;显然我在保存或恢复数组时做错了什么,但我不知道如何以及在哪里......或者我正在遵循的整个模式来保存和恢复错误的数据,我是自学成才我大约一个月前开始编码,所以我确信我的逻辑存在严重问题。
提前致谢,
切萨雷
【问题讨论】:
-
你是在设备还是模拟器上测试的?如果您在设备上测试过,请尝试删除该应用并重新构建。
-
@Bejibun 我在模拟器上测试过,我还没有苹果开发者账号。
-
@Cesare,我的建议是从小处着手。创建一个没有 UI 的新项目,只有你的 Peso 类。启动,在 NSUserDefaults 中查找 Pesos 数组。如果找不到,请在代码中创建一个并将其保存到 NSUserDefaults。如果您确实找到了,请添加一些比索并重新保存。 NSLog 每一步。如果你有问题,你可以来这里发布一个小得多的问题。
-
谢谢@danh,您的建议非常有道理:我担心我试图同时做太多事情,也许从小处着手可以帮助我找到问题的原因大批。我会告诉你的,再次感谢!
-
嗨@danh,这很疯狂,但我在没有用户界面且功能较少的简化项目中也回到了第一方。我想我更好地理解了问题所在,但我无法解决它:基本上,在从 NSUSerDefaults 恢复数组之后(并且仅在之后),将新对象添加到可变数组的简单操作会导致:新对象按预期推送到索引 0,索引 1 处的对象(在新保存之前位于索引 0 处)消失并被索引 0 处相同对象的副本替换。相反,如果我尝试在最后添加新对象...
标签: ios objective-c nsmutablearray nsuserdefaults