我对Objective-C(和一般的编码)很陌生,我正在开发一个iOS项目来熟悉自定义对象的持久存储。
在开发的第一阶段一切都很顺利(也要感谢我在StackOverflow上找到的许多答案)直到几天前,我遇到了一个奇怪的行为,我无法理解也无法解决。我将尽我所能描述这个应用程序,并解释我遇到的问题(抱歉,英语不是我的主要语言)。
应用程序的结构已经有:
- 重量对象的自定义类(每个对象都有几个属性:
NSDate
, int表示千克,int表示克,float表示千克,等等); - 一个MainViewController,包含一个UIPickerView与两个组件(公斤和克),一个"保存选定的重量"按钮,添加当前的重量对象(实际上,它的一个确切的副本-我实现了NSCoping协议)到
NSMutableArray
插入它在索引0,一个"历史"按钮,触发segue到FlipSideViewController和一对标签显示的日期最后记录的重量和它的值; - 一个包含TableView的
FlipSideViewController
,我在其中加载了NSMutableArray的值。 - 还有一个SettingsViewController,但它还没有做任何事情,它与这个问题无关(我提到它只是因为有一些参考它的代码我稍后包括)。
我通过NSUserDefaults
实现数据持久性,并在我的自定义权重类中添加NSCoding
方法(initWithCoder和encodeWithCoder):我编码和解码包含自定义权重对象(NSKeyedArchiver archivedDataWithRootObject:obj
)的数组,因为,如果我正确地得到,NSUserDefaults不允许存储未在NSData
中编码的自定义对象。
基本的交互已经起作用了:
- 如果我开始干净,不恢复以前存储的数据,我可以创建许多新的权重对象,这是添加到NSMutableArray和显示在FlipSideViewController的TableView。
- 一切似乎都很好(但事实并非如此,我将稍后解释),即使我恢复以前记录的数据:在
MainViewController
中的标签显示以前记录的最后一个对象的值,PickerView的行匹配最后选择的值,在FlipSideViewController
中的TableView显示数组的恢复值…
但是有一个但是:当我恢复以前的数据(上面的第2点)时,即使一切看起来都是正确的,当我点击"保存按钮"添加新记录时,数组中的值被打乱了。
也许用一个例子更容易解释它们是如何搞砸的:
假设在第一次训练中,我保存了3个不同的体重值:第一次是12,0 kg,然后是25,0 kg,最后是70,0 kg。然后TableView在顶部显示70,0kg,第二个值为25,0kg,第三个值为12,0;MainViewController上的标签显然显示70,0kg,因为它是最后记录的值。现在,如果我退出应用程序并再次启动它,在它加载后,一切看起来都和我退出前一样:标签显示70,0kg TableView有相同的3个值,顺序相同 问题开始了,如果我想添加一个新值,比如100,5kg,按下保存按钮:之后,在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
如果这个问题太长,可能不太清楚,如果我包含了所有的代码,请原谅;显然,我在保存或恢复数组时做错了什么,但我不知道如何以及在哪里……或者可能是我所遵循的保存和恢复数据的整个模式是错误的,我是自学的,我大约一个月前开始编码,所以我确信我的逻辑有严重的问题。
提前感谢,
凯撒
我不确定我是否正确理解了你的问题,但是使用NSUserDefaults时的问题之一可能是因为它需要一些时间来保存所有数据。它基本上在内部使用一个。plist文件,所以如果你的数据非常大,更新的信息可能无法立即可用,例如,如果你想在相同的毫秒内显示在表视图上。
我的建议是在你需要的类中使用一些NSMutableDictionary属性(或者在全局单例对象中更好,所以它在任何地方都是可用的),并且每当数据更新时,首先更新字典属性,这样视图就会显示正确的数据。之后你也可以在NSUserDefaults中保存更改。另一个好的做法是使用[[NSUserDefaults standardUserDefaults] synchronize];
我希望它能以某种方式帮助你:)
看起来像我的问题,我张贴了这个问题(添加一个对象到NSMutableArray,从NSUserDefaults恢复导致一个奇怪的行为:新对象被添加到数组在索引0和索引1,完全取代的对象,而不是应该在索引1)可以很容易地修复。
在我的代码(这是包含在上面的问题),在MainViewController。m -> viewDidLoad之前我写过:
if ([[[NSUserDefaults standardUserDefaults] dictionaryRepresentation].allKeys containsObject:kPercorso]) {
cronologiaPeso = [[NSMutableArray alloc]initWithArray:[self caricaCronologiaPesoCodificatoConKey:kPercorso]];
/...
证明添加copyItems:YES到initWithArray:完全解决了这个问题。正确的代码是:
//...
cronologiaPeso = [[NSMutableArray alloc]initWithArray:[self caricaCronologiaPesoCodificatoConKey:kPercorso] copyItems:YES];
//...