我一直试图在我的项目中存储并从NSUserDefaults
自定义对象数组中恢复,但似乎出现了问题。我对这个问题进行了很多研究,但我找不到任何问题的答案。
在NSUserDefaults
中保存数据时,似乎存储它的效果还不错,问题出现在我尝试取回这些数据时:应用将会出现问题因libc++abi.dylib: terminating with uncaught exception of type NSException
错误而完全崩溃。
这是我的代码:
Wine.h
#import <Foundation/Foundation.h>
@import UIKit;
#define NO_RATING -1
@interface WineModel : NSObject <NSCoding>
@property(copy, nonatomic) NSString *type;
@property(strong, nonatomic) UIImage *photo;
@property(strong, nonatomic) NSURL *photoURL;
@property(strong, nonatomic) NSURL *wineCompanyWeb;
@property(copy, nonatomic) NSString *notes;
@property(copy, nonatomic) NSString *origin;
@property(nonatomic) int rating;
@property(strong, nonatomic) NSArray *grapes;
@property(copy, nonatomic) NSString *name;
@property(copy, nonatomic) NSString *wineCompanyName;
- (id) initWithCoder: (NSCoder *) decoder;
- (void) encodeWithCoder: (NSCoder *) encoder;
//SOME OTHER METHODS...//
-(id) initWithName: (NSString *) aName
wineCompanyName: (NSString *) aWineCompanyName
type: (NSString *) aType
origin: (NSString *) anOrigin
grapes: (NSArray *) arrayOfGrapes
wineCompanyWeb: (NSURL *) aURL
notes: (NSString *) aNotes
rating: (int) aRating
photoURL: (NSURL *) aPhotoURL;
//For JSON
-(id) initWithDictionary: (NSDictionary *) aDict;
@end
Wine.m
#import "WineModel.h"
@implementation WineModel
@synthesize photo = _photo;
#pragma mark - Properties
-(UIImage *) photo {
//SOME MORE CODE...
return _photo;
}
- (id) initWithCoder:(NSCoder *)decoder {
if (self = [super init]) {
self.name = [decoder decodeObjectForKey:@"name"];
self.wineCompanyName = [decoder decodeObjectForKey:@"company"];
self.type = [decoder decodeObjectForKey:@"type"];
self.origin = [decoder decodeObjectForKey:@"origin"];
self.grapes = [self extractGrapesFromJSONArray:[decoder decodeObjectForKey:@"grapes"]];
self.wineCompanyWeb = [NSURL URLWithString:[decoder decodeObjectForKey:@"wine_web"]];
self.notes = [decoder decodeObjectForKey:@"notes"];
self.rating = [[decoder decodeObjectForKey:@"rating"] intValue];
self.photoURL = [NSURL URLWithString:[decoder decodeObjectForKey:@"picture"]];
}
return self;
}
- (void) encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.name forKey:@"name"];
[encoder encodeObject:self.wineCompanyWeb forKey:@"company"];
[encoder encodeObject:self.type forKey:@"type"];
[encoder encodeObject:self.origin forKey:@"origin"];
[encoder encodeObject:self.grapes forKey:@"grapes"];
[encoder encodeObject:self.wineCompanyWeb forKey:@"wine_web"];
[encoder encodeObject:self.notes forKey:@"notes"];
[encoder encodeInt:self.rating forKey:@"rating"];
[encoder encodeObject:self.photoURL forKey:@"picture"];
}
#pragma mark - Init
-(id) initWithName: (NSString *) aName
wineCompanyName: (NSString *) aWineCompanyName
type: (NSString *) aType
origin: (NSString *) anOrigin
grapes: (NSArray *) arrayOfGrapes
wineCompanyWeb: (NSURL *) aURL
notes: (NSString *) aNotes
rating: (int) aRating
photoURL: (NSURL *) aPhotoURL {
if(self==[super init]) {
_name = aName;
_wineCompanyName = aWineCompanyName;
_type = aType;
_origin = anOrigin;
_grapes = arrayOfGrapes;
_wineCompanyWeb = aURL;
_notes = aNotes;
_rating = aRating;
_photoURL = aPhotoURL;
}
return self;
}
#pragma mark - JSON
-(id) initWithDictionary:(NSDictionary *)aDict {
return [self initWithName:[aDict objectForKey:@"name"]
wineCompanyName:[aDict objectForKey:@"company"]
type:[aDict objectForKey:@"type"]
origin:[aDict objectForKey:@"origin"]
grapes:[self extractGrapesFromJSONArray:[aDict objectForKey:@"grapes"]]
wineCompanyWeb:[NSURL URLWithString:[aDict objectForKey:@"wine_web"]]
notes:[aDict objectForKey:@"notes"]
rating:[[aDict objectForKey:@"rating"]intValue]
photoURL:[NSURL URLWithString:[aDict objectForKey:@"picture"]]
];
}
-(NSArray *) extractGrapesFromJSONArray: (NSArray *)JSONArray {
//SOME MORE CODE...
return grapes;
}
@end
这是葡萄酒类。它具有<NSCoding>
协议以及(id) initWithCoder: (NSCoder *) decoder;
和(void) encodeWithCoder: (NSCoder *) encoder;
两种方法。到目前为止,我看起来还不错,让我们继续下一堂课:
Winery.h
#import <Foundation/Foundation.h>
#import "Wine.h"
#define RED_WINE_KEY @"Red"
#define WHITE_WINE_KEY @"White"
#define OTHER_WINE_KEY @"Others"
@interface WineryModel : NSObject
@property (strong, nonatomic) NSMutableArray *redWines;
@property (strong, nonatomic) NSMutableArray *whiteWines;
@property (strong, nonatomic) NSMutableArray *otherWines;
@property(readonly, nonatomic) int redWineCount;
@property(readonly, nonatomic) int whiteWineCount;
@property(readonly, nonatomic) int otherWineCount;
-(WineModel *) redWineAtIndex: (NSUInteger) index;
-(WineModel *) whiteWineAtIndex: (NSUInteger) index;
-(WineModel *) otherWineAtIndex: (NSUInteger) index;
@end
Winery.m
#import "Winery.h"
@implementation WineryModel
#pragma mark - Properties
-(int) redWineCount {
return [self.redWines count];
}
-(int) whiteWineCount {
return [self.whiteWines count];
}
-(int) otherWineCount {
return [self.otherWines count];
}
-(id) init {
if(self == [super init]) {
NSUserDefaults *userDefault=[NSUserDefaults standardUserDefaults];
//Check if there is data stored locally
if(([[[userDefault dictionaryRepresentation] allKeys] containsObject:@"redWines"])
&&([[[userDefault dictionaryRepresentation] allKeys] containsObject:@"whiteWines"])
&&([[[userDefault dictionaryRepresentation] allKeys] containsObject:@"otherWines"])) {
if([userDefault objectForKey:@"redWines"] != nil && [userDefault objectForKey:@"whiteWines"] != nil && [userDefault objectForKey:@"otherWines"] != nil) {
//Try to load data from NSUserDefaults
NSData *decodedRedWines = [userDefault objectForKey:@"redWines"];
self.redWines = [[NSKeyedUnarchiver unarchiveObjectWithData: decodedRedWines] mutableCopy]; //IT WILL CRASH HERE
NSData *decodedWhiteWines = [userDefault objectForKey:@"whiteWines"];
self.whiteWines = [[NSKeyedUnarchiver unarchiveObjectWithData: decodedWhiteWines] mutableCopy];
NSData *decodedOtherWines = [userDefault objectForKey:@"otherWines"];
self.otherWines = [[NSKeyedUnarchiver unarchiveObjectWithData: decodedOtherWines] mutableCopy];
}
} else {
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://url.com/wines.json"]]; //JSON URL
NSURLResponse *response = [[NSURLResponse alloc]init];
NSError *error;
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if(data != nil) { //No errors
//Passing from JSON to an NSArray
NSArray * JSONObjects = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions
error:&error];
if (JSONObjects != nil) {
//No errors
for(NSDictionary *dict in JSONObjects){
WineModel *wine = [[WineModel alloc] initWithDictionary:dict];
if(wine.name != nil && wine.wineCompanyName != nil && wine.type != nil && wine.origin != nil ) {
if ([wine.type isEqualToString:RED_WINE_KEY]) {
if (!self.redWines) {
self.redWines = [NSMutableArray arrayWithObject:wine];
}
else {
[self.redWines addObject:wine];
}
}
else if ([wine.type isEqualToString:WHITE_WINE_KEY]) {
if (!self.whiteWines) {
self.whiteWines = [NSMutableArray arrayWithObject:wine];
}
else {
[self.whiteWines addObject:wine];
}
}
else {
if (!self.otherWines) {
self.otherWines = [NSMutableArray arrayWithObject:wine];
}
else {
[self.otherWines addObject:wine];
}
}
}
}
} else {
NSLog(@"JSON parsing error: %@", error.localizedDescription);
}
} else {
NSLog(@"Server error: %@", error.localizedDescription);
}
//Storing the array of wine objects in the NSUserDefaults
NSData *encodedRedWines = [NSKeyedArchiver archivedDataWithRootObject:_redWines];
[userDefault setObject:encodedRedWines forKey:@"redWines"];
NSData *encodedWhiteWines = [NSKeyedArchiver archivedDataWithRootObject:_whiteWines];
[userDefault setObject:encodedWhiteWines forKey:@"whiteWines"];
NSData *encodedOtherWines = [NSKeyedArchiver archivedDataWithRootObject:_otherWines];
[userDefault setObject:encodedOtherWines forKey:@"otherWines"];
}
}
return self;
}
-(WineModel *) redWineAtIndex: (NSUInteger) index {
return [self.redWines objectAtIndex:index];
}
-(WineModel *) whiteWineAtIndex: (NSUInteger) index{
return [self.whiteWines objectAtIndex:index];
}
-(WineModel *) otherWineAtIndex: (NSUInteger) index{
return [self.otherWines objectAtIndex:index];
}
@end
因此,第一次启动应用程序时,它将从Web中的JSON文件下载数据,然后将信息存储在NSUserDefaults中。看起来这一步它已经正确完成(至少在这一点上没有崩溃)。第二次启动应用程序后出现问题。它将检查NSUserDefault下是否存在本地数据存储,如果是,它将尝试加载数据并存储到NSMutableAtray中。不幸的是它不会这样做,它在这里self.redWines =[NSKeyedUnarchiver unarchiveObjectWithData: decodedRedWines];
崩溃了我之前写的错误代码。在调试时,我可以看到在检索redWines
密钥时有数据,但它似乎出现了问题。
请注意,我使用自定义初始化程序(initWithDictionary
)来创建wine对象而不是默认的init
方法。我不知道这可能是坠机的原因......
这是完整的日志:
2017-05-22 20:31:30.354640+0200 App[1905:891526] -[NSTaggedPointerString objectForKey:]: unrecognized selector sent to instance 0xa5c064950b08843b
2017-05-22 20:31:30.354932+0200 App[1905:891526] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSTaggedPointerString objectForKey:]: unrecognized selector sent to instance 0xa5c064950b08843b'
*** First throw call stack:
(0x18e0bafe0 0x18cb1c538 0x18e0c1ef4 0x18e0bef54 0x18dfbad4c 0x1000591d8 0x100057dec 0x18eb0a430 0x18eb10f10 0x18eaa684c 0x18eb0a430 0x18eb09b68 0x18eb08d94 0x100061118 0x1000621d0 0x10005c120 0x19425d204 0x194469738 0x19446f1e0 0x194483d18 0x19446c474 0x18fc63884 0x18fc636f0 0x18fc63aa0 0x18e06942c 0x18e068d9c 0x18e0669a8 0x18df96da4 0x194256384 0x194251058 0x100060b90 0x18cfa559c)
libc++abi.dylib: terminating with uncaught exception of type NSException
任何想法??
提前致谢!!
答案 0 :(得分:0)
你的initWithCoder方法中有一个拼写错误:
self.wineCompanyName = [decoder decodeObjectForKey:@“ comapny ”];
如果这不能解决问题,我会更仔细地查看NSUserDefaults文档 - 它说“NSUserDefaults返回的值是不可变的,即使你将一个可变对象设置为值。”您的redWines属性被定义为NSMutableArray。
答案 1 :(得分:0)
要使不可变对象变为可变,只需调用mutableCopy
@property (strong, nonatomic) NSMutableArray *redWines;
...
self.redWines = [[NSKeyedUnarchiver unarchiveObjectWithData: decodedRedWines] mutableCopy];