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

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" ];
     }
 
     return self;
}


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" ];
     }
 
     return self;
}
@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>
 
@ 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 中

?
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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值