关闭

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

1237人阅读 评论(0) 收藏 举报
分类:

ios应用数据存储方式:
在使用plist进行数据存储和读取,只适用于系统自带的一些常用类型才能用,且必须先获取路径相对麻烦;
偏好设置(将所有的东西都保存在同一个文件夹下面,且主要用于存储应用的设置信息)
归档:因为前两者都有一个致命的缺陷,只能存储常用的类型。归档可以实现把自定义的对象存放在文件中。


【问题】

在折腾:

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

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

 

【解决过程】

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

贴出部分相关的代码:

BirdSighting.h:

?
1
2
3
4
5
6
7
8
9
@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:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-(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"];
    }
 
    returnself;
}


BirdSightingDataController.m:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
-(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. 包含了几个自己的不同类型的属性

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//
//  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中解码出数据还原到对应的属性变量中

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//
//  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"];
    }
 
    returnself;
}
@end

 

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

先说背景条件:

BirdSightingDataController是个Controller

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//
//  BirdSightingDataController.h
//  BirdWatching
//
//  Created by li crifan on 12-8-21.
//  Copyright (c) 2012年 li crifan. All rights reserved.
//
 
#import <Foundation/Foundation.h>
 
@classBirdSighting;
 
@interface BirdSightingDataController : NSObject
{
}
 
@property (nonatomic, copy)NSMutableArray *masterBirdSightingList;
 
......
 
@end

 

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

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

 

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

?
1
2
3
4
5
6
7
8
9
10
-(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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
-(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偶尔/有时候保存数据会失败/失效

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:34826次
    • 积分:517
    • 等级:
    • 排名:千里之外
    • 原创:8篇
    • 转载:58篇
    • 译文:0篇
    • 评论:0条
    文章分类