最新消息:20210816 当前crifan.com域名已被污染,为防止失联,请关注(页面右下角的)公众号

【已解决】iPhone/iOS中保存自定义对象(Custom Object/Custom Class)的数组(NSMutableArray/NSArray)到NSUserDefaults

iOS crifan 6783浏览 0评论

【问题】

在折腾:

给Your Second iOS App:BirdWatching添加支持程序退出后,用户数据仍然保留

的过程中,遇到一个问题,需要将一个自定义对象的数组,保存到NSUserDefaults。

 

【解决过程】

1.经过学习很多资料后,然后加上一番折腾,先去实现了单个自定义对象的编解码和存储/恢复:

贴出部分相关的代码:

BirdSighting.h:

@interface BirdSighting : NSObject  <NSCoding>
{

}

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *location;
@property (nonatomic, strong) NSDate *date;
@property (nonatomic) UIImage *image;


BirdSighting.m:

-(void)encodeWithCoder:(NSCoder *)aCoder{
    //encode properties/values
    [aCoder encodeObject:self.name      forKey:@"name"];
    [aCoder encodeObject:self.location  forKey:@"location"];
    [aCoder encodeObject:self.date      forKey:@"date"];
    [aCoder encodeObject:self.image     forKey:@"image"];
}

-(id)initWithCoder:(NSCoder *)aDecoder{
    if((self = [super init])) {
        //decode properties/values
        self.name       = [aDecoder decodeObjectForKey:@"name"];
        self.location   = [aDecoder decodeObjectForKey:@"location"];
        self.date       = [aDecoder decodeObjectForKey:@"date"];
        self.image      = [aDecoder decodeObjectForKey:@"image"];
    }

    return self;
}


BirdSightingDataController.m:

-(void)initializeDefaultDataList{  
    NSMutableArray *sightingList = [[NSMutableArray alloc] init];
    self.masterBirdSightingList = sightingList;
    //[self addBirdSightingWithName:@"Pigeon" location:@"Everywhere" date:[NSDate date] image:[UIImage imageNamed:@"defaultBirdImage.gif"] ];

    //restore data
//    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
//    NSData *savedEncodedData = [defaults objectForKey:@"BirdSightingList"];
//    self.masterBirdSightingList = (NSMutableArray *)[NSKeyedUnarchiver unarchiveObjectWithData:savedEncodedData];

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSData *prevSavedData = [defaults objectForKey:@"SingleBirdSightingObject"];
    BirdSighting *decodedSingleObj = (BirdSighting *)[NSKeyedUnarchiver unarchiveObjectWithData:prevSavedData];
    if(decodedSingleObj != nil)
    {
        [self.masterBirdSightingList addObject:decodedSingleObj];
    }
}
...

-(void)addBirdSightingWithName:(NSString *)inputBirdName location:(NSString *)inputLocation date:(NSDate *)date image:(UIImage *)image{
    BirdSighting *sighting;
    //NSDate *today = [NSDate date];
    //sighting = [[BirdSighting alloc]initWithName:inputBirdName location:inputLocation date:today];
    sighting = [[BirdSighting alloc]initWithName:inputBirdName location:inputLocation date:date image:image];
    [self.masterBirdSightingList addObject:sighting];

    //save data
    //NSData *encodedCurBirdSightingList = [NSKeyedArchiver archivedDataWithRootObject:self.masterBirdSightingList];
//    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
//    [defaults setObject:encodedCurBirdSightingList forKey:@"BirdSightingList"];
    NSData *encodedSingleObj = [NSKeyedArchiver archivedDataWithRootObject:sighting];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setObject:encodedSingleObj forKey:@"SingleBirdSightingObject"];
}

如此,暂时可以实现了,单个的对象,每次保存后,下次恢复,也可以完整的恢复出来数据并正确显示:

can restore single obj_thumb

2.后来也参考了很多帖子:

How to store custom objects in NSUserDefaults

save and restore an array of custom objects

How to archive an NSArray of custom objects to file in Objective-C

 

3.但是我这里又遇到一个问题,那就是,我此处需要保存的是自定义对象的数组,即用一个数组指针指向的多个自定义对象,所以,就没法直接使用上述方法保存这一系列自定义对象了,所以还要想其他办法才行。

但是后来却发现,其实上述原先的代码,本身已经可以工作了。

即,解码的时候,可以看到能从NSUserDefaults获得一堆的数据了:

saved data is many bytes

对应的,在恢复自定义对象数组masterBirdSightingList之前,其是null:

before restore master list is null

然后调用unarchiveObjectWithData时,也可以执行对应的自定义对象的initWithCoder函数了:

can call initWithCoder

解码后,对应的masterBirdSightingList就包含了还原出来的2个对象了:

after restore master list contain 2 obj

 

而编码过程也是所期望的:

先是NSData是空的:

nsdata is null

然后调用archivedDataWithRootObject去编码,可以调用到自定义对象的encodeWithCoder函数:

can call encodeWithCoder

然后经过4个对象的编码后,就包含了编码后的数据了:

after 4 call contain data

接着就是正常的保存到NSUserDefaults中去了.

 

如此,关闭程序之前的显示的数据是:

before close app

恢复后的显示的效果为:

reopen app

很明显,两者完全一样,即整套逻辑是正常工作的,可以完美的实现自定义对象的数组的保存和恢复.

 

下面对整个逻辑和示例代码,进行详细的总结。

 

【总结】

【自定义对象的数组NSMutableArray,保存到/恢复自 NSUserDefaults 的逻辑】

想要实现自定义类型的对象的数组,保存到NSUserDefaults中去,核心的逻辑是:

1.想要把数据保存到NSUserDefaults中的话,那首先要知道NSUserDefaults所支持的数据类型。

根据官网

NSUserDefaults Class Reference

的解释,默认支持类型是:

  • NSData
  • NSString
  • NSNumber
  • NSDate
  • NSArray
  • NSDictionary

所以,如果你本身要保存的数据是属于上述中已有的,常见的,数据类型,比如NSString,NSDate之类的话,那么很简单,直接保存即可。

2.而此处所谓的自定义对象(Custom Object)/类(Class)的话,常见的做法是,把自定义对象转换为NSData,然后利用NSUserDefaults去保存NSData。

把自定义对象转换为NSData,用的方法就是Archive,Archive可翻译为归档,压缩。

相关的方法是:

 

对应的,自定义对象本身,需要遵循NSCoding,其有两个函数:

3. 之后:

  • 当你的使用NSKeyedArchiver去把自定义对象编码为NSData时,就会调用到该对象的encodeWithCoder,编码后得到了NSData后,再保存到NSUserDefaults里面;
  • 当你从NSUserDefaults读取出你之前所保存的NSData之后,再调用NSKeyedUnarchiver去解码此NSData,然后内部会自动调用到该自定义对象的initWithCoder方法,将NSData解码为对应的自己的对象

如此,就可以实现将自定义对象保存到NSUserDefaults和从NSUserDefaults恢复之前保存的自定义对象了。

4.上述方法,看似是只针对单个自定义对象的,但是本质上,对于自定义对象的数组,也是适用的。

因为,对于一般的NSMutableArray来说,其本身也是实现了NSCoding,所以,当你去使用NSKeyedArchiver将一个自定义对象的NSMutableArray数组编码为NSData时,其内部会先调用NSMutableArray的encodeWithCoder,然后发现是个自定义对象的数组,然后就会分别针对每个自定义对象调用该自定义对象的encodeWithCoder,这样,最终实现了将整个自定义对象数组,转换为NSData;

相应的解码过程也是一样的,当从NSData解码为对应的自定义对象的NSMutableArray数组时,也是先调用NSMutableArray的initWithCoder,然后分别调用每个自定义对象的initWithCoder,最终解码为对应的自定义对象的NSMutableArray数组。

 

【自定义对象的数组NSMutableArray,保存到/恢复自 NSUserDefaults 的 参考代码】

1. 自定义对象BirdSighting

(1)对应的头文件中:

A. 实现了遵循NSCoding(Conform to NSCoding)

B. 包含了几个自己的不同类型的属性

//
//  BirdSighting.h
//  BirdWatching
//
//  Created by li crifan on 12-8-20.
//  Copyright (c) 2012年 li crifan. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface BirdSighting : NSObject  <NSCoding>
{

}

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *location;
@property (nonatomic, strong) NSDate *date;
@property (nonatomic) UIImage *image;

@end

(2)对应BirdSighting.m中

A。实现了对应的NSCoding的encodeWithCoder,将对应的不同的属性变量编码为对应数据

B。实现了对应的NSCoding的initWithCoder,从对应的 key中解码出数据还原到对应的属性变量中

//
//  BirdSighting.m
//  BirdWatching
//
//  Created by li crifan on 12-8-20.
//  Copyright (c) 2012年 li crifan. All rights reserved.
//

#import "BirdSighting.h"

@implementation BirdSighting

...

-(void)encodeWithCoder:(NSCoder *)aCoder{
    //encode properties/values
    [aCoder encodeObject:self.name      forKey:@"name"];
    [aCoder encodeObject:self.location  forKey:@"location"];
    [aCoder encodeObject:self.date      forKey:@"date"];
    [aCoder encodeObject:self.image     forKey:@"image"];
}

-(id)initWithCoder:(NSCoder *)aDecoder{
    if((self = [super init])) {
        //decode properties/values
        self.name       = [aDecoder decodeObjectForKey:@"name"];
        self.location   = [aDecoder decodeObjectForKey:@"location"];
        self.date       = [aDecoder decodeObjectForKey:@"date"];
        self.image      = [aDecoder decodeObjectForKey:@"image"];
    }

    return self;
}
@end

 

2. 在别处将自定义对象BirdSighting的数组保存到和读取自NSUserDefaults

先说背景条件:

BirdSightingDataController是个Controller

在对应的头文件BirdSightingDataController.h中,定义了个属性变量,是 自定义对象BirdSighting的数组 NSMutableArray

//
//  BirdSightingDataController.h
//  BirdWatching
//
//  Created by li crifan on 12-8-21.
//  Copyright (c) 2012年 li crifan. All rights reserved.
//

#import <Foundation/Foundation.h>

@class BirdSighting;

@interface BirdSightingDataController : NSObject
{
}

@property (nonatomic, copy)NSMutableArray *masterBirdSightingList;

......

@end

 

然后想要在Controller的实现文件BirdSightingDataController.m中,保存这个 自定义对象BirdSighting的数组NSMutableArray, 即属性变量masterBirdSightingList 到 NSUserDefaults,

以及对应的也可以从NSUserDefaults中还原出masterBirdSightingList。

 

(1)编码 masterBirdSightingList(自定义对象BirdSighting的数组NSMutableArray) 到 NSUserDefaults 中

-(void)addBirdSightingWithName:(NSString *)inputBirdName location:(NSString *)inputLocation date:(NSDate *)date image:(UIImage *)image{
    BirdSighting *sighting;
    sighting = [[BirdSighting alloc]initWithName:inputBirdName location:inputLocation date:date image:image];
    [self.masterBirdSightingList addObject:sighting];

    //save data
    NSData *encodedCurBirdSightingList = [NSKeyedArchiver archivedDataWithRootObject:self.masterBirdSightingList];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setObject:encodedCurBirdSightingList forKey:@"BirdSightingList"];
}

 

(2) 从 NSUserDefaults 中 解码还原出 (自定义对象BirdSighting的数组NSMutableArray)masterBirdSightingList

-(void)initializeDefaultDataList{
    //restore data
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSData *savedEncodedData = [defaults objectForKey:@"BirdSightingList"];
    if(savedEncodedData == nil)
    {
        NSMutableArray *sightingList = [[NSMutableArray alloc] init];
        self.masterBirdSightingList = sightingList;
    }
    else{
        self.masterBirdSightingList = (NSMutableArray *)[NSKeyedUnarchiver unarchiveObjectWithData:savedEncodedData];
    }
}

 

其中,两者的key要一致,即此处两处都使用同一个"BirdSightingList"。

 

如此,就可以实现,将 自定义对象的数组 保存到 NSUserDefaults 了。


特别提示:

如果你遇到数据偶尔没有保存成功的话,那和你要保存的变量,以及NSCoder对象是否是autoRelease的,没有半毛钱关系,而是和iOS中数据同步写回有关系。

解决办法参见:

【已解决】NSUserDefaults偶尔/有时候保存数据会失败/失效

转载请注明:在路上 » 【已解决】iPhone/iOS中保存自定义对象(Custom Object/Custom Class)的数组(NSMutableArray/NSArray)到NSUserDefaults

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

网友最新评论 (7)

  1. 能不能把你的源码给我看看,急求,谢谢
    姜辛阳9年前 (2015-05-11)回复
  2. 不错,解决了我的问题
    haven9年前 (2015-03-12)回复
  3. 您好,请问您能把源码发给我吗?我做了几天,也做不出来您的那种效果,我的邮箱[email protected]
    郭小寓10年前 (2014-08-20)回复
  4. 按你的思路我也完成了对象数组的保存,但是发现关掉应用,再启动时,对象数组丢失的现象,原因应该是经过NSCoder的对象是自动autoRelease的,这个怎么处理?
    mooncake12年前 (2012-11-25)回复
  5. 不错,谢谢分享
    andylee12年前 (2012-11-12)回复
88 queries in 0.176 seconds, using 22.13MB memory