iOS开发丨plist文件创建和读写数据并使用KVO自动保存

在很多项目中,都需要去存储一些用户数据或者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: 方法,就可以方便的去除字典中包含的异常值,从而可以正常保存。

好了,希望我的方法能够给你们带来方便,如果用得高兴的话顺便帮我点个赞吧,谢谢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352