iOS的数据持久化的主要几种方法:
- NSUserDefaults
- KeyChain Services
- Archive归档/解档
- SQLite数据库(Core Data底层也是由SQLite实现)
对于一些轻量级的数据保存,我们通常都使用NSUserDefaults或KeyChain,需求对应场景也相对较为简单例如:保存用户输入的账号密码、服务器鉴权Token、UUID等。个人感觉苹果爸爸对有些数据持久化方法提供的API非常不友好。
1.NSUserDefaults
NSUserDefaults是一个单例类,通过[NSUserDefaults standardUserDefaults]获取其实例对象,支持存储的对象类型有:NSNumber、NSString、NSDate、NSArray、NSDictionary、BOOL
NSUserDefaults保存的数据依赖App沙盒,一旦App被删除原先NSUserDefaults中保存的数据也会随之被清除,在平时开发中需要注意
例如我们如果只需要保存一个NSString字符串数据时,使用起来就非常方便,因为NSUserDefaults本身就支持该类型存储:
+ (void)setString:(NSString*)str key:(NSString*)keyWord{
NSUserDefaults *user=[NSUserDefaults standardUserDefaults];
[user setObject:str forKey:keyWord];
[user synchronize];
}
+ (NSString *)getStringBy:(NSString*)keyWord{
NSUserDefaults *user=[NSUserDefaults standardUserDefaults];
id obj=[user objectForKey:keyWord];
return obj;
}
但是实际开发中我们大多数时候使用到的都是自定义类型,这时我们就需要将其实例对象转换成NSData类型,在这之前还要实现NSCoding协议,下面是NSUserDefaults在保存自定义类型数据时set和get方法的具体实现:
+ (void)setObject:(id)obj key:(NSString*)keyWord{
NSUserDefaults *user=[NSUserDefaults standardUserDefaults];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:obj];
[user setObject:data forKey:keyWord];
[user synchronize];
}
+ (id)getObjectBy:(NSString*)keyWord{
NSUserDefaults *user=[NSUserDefaults standardUserDefaults];
id obj=[user objectForKey:keyWord];
if (obj && [obj isKindOfClass:[NSData class]]) {
id file=[NSKeyedUnarchiver unarchiveObjectWithData:obj];
return file;
}
return nil;
}
2.KeyChain Services
keychain其实是一个数据库,对于iOS每个设备只有一个keychain,因此它本身是跟设备绑定的,所以在同一个iCloud帐号下keychain是不变的,这样一来对开发者而言最大的好处就是数据是保存在沙盒之外的,即使用户删除App,并不会影响到先前保存的数据。
#pragma mark public method
+ (void)setObject:(id)obj key:(NSString*)keyWord{
NSMutableDictionary *keychainQuery = [self getKeychainQuery:keyWord];
SecItemDelete((CFDictionaryRef)keychainQuery);
[keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:obj] forKey:(id)kSecValueData];
SecItemAdd((CFDictionaryRef)keychainQuery, NULL);
}
+ (id)getObjectBy:(NSString*)keyWord{
id ret = nil;
NSMutableDictionary *keychainQuery = [self getKeychainQuery:keyWord];
[keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
[keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
CFDataRef keyData = NULL;
unsigned long t = SecItemCopyMatching((CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData);
if (t == noErr) {
@try {
ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];
} @catch (NSException *e) {
NSLog(@"Unarchive of %@ failed: %@", keyWord, e);
} @finally {
}
}
if (keyData)
CFRelease(keyData);
return ret;
}
#pragma mark private method
+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service{
return [NSMutableDictionary dictionaryWithObjectsAndKeys:
(id)kSecClassGenericPassword,(id)kSecClass,
service, (id)kSecAttrService,
service, (id)kSecAttrAccount,
(id)kSecAttrAccessibleAfterFirstUnlock,(id)kSecAttrAccessible,
nil];
}
使用示例:获取设备UUID
很早之前,我们可以通过UIDevice类获取UDID作为设备的唯一标识,but 这个方法从iOS 5之后就被禁用了
NSString *udidString = [[UIDevice currentDevice] uniqueIdentifier];
后来获取UUID的方法本身不会自动存储,所以每次调都会获得一个新的UUID标识符,函数实现如下:
//创建一个UUID
+ (NSString *)createUUID{
CFUUIDRef uuidObject = CFUUIDCreate(kCFAllocatorDefault);
NSString *uuidStr = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, uuidObject);
CFRelease(uuidObject);
return [uuidStr autorelease];
}
为了保证UUID的唯一性,可以通过KeyChain来持久存储以保证即使删除后重装应用都能够获取这个唯一标识:
#define DF_UUID_KEY @"DF_UUID_KEY"
+ (void)saveUUID:(NSString *)deviceUUID{
NSMutableDictionary *uuidDict = [NSMutableDictionary dictionary];
[uuidDict setObject:deviceUUID forKey:DF_UUID_KEY];
[DFKeyChainService setObject:uuidDict key:DF_UUID_KEY];
}
+ (NSString *)loadUUID{
NSMutableDictionary *uuidDict = (NSMutableDictionary *)[DFKeyChainService getObjectBy:DF_UUID_KEY];
NSString *retUUID = [uuidDict objectForKey:DF_UUID_KEY];
if (StrisEmpty(retUUID)) {
//取UUID并存入系统中的keychain中
retUUID = [NSString createUUID];
[NSString saveUUID:retUUID];
}
return retUUID;
}
3.Archive归档/解档
需要注意的是:
- 归档支持的数据类型有:NSString、NSNumber、NSArray、NSDictionary;
- 如果需要归档的是自定义数据类型,使用方法上面keychain相同,对象转换成NSData类型,在这之前还要实现NSCoding协议;
NSKeyedArchiver归档:
[NSKeyedArchiver archiveRootObject:object toFile:filePath];
NSKeyedUnarchiver解档:
[NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
4.SQLite 数据库
SQLite是一个轻量级关系型数据库,使用它需要最基本的数据库操作语言,不明所以的小伙伴建议先去W3Cschool学习一下。
如果你说没有用过SQLite原生方法,那非常荣幸因为苹果爸爸提供的都是c接口使用起来非常复杂还需要自己管理线程,但是如果说FMDB都没有用过那就应该关小黑屋了。
在GitHub上FMDB的链接:https://github.com/ccgus/fmdb
FMDB提供的方法比苹果提供的Core Data更加轻巧灵活,并且还提供多线程安全处理。与之前的数据持久化方法一样,SQLite也需要注意以下几点:
- 由于iOS的沙盒机制,SQLite数据库只能被创建在沙盒内部,删除App同样会导致数据丢失;
- 对数据库进行任何操作(建表、增、删、改、查)前必须调用open方法,在完成操作后及时调用close方法关闭;
- 数据库操作是需要消耗时间的,在插入、查询大量数据时尤为明显,这也是为什么FMDB需要做多线程安全处理的原因,即使是在当前iOS设备性能相当强悍的今天,本人也不建议一次性处理大量数据(我曾写过一个类似微信好友通讯录列表,由于必须一次性全部读取,当数据量达到上千时,每次从数据库查询时需要消耗较长时间,用户体验非常不好);
- 建表时尽量保证表字段的完整性,App产品本身在迭代的同时可能会有字段的增减,除非在项目有非常健壮的框架支持下,不建议经常直接修改数据库表体,处理不谨慎会导致一系列问题;
- 作为轻量级数据库并不能代替服务器本身,请不要把SQLite当成服务器数据库存入成千上万条数据,阶段性维护是很有必要的,删除无用的表体或表数据可以很有效的减少数据库文件所占用的硬盘空间。
使用步骤如下:
(1)创建数据库
//1.获取App沙盒路径下的Document文件夹
NSString *docuPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
//2.指定DB路径,后面需要每次初始化FMDatabase对象时都要使用这个.db结尾的文件
NSString *dbPath = [docuPath stringByAppendingPathComponent:@"test.db"];
//3.在对应路径下创建数据库
FMDatabase *db = [FMDatabase databaseWithPath:dbPath];
//4.在对数据库进行 增、删、改、查、建表 等一系列操作时,需要先调用FMDatabase实例方法open,如果open失败,可能是权限或者资源不足
if([db open]){
//这里可以进行数据库操作
}
//5.完成数据库操作后,必须使用close方法关闭数据库
[db close];
(2)创建数据库表
NSString *sql = @"create table if not exists t_student ('ID' INTEGER PRIMARY KEY AUTOINCREMENT,'name' TEXT NOT NULL,'age' INTEGER NOT NULL)";
//解释上面这句SQL语句:
//创建一张名为“t_student”的数据库表
//表字段:ID(integer类型,主键,自增)
//表字段:name(text类型,不可为空)
//表字段:age(integer类型,不可为空)
BOOL result = [db executeUpdate:sql];//执行SQL语句
if (result) {
NSLog(@"create table success");
}
(2)插入数据
[db open];
BOOL result = [db executeUpdate:@"insert into 't_student'(ID,name,age) values(?,?,?)" withArgumentsInArray:@[model.ID, model.name, @(model.age)]];
if (result) {
NSLog(@"insert success");
} else {
NSLog(@"insert fail");
}
[db close];
(3)查询数据
//从t_student表中查询ID为113的数据
FMResultSet *result = [db executeQuery:@"select * from 't_student' where ID = ?" withArgumentsInArray:@[@113]];
NSMutableArray *dataArr = [NSMutableArray array];
while ([result next]) {
Student *stu = [Student new];
stu.ID = [result intForColumn:@"ID"];
stu.name = [result stringForColumn:@"name"];
stu.age = [result intForColumn:@"age"];
[dataArr addObject:stu];
}
retrun dataArr;
(4)删除数据
//从t_student表中删除ID为113的数据
BOOL result = [db executeUpdate:@"delete from 't_student' where ID = ?" withArgumentsInArray:@[@113]];
if (result) {
NSLog(@"delete success");
} else {
NSLog(@"delete fail");
}
如果本文对你有所帮助,记得点击一下喜欢哈
网友评论