最近我们可爱的测试工程师向我提出了一个关于用户登录状态存储的问题,仔细看了下代码,正好整理了下NSKeyedArchiver的使用问题,所以形成了这些文字,也是我在简书的第一篇文章!
1.1关于数据的持久化存储的几种方式
说到NSKeyedArchiver,也就先要了解下iOS开发中关于数据持久化存储的几种方式:1.属性列表 2.对象归档 3.数据库存储(SQLite) 4.Apple提供的CoreData存储工具,关于以上存储方式的使用场景和各自的优缺点,我在此就不再赘述了,今天主要谈一谈关于第二类存储方式-对象归档的使用方法和特点。
1.2什么是对象归档
归档是一种很常用的文件储存方法,几乎任何类型的对象都能够被归档储存(实际上是一种文件保存的形式)。
苹果提供了NSKeyedArchiver和NSKeyedUnarchiver两个类以供我们把对象序列化和反序列化,在存储之前使用NSKeyedArchiver进行序列化操作,并且写入本地文件,在使用之前使用NSKeyedUnarchiver进行反序列化的操作,以供提取使用!
1.3什么场景下会使用到对象归档
在实际的开发过程中,我们会使用各种数据存储的方式。
如果是简单的进行一些系统提供的类型,例如NSArray,NSDictionary,NSString,BOOL,NSInteger,NSfloat等基本数据类型或者对象,我们可以选择系统提供的NSUserDefault这个单例,使用简单方便,但是仅仅只能对以上这些特定的数据格式进行存储,是否有些局限性?而且属性属性列表这种方式又是否安全呢?可能这些这些条件NSUserDefault都无法满足!
对于一些规律性的,量级比较大的数据,又有规律可循的数据,我们可以选择建表或者使用Apple提供的CoreData进行持久化的存储!
那么如果数据的量级不是很大,没有必要动用数据库或者是CoreData这种大规模的杀伤性武器的时候,而且又对数据的安全性和持久性有那么些要求的时候,我们最好去选择对象序列化这种中等杀伤性工具了!
1.4对象归档的使用方法
使用归档的方法对系统提供的基本类型和基本对象进行归档的操作,在这里不再阐述了,如果明白了对自定义对象的归档和解档,那么系统的基本数据类型和基本对象的归档和解档也就相对很easy了!
具体的使用方法我就借用目前我正在开发维护的代码进行以下说明:
目前我们产品的需求是在用户登录之后,就持久化存储用户的登陆相关信息,在后续使用中不需要再次登陆。当用户没有退出登录,卸载程序后,重新从App Store中现在app后,同样保持登陆状态。
如果仅仅是登陆之后的登陆状态,使用NSUserDefault完全可以实现,只是安全性不是太好而已,但是当用户在登陆状态卸载并且重新安装app后,仍然要保持登陆状态的话,那这个问题就值得思考下了!
基于以上需求,也就是说我们要把用户的登陆信息存储在一个地方,这个地方要满足的条件是1.持久化存储,用户登录后,再次打开app不需要重新进行登录 2.用户在登录的状态下卸载app,再次重新安装后,仍然保持卸载前的登陆状态,也就是要完美重现卸载前的状态!
基于以上的条件,我们自然而然地联想到苹果的sandBox机制,关于苹果的sandBox机制,我们不再详述,最关键的一点是:在sandBox中的Document目录下存储的文件,会根据用户的appleID同步到apple的服务端,也就是说如果再次安装app的时候,此app中的沙盒(sandBox)的Document目录下的文件会被再次还原(用户的app购买信息是和用户的appleID绑定的),那么需求就被完美的满足了,具体的代码实现以及注意事项请继续向下阅读:
@interface IHomeSession : NSObject<NSCoding>
@property (nonatomic, strong) NSString *sessionId; //会话Id
@property (nonatomic, strong) NSDate *lastSessionDate; //记录上一次请求时间
@property (nonatomic, strong) NSString *token; //ut值
@property (nonatomic, strong) NSString *alias; //设备别名
@property (nonatomic, strong) NSString *username; //用户名
//序列化对象的单例
+ (IHomeSession *)sharedMemory;
//保存
- (void)save;
//获取ssesionId
- (NSString *)getSessionId;
//重置
- (void)reset;
@end
以上是.h文件
具体属性可以根据自己的需求进行添加
提供获取序列化单例的方法,方便在项目全局进行获取和使用
提供保存(save),重置(reset),获取sessionId(getSessionId)的api接口以供使用,也可以根据自己的需求添加api
最重要的一点,是当前类需要遵循NSCoding协议,NSCoding协议中有两个方法,都是requred方法,遵循该协议后,必须实现。
以下是.m文件中NSCoding协议的具体实现
#pragma mark - NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:_sessionId forKey:@"_sessionId"];
[aCoder encodeObject:_lastSessionDate forKey:@"_lastSessionDate"];
[aCoder encodeObject:_token forKey:@"_token"];
[aCoder encodeObject:_username forKey:@"_username"];
[aCoder encodeObject:_alias forKey:@"_alias"];
}
通过以上编码的方法,对当前类中的属性进行逐一的键值编码!
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super init])
{
_sessionId = [aDecoder decodeObjectForKey:@"_sessionId"];
_alias = [aDecoder decodeObjectForKey:@"_alias"];
_lastSessionDate = [aDecoder decodeObjectForKey:@"_lastSessionDate"];
_token = [aDecoder decodeObjectForKey:@"_token"];
_username = [aDecoder decodeObjectForKey:@"_username"];
}
return self;
}
通过以上解码的方法,对当前类中的属性,根据键进行逐一的逆向编码,并返回一个当前类的实例!
实现了以上的协议方法后,我们就可对当前的类对象进行归档和解档的操作了:
首先我们规定一个归档文件在沙盒中的存储路径,写在一个类方法中,方便取用,为了满足app重新安装后仍然可以获取到最后一次登陆信息的需求,我们把文件存储在沙盒中的第一个文件夹(Document)中,这样可以在程序重新安装后自动回复,原理我在需求分析上已经做了阐述!
+ (NSString *)path
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDir = [paths objectAtIndex:0];
NSString *dstPath = [documentDir stringByAppendingPathComponent:@"user.data"];
return dstPath;
}
归档的方法,我们集成在save的接口中:
- (void)save
{
[NSKeyedArchiver archiveRootObject:self toFile:[IHomeSession path]];
}
解档的方法我们集成在单例的获取中,一定要先查找对应路径下的文件是否存在,如果存在进行解档操作,不存在的话重新生成一个单例,这样会增强程序的健壮性,防止误取单例,造成程序崩溃!
+ (IHomeSession *)sharedMemory
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if ([[NSFileManager defaultManager] fileExistsAtPath:[IHomeSession path]]) {
instance = [NSKeyedUnarchiver unarchiveObjectWithData:
[NSData dataWithContentsOfFile:[IHomeSession path]]];
}
else
{
instance = [[IHomeSession alloc] init];
}
});
return instance;
}
重置的接口中,我们需要删除本地文件,同时将单例中的各种属性恢复到初始状态,然后将初始状态下的对象保存归档!
- (void)reset
{
[[NSFileManager defaultManager] removeItemAtPath:[IHomeSession path] error:nil];
instance = [[IHomeSession alloc] init];
instance.sessionId = nil;
instance.lastSessionDate = nil;
instance.token = @"default";
instance.username = nil;
instance.alias = nil;
[instance save];
}
OK了,以上的关键代码实现之后,归档和解档的操作就可以完美实现了,在程序的任何一个地方都可以依托IHomeSeesion这个单例完成操作了,至于后续需要存储的字段,可以根据产品需求依次添加了!
以上代码的形成如有不够严谨的地方,欢迎指出!谢谢!