【问题标题】:Troubles storing and loading custom object into NSUserDefaults Objective-C将自定义对象存储和加载到 NSUserDefaults Objective-C 中的麻烦
【发布时间】:2017-05-22 16:30:01
【问题描述】:

我一直在尝试在我的项目中存储和恢复 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

这是葡萄酒课。它具有&lt;NSCoding&gt; 协议和(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

所以,第一次启动应用程序时,它会从网络中的 JSON 文件下载数据,然后将信息存储在 NSUserDefaults 中。这一步似乎正确完成(至少此时不会崩溃)。第二次启动应用程序后出现问题。它将检查 NSUserDefault 下是否有本地数据存储,如果有,它将尝试加载数据并存储到 NSMutableAtray 中。不幸的是,它不会这样做,它在这里崩溃self.redWines =[NSKeyedUnarchiver unarchiveObjectWithData: decodedRedWines]; 并带有我之前编写的错误代码。调试的时候,检索redWineskey时可以看到有数据,但是好像出了什么问题。

请注意,我使用自定义初始化程序 (initWithDictionary) 来创建我的 wines 对象,而不是默认的 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

有什么想法吗??

提前致谢!!

【问题讨论】:

  • 您是否尝试过在 libobjc.A.dylib 中的 objc_exception_throw 或 CoreFoundation 中的 [NSException raise] 上设置断点?我怀疑如果你能将问题追溯到它阻塞的确切元素,你会很快发现问题。
  • 你能记录[NSKeyedUnarchiver unarchiveObjectWithData: decodedRedWines]这一行并告诉我返回对象的类型吗?像这样:NSLog(@"%@", [NSKeyedUnarchiver unarchiveObjectWithData: decodedRedWines]); 这将有助于了解您在NSUserDefaults 中究竟存储了什么。
  • Hemang,您好,感谢您的评论。我尝试了您的建议,但在 NSLog 中没有得到输出,而是收到此错误:[NSTaggedPointerString objectForKey:]: unrecognized selector sent to instance 0xa5c064950b08843b App[3437:1872740] *** Terminating app due to unaught exception 'NSInvalidArgumentException ',原因:'-[NSTaggedPointerString objectForKey:]: 无法识别的选择器发送到实例 0xa5c064950b08843b' *** 第一次抛出调用堆栈:(0x18e0bafe0 0x18cb1c538 0x18e0c1ef4…0x194251058 0x1000c4acc 0x18cfa559c 异常终止与 libc++lib 的类型:

标签: ios objective-c nsuserdefaults


【解决方案1】:

您的 initWithCoder 方法中有错字:

self.wineCompanyName = [解码器 decodeObjectForKey:@"comapny"];

如果这不能解决问题,我会更仔细地查看 NSUserDefaults 文档 - 它说“从 NSUserDefaults 返回的值是不可变的,即使您将可变对象设置为值。”您的 redWines 属性定义为 NSMutableArray。

【讨论】:

  • 感谢您的评论。我修正了错字(我讨厌这个愚蠢的错误),我还尝试添加三个新的非可变 NSArray 来检查这是否是问题,但我仍然得到相同的结果......我将更新线程以向您展示现在就可以了。
【解决方案2】:

要使不可变对象可变,只需调用mutableCopy

@property (strong, nonatomic) NSMutableArray *redWines;

...

self.redWines = [[NSKeyedUnarchiver unarchiveObjectWithData: decodedRedWines] mutableCopy];

【讨论】:

  • 我也会尝试,但我不确定问题是否在于我使用的是非可变数组而不是可变数组。
  • 这两种类型的代码看起来令人困惑。如果您确实需要可变对象,请删除不可变对象。添加异常的实际原因会很有帮助。
  • 谢谢,你是对的,这两种类型都让人有点困惑。我将再次更新线程,以便您可以看到更改,但我仍然遇到同样的崩溃:(
  • 添加断点并调试代码或打印未归档对象的输出。
  • 我添加了这些断点: 1. if(([[[userDefault dictionaryRepresentation] allKeys] containsObject:@"redWines"]) &&(...)) 2. NSData *decodedRedWines = [userDefault objectForKey:@"redWines"]; 3. self.redWines = [[NSKeyedUnarchiver unarchiveObjectWithData: decodedRedWines] mutableCopy]; 4. NSData *encodedRedWines = [NSKeyedArchiver archivedDataWithRootObject:_redWines]; 5. [userDefault setObject:encodedRedWines forKey:@"redWines"];
猜你喜欢
  • 2017-08-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-12
  • 2011-02-28
  • 2011-01-19
相关资源
最近更新 更多