在很多项目中,都需要去存储一些用户数据或者APP配置, [NSUserDefaults standardUserDefaults] 只适合保存单一量的数据如APP版本检测、当前用户登录状态、当前登录账号等通用数据配置。
当项目在面临多账户数据存储的时候,利用不同文件夹路径下的 plist 文件来存储不同账号的用户数据则显得更为方便。但是也有不足的地方就是每次数据更改之后都需要调用保存语句否则就无法顺利更改到文件中。
本人比较懒,一直希望数据能够在改动之后就可以立即保存,省时省力省心。因此通过实际项目经验,摸索出了一种使用 KVO 监听和调用键值对的方法来实现轻量级的数据自动化存储。在很多项目开发中我一直都使用这个方法,觉得还是挺实用的,所以写出来分享给大家。
首先创建一个用户数据管理类,来管理 plist 文件中的用户数据,暂且命名为 UserDataManager。这个类创建好之后也可以随时复用到不同的项目中,根据需求来扩展你需要存储的用户数据变量。
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// 用户信息
static NSString * const UD_Info_UserID = @"UD_Info_UserID";
static NSString * const UD_Info_Code = @"UD_Info_Code";
static NSString * const UD_Info_Phone = @"UD_Info_Phone";
static NSString * const UD_Info_Email = @"UD_Info_Email";
static NSString * const UD_Info_Gender = @"UD_Info_Gender";
static NSString * const UD_Info_Birthday = @"UD_Info_Birthday";
static NSString * const UD_Info_Height = @"UD_Info_Height";
static NSString * const UD_Info_Weight = @"UD_Info_Weight";
/////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface UserDataManager : NSObject
@property (strong, nonatomic) NSMutableDictionary *data;
//______________________________________________________________________________________________________
/// 单例初始化
+ (UserDataManager *)sharedModel;
/// 获取非空字典
+ (NSDictionary *)getNoNullDic:(NSDictionary *)dic;
//______________________________________________________________________________________________________
/// 检测 plist 文件是否存在,如果不存在则主动创建
- (BOOL)checkWithUID:(NSString *)uid;
/// 保存 plist 数据
- (void)savePlist;
@end
上面就是UserDataManager.h文件的具体实现内容,UD_Info_XXX表示你需要存储的用户信息变量名称,你可以根据实际的项目需求来指定不同的变量名称。
#import "UserDataManager.h"
/////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface UserDataManager ()
@property (strong, nonatomic) NSString *uid;
@property (strong, nonatomic) NSString *dataPath;
@end
/////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation UserDataManager
+ (UserDataManager *)sharedModel {
static UserDataManager *sharedInstance;
@synchronized(self) {
if (!sharedInstance) {
sharedInstance = [[UserDataManager alloc] init];
}
return sharedInstance;
}
return sharedInstance;
}
+ (NSDictionary *)getNoNullDic:(NSDictionary *)dic {
NSMutableDictionary *newDic = [[NSMutableDictionary alloc] init];
for (NSString * key in [dic allKeys]) {
if (![[dic objectForKey:key] isKindOfClass:[NSNull class]])
[newDic setObject:[dic objectForKey:key] forKey:key];
}
return newDic;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark 加载Plist文件
/////////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)checkWithUID:(NSString *)uid {
if (uid.length == 0) {
NSLog(@"UID错误!");
return NO;
}
self.uid = uid;
// 移除旧的监听,避免重复
[self removeObserver];
// 获取 APP 沙盒路径
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *uidPath = [NSString stringWithFormat:@"%@/%@", docPath, self.uid];
self.dataPath = [uidPath stringByAppendingPathComponent:@"user_data.plist"];
// 检测路径和文件是否存在, 如果不存在则创建路径
if ([[NSFileManager defaultManager] fileExistsAtPath:self.dataPath] == false) {
[[NSFileManager defaultManager] createDirectoryAtPath:uidPath withIntermediateDirectories:YES attributes:nil error:nil];
NSLog(@"UID路径创建成功!");
// 首次创建时初始化 plist 文件
_data = [[NSMutableDictionary alloc] init];
[self loadAllData];
}
// 加载 plist 文件数据
_data = [[NSMutableDictionary alloc] initWithContentsOfFile:self.dataPath];
[self loadAllData];
[self addObserver];
return YES;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
// KVC 监听到数据改变,自动进行保存
NSLog(@"Plist Data AutoSave, %@ = %@", keyPath, change);
[self savePlist];
}
- (void)savePlist {
if ([_data writeToFile:self.dataPath atomically:YES] == NO) {
NSLog(@"Plist数据保存失败!");
}
}
- (void)addNewData:(NSObject *)data key:(id)key {
if ([_data objectForKey:key] == nil) {
[_data setObject:data forKey:key];
NSLog(@"UserPlist Add New = [%@ : %@]", key, data);
[self savePlist];
}
}
- (void)loadAllData {
// 加载所有数据,可以在这里进行初始化赋值;如果后续有新的数据项,直接添加在末尾就行了
[self addNewData:@"" key:UD_Info_UserID];
[self addNewData:@"" key:UD_Info_Code];
[self addNewData:@"" key:UD_Info_Phone];
[self addNewData:@"" key:UD_Info_Email];
[self addNewData:@"" key:UD_Info_Gender];
[self addNewData:@"" key:UD_Info_Birthday];
[self addNewData:@"" key:UD_Info_Height];
[self addNewData:@"" key:UD_Info_Weight];
}
- (void)addObserver {
// 开启监听, 如果 data 在外部被改变,则自动存储
NSArray *keyArray = [NSArray arrayWithArray:[_data allKeys]];
for (NSString *key in keyArray) {
[_data addObserver:self forKeyPath:key options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
}
NSLog(@"开启 Plist 监听! UID = %@, UserData = %@", self.uid, _data);
}
- (void)removeObserver {
// 关闭 data 监听
NSArray *keyArray = [NSArray arrayWithArray:[_data allKeys]];
for (NSString *key in keyArray) {
[_data removeObserver:self forKeyPath:key context:nil];
}
NSLog(@"移除 Plist 监听!");
}
- (void)dealloc {
[self removeObserver];
}
@end
上面就是 UserDataManager.m 文件的具体实现内容,plist 文件中存储的数据,都是通过转换成 NSMutableDictionary 的字典来使用的,比如你需要获取用户的 Email 信息,可以通过 [UserDataManager sharedModel].data[UD_Info_Email] 很方便的拿到。
在项目中的具体使用步骤:
1、使用 [UserDataManager sharedModel] 初始化一个单例。(在 loadAllData 函数中你可以自定义初始化的数据,可以是字符串、NSNumber、Array、或者 NSDictionary)
2、调用 checkWithUID: 方法来初始化和加载 plist 文件,传入用户的 uid 值(或者用户的 account),用来区分不同文件夹下的 "user_data.plist" 文件。
3、在需要用到数据存储的地方,如用户的Email举例,通过 [UserDataManager sharedModel].data[UD_Info_Email] 直接拿到数据;通过赋值 [UserDataManager sharedModel].data[UD_Info_Email] = @"123456@gmail.com",直接改变 plist 文件中对应 Key 下面的值,改变之后因为 KVO 的作用 data 会自动更新和保存。
注意事项
1、如果你的数据存储中任何一项 Key 对应的值包含 NSNull 或者 nil 类型的数据,将会导致 plist 文件保存失败,因此,在存储时你需要额外保证你的数据不会为 NSNull 或者 nil,但是你可以使用 @"" 、@(0)、@{}、@[] 这些值不会导致失败。
2、如果你初始化用了 NSMutableArray 或者 NSMutableDictionary 类型的数据,那么保存时也需要使用对应类型的数据。
3、部分从网络获取的 NSDictionary 类型的数据可能会包含 NSNull 或者 nil 值,在保存之前你只需要调用 getNoNullDic: 方法,就可以方便的去除字典中包含的异常值,从而可以正常保存。
好了,希望我的方法能够给你们带来方便,如果用得高兴的话顺便帮我点个赞吧,谢谢。