iOS 数据持久化
所谓的持久化,就是将数据保存到硬盘中,使得在应用程序或机器重启后可以继续访问之前保存的数据。在iOS开发中,有很多数据持久化的方案.
在开发中常用的存储方案主要有以下几种:
1 plist文件(属性列表)
2.NSKeyedArchiver(归档)
3.SQLite 3
4.CoreData(这个不经常用)
5.keyChain
沙盒
在介绍各种存储方法之前,有必要说明以下沙盒机制。iOS程序默认情况下只能访问程序自己的目录,这个目录被称为“沙盒”。
沙盒的目录结构如下图:
ss.png
Documents文件夹。存储用户数据和用户文档。
Library文件夹。存储系统数据。
tmp文件夹。临时文件夹,用于存储临时数据,这个文件夹不稳定,数据可能被清除。
SystemData 最近刚加的,存储系统数据
1.1存储为plist文件
plist文件是将某些特定的类,通过XML文件的方式保存在目录中。
可以被序列化的类型只有如下几种:必须要遵循
NSArray;
NSMutableArray;
NSDictionary;
NSMutableDictionary;
NSData;
NSMutableData;
NSString;
NSMutableString;
NSNumber;
NSDate;
eg:存入字符串
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/myFile.plist"];
NSError *err = nil;
//writeToFile把字符串存入文件。第一个参数表示存储的路径(包含文件名),第二个参数表示是否原子性写入(如果多个线程可能同时操作这个文件,原子性需要写YES),第三个参数表示文本的编码方式(字符串存储到硬盘上必须先转换为二进制数据,选择按照哪种编码格式进行编码)。第四个参数表示如果存储失败,失败的原因。
if (![@"天涯明月新,朝暮最相思" writeToFile:path atomically:NO encoding:NSUTF8StringEncoding error:&err]) {
NSLog(@"%@",err);
}
读取字符串
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/myFile.plist"];
//从文件中读取字符串,第一个参数,读取的文件的路径,第二个参数编码方式,第三个参数,如果失败,失败的原因。
NSString *text = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
NSLog(@"%@",text);
eg:存入数组
NSArray *array = @[@"惟将长夜未开眼",@"报答平生未展眉"];
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/array.plist"];
//把数组存入文件
[array writeToFile:path atomically:NO];
读取数组
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/array.plist"];
//从文件中读取数组
NSArray *array = [NSArray arrayWithContentsOfFile:path];
for (NSString *strs in array)
{
NSLog(@"%@",strs);
}
1.2 NSUserDefaults 适用于轻量级数据了比较小的,一般存储用户信息的相关数据。
//1.获得NSUserDefaults文件
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
//2.向文件中写入内容
[userDefaults setObject:@"AAA" forKey:@"a"];
[userDefaults setBool:YES forKey:@"sex"];
[userDefaults setInteger:21 forKey:@"age"];
//2.1立即同步
[userDefaults synchronize];
//3.读取文件
NSString *name = [userDefaults objectForKey:@"a"];
BOOL sex = [userDefaults boolForKey:@"sex"];
NSInteger age = [userDefaults integerForKey:@"age"];
NSLog(@"%@, %d, %ld", name, sex, age);
- NSKeyedArchiver 数据序列化,只有遵守了NSCoding或 NSSecureCoding(更为安全的归档协议)协议,并且实现了协议里归档与解归档的方法的的类创建的对象才能够进行归档
一般是对自己创建的模型进行存储是会用到,
需要主要以下三点:
1.必须遵循并实现NSCoding协议
2.保存文件的扩展名可以任意指定
3.继承时必须先调用父类的归档解档方法
eg:建一个Person类
.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface Person : NSObject
@property (strong, nonatomic)UIImage *avatar;
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) NSInteger age;
@end
.m
#import "Person.h"
@implementation Person
//解档
- (id)initWithCoder:(NSCoder *)aDecoder
{
if ([super init])
{
self.avatar = [aDecoder decodeObjectForKey:@"avatar"];
self.name = [aDecoder decodeObjectForKey:@"name"];
self.age = [aDecoder decodeIntegerForKey:@"age"];
}
return self;
}
//归档
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.avatar forKey:@"avatar"];
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeInteger:self.age forKey:@"age"];
}
@end
存入person:
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/person.plist"];
Person *person = [[Person alloc] init];
person.avatar = [UIImage imageNamed:@"ss"];
person.name = @"小白";
person.age = 22;
[NSKeyedArchiver archiveRootObject:person toFile:path];
读取person:
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/person.plist"];
Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
if (person)
{
NSLog(@"名字是:%@",person.name);
NSLog(@"年龄是:%ld",person.age);
}
3.FMDB
大量数据的存储我们一般用FMDB,比如聊天中的信息一般是用FMDB存储的,FMDB是在sqlite的基础上进行封装的.
一般我们在使用时会对FMDB进行再次封装,我们对数据的操作一般也就是增,删,改,查。将fmdb 封装成一个工具类。将需要存储的信息封装成一个model。代码如下:
PersonInfoModel.h
#import <Foundation/Foundation.h>
@interface PersonInfoModel : NSObject
@property (nonatomic,strong)NSString *userName;
@property (nonatomic,strong)NSString *headImage;
@property (nonatomic,strong)NSString *nickname;
@property (nonatomic,assign)NSInteger haveMoney;
@end
FmdbTool.h
#import <Foundation/Foundation.h>
@class FMDatabase;
@class PersonInfoModel;
@interface FmdbTool : NSObject
@property (nonatomic, strong) FMDatabase *db;
+ (instancetype)shareManager;
//查找
- (PersonInfoModel *)selectInfoWithUserName:(NSString *)username;
//增加
- (void)saveInfoWithPersonalModel:(PersonInfoModel *)model;
//删除
- (void)deleteInfoWithUserName:(NSString *)username;
//改
- (void)updateInfoWithPersonModel:(PersonInfoModel *)model;
@end
FmdbTool.m
#import "FmdbTool.h"
#import "FMDB.h"
#import "PersonInfoModel.h"
static FmdbTool *__help = nil;
@implementation FmdbTool
+ (instancetype)shareManager
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__help = [[FmdbTool alloc] init];
[__help createDataBase];
[__help createTableView];
});
return __help;
}
- (void)createDataBase
{
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/conversionInfo.sqlite"];
_db = [[FMDatabase alloc] initWithPath:path];
[_db open];
[_db close];
}
-(void)createTableView
{
NSString *sql = @"CREATE TABLE IF NOT EXISTS ConversionInfo(conversionInfo_id INTEGER PRIMARY KEY AUTOINCREMENT,userName TEXT,headImage TEXT,nickname TEXT,haveMoney INTEGER)";
if ([self.db open])
{
if ([_db executeUpdate:sql])
{
NSLog(@"创建表成功");
}
else
{
NSLog(@"创建表失败");
}
[self.db close];
}
else
{
NSLog(@"打开数据库失败");
}
}
- (PersonInfoModel *)selectInfoWithUserName:(NSString *)username
{
[_db open];
NSString *sql = [NSString stringWithFormat:@"SELECT * FROM ConversionInfo where userName='%@'",username];
FMResultSet *set = [_db executeQuery:sql];
PersonInfoModel *model = [[PersonInfoModel alloc] init];
while ([set next])
{
// int studentID = [set intForColumn:@"student_id"];
NSString *name = [set stringForColumn:@"userName"];
NSString *headImage = [set stringForColumn:@"headImage"];
NSString *nickname = [set stringForColumn:@"nickname"];
int haveMoney = [set intForColumn:@"haveMoney"];
model.userName = name;
model.headImage = headImage;
model.nickname = nickname;
model.haveMoney = haveMoney;
// NSDictionary *dictionary = [set resultDictionary];
// NSLog(@"%d,%@,%d,%@",studentID,name,age,dictionary);
}
[set close];
[_db close];
return model;
}
- (void)saveInfoWithPersonalModel:(PersonInfoModel *)model
{
[_db open];
NSString *sql = [NSString stringWithFormat:@"INSERT INTO ConversionInfo(userName, headImage, nickname,haveMoney) VALUES('%@','%@','%@',%ld)",model.userName,model.headImage,model.nickname,(long)model.haveMoney];
// NSString *sql = @"insert into app (_id,updated,cover,title) values (?,?,?,?)"
BOOL result = [_db executeUpdate:sql];
// BOOL result = [_db executeUpdate:@"%@",sql];
if (result)
{
NSLog(@"插入成功");
}
else
{
NSLog(@"插入失败");
}
[_db close];
}
- (void)deleteInfoWithUserName:(NSString *)username
{
[_db open];
NSString *sql = [NSString stringWithFormat:@"DELETE FROM ConversionInfo where userName='%@'",username];
BOOL result = [_db executeUpdate:sql];
if (result)
{
NSLog(@"删除成功");
}
else
{
NSLog(@"删除失败");
}
[_db close];
}
- (void)updateInfoWithPersonModel:(PersonInfoModel *)model
{
[_db open];
NSString *sql = [NSString stringWithFormat:@"update ConversionInfo set headImage='%@',nickname='%@',haveMoney=%ld where userName='%@'",model.headImage,model.nickname,(long)model.haveMoney,model.userName];
BOOL result = [_db executeUpdate:sql];
if (result)
{
NSLog(@"修改成功");
}
else
{
NSLog(@"修改失败");
}
[_db close];
}
@end
- CoreData 这个基本很少用
5.KeyChain
iOS keychain 是一个相对独立的空间,保存到keychain钥匙串中的信息不会因为卸载/重装app而丢失, 。相对于NSUserDefaults、plist文件保存等一般方式,keychain保存更为安全。所以我们会用keyChain保存一些私密信息,比如密码、证书、设备唯一码(把获取到用户设备的唯一ID 存到keychain 里面这样卸载或重装之后还可以获取到id,保证了一个设备一个ID)等等。keychain是用SQLite进行存储的。用苹果的话来说是一个专业的数据库,加密我们保存的数据,可以通过metadata(attributes)进行高效的搜索。keychain适合保存一些比较小的数据量的数据,如果要保存大的数据,可以考虑文件的形式存储在磁盘上,在keychain里面保存解密这个文件的密钥。
如果需要在应用里使用使用keyChain,我们需要导入Security.framework ,keychain的操作接口声明在头文件SecItem.h里。但是自己需要自己封装有点复杂。我这边使用的是第三方封装好的SSKeychain。导入即可使用
保存密码:
if ( [SAMKeychain setPassword:@"白首如新,倾盖如故" forService:@"test" account:@"testAccount"])
{
NSLog(@"keyChin存储成功");
}
else
{
NSLog(@"keyChain存储失败");
}
读取密码
NSString *passWord = [SAMKeychain passwordForService:@"test" account:@"testAccount"];
NSLog(@"passWord is %@",passWord);