iOS数据存储通常有以下五种方式:
1、Plist 2、NSUserDefaults 3、NSkeyedArchiver 4、SQLite 5、CoreData
等不及想看项目的同学点这儿demo
1、Plist
可被序列化的通常有以下几种类型:
NSString(含NSMutableString),NSArray(含NSMutableArray), NSDictionary(含NSMutableDictionary),NSData(含 NSMutableData),NSNumber,NSDate
使用方法
- 不在工程中新建plist文件
先创建一个plist的存储路径
NSString *docuPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *plistPath = [docuPath stringByAppendingPathComponent:@"hooyking.plist"];
以NSString保存示例
NSError *error = nil;
[@"张三" writeToFile:plistPath atomically:YES encoding:NSUTF8StringEncoding error:&error];
NSString数据取出
NSError *error = nil;
NSString *string = [NSString stringWithContentsOfFile:plistPath encoding:NSUTF8StringEncoding error:&error];
-
在工程中新建一个plist文件
以NSArray保存示例
NSString *customerPlistPath = [[NSBundle mainBundle] pathForResource:@"HK" ofType:@"plist"];
NSArray *nameArray = @[@"first",@"last"];
[nameArray writeToFile:customerPlistPath atomically:YES];
NSArray数据取出
NSArray *customerPlistArray = [NSArray arrayWithContentsOfFile:customerPlistPath];
NSLog(@"项目中新建的plist文件保存的数据%@",customerPlistArray);
2、NSUserDefaults
NSUserDefaults支持的数据类型通常有以下几种:
NSNumber(CGFloat、NSInteger、int、float、double), NSString(含NSMutableString),NSData(含NSMutableData,NSArray(含NSMutableArray),NSDictionary(含NSMutableDictionary),BOOL
- 基本数据类型
NSUserDefaults存储的对象全是不可变的,即使存进去的是可变的,取出来也是不可变的,如NSMutableArray存进去,取出来就是NSArray了。
以NSString保存示例
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
[ud setObject:self.textField.text forKey:@"userDefaultKey"];
[ud synchronize];
NSString数据取出
NSString *text = [ud objectForKey:userDefaultKeyStr];
NSLog(@"取出的数据%@",text);
- 自定义类型
使用NSUserDefaults保存自定义类型,自定义类型必须遵循NSCoding协议,下面以PersonModel示例。
PersonModel.h中遵循<NSCoding>
PersonModel.m中实现解归档
//归档
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeInteger:self.age forKey:@"age"];
}
//解档
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
self.name = [aDecoder decodeObjectForKey:@"name"];
self.age = [aDecoder decodeIntegerForKey:@"age"];
}
return self;
}
保存数据
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
PersonModel *model = [PersonModel new];
model.name = @"张哈哈";
model.age = 10;
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:model];
[ud setObject:data forKey:@"customerObjectKey"];
[ud synchronize];
取出数据
NSData *data = [ud objectForKey:@"customerObjectKey"];
PersonModel *model = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSLog(@"名字:%@--年龄:%zd",model.name,model.age);
3、NSKeyedArchiver
这里的后缀名用了.data,实际上用什么后缀名都无所谓的。
- 单个普通数据的归解档
文件路径
NSString *docuPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"归档文件路径:%@",docuPath);
NSString *singleCommonFilePath = [docuPath stringByAppendingPathComponent:@"singleCommon.data"];
归档
NSString *singleString = @"单个普通数据的归档看看如何?";
BOOL singleSuccess = [NSKeyedArchiver archiveRootObject:singleString toFile:singleCommonFilePath];
if (singleSuccess) {
NSLog(@"单个普通数据归档成功");
} else {
NSLog(@"单个普通数据归档失败");
}
解档
NSString *singleString = [NSKeyedUnarchiver unarchiveObjectWithFile:singleCommonFilePath];
NSLog(@"单个普通数据的解档:%@",singleString);
- 多个普通数据的归解档
文件路径
NSString *docuPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"归档文件路径:%@",docuPath);
NSString *multipleCommonFilePath = [docuPath stringByAppendingPathComponent:@"multipleCommon.data"];
归档
NSInteger age = 20;
NSString *name = @"张三";
NSArray *toies = @[@"柯尼赛格",@"百达斐丽",@"陆家嘴100套房"];
NSMutableData *mutableData = [[NSMutableData alloc] init];
NSKeyedArchiver *multipleKeyArchiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:mutableData];
[multipleKeyArchiver encodeInteger:age forKey:@"age"];
[multipleKeyArchiver encodeObject:name forKey:@"name"];
[multipleKeyArchiver encodeObject:toies forKey:@"toies"];
[multipleKeyArchiver finishEncoding];//完成归档
BOOL multipleSuccess = [mutableData writeToFile:multipleCommonFilePath atomically:YES];//写入文件
if (multipleSuccess) {
NSLog(@"多个普通数据归档成功");
} else {
NSLog(@"多个普通数据归档失败");
}
解档
NSMutableData *mutableData = [NSMutableData dataWithContentsOfFile:multipleCommonFilePath];
NSKeyedUnarchiver *keyedUnarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:mutableData];
NSInteger age = [keyedUnarchiver decodeIntegerForKey:@"age"];
NSString *name = [keyedUnarchiver decodeObjectForKey:@"name"];
NSArray *toies = [keyedUnarchiver decodeObjectForKey:@"toies"];
[keyedUnarchiver finishDecoding];
NSLog(@"多个普通数据的解档:年龄:%zd--名字:%@--玩具:%@,%@,%@",age,name,toies[0],toies[1],toies[2]);
- 自定义对象的归解档
自定义对象的归档必须遵循NSCoding
协议,当然自定义对象还可以包含自定义对象,被包含自定义对象同样需要遵循NSCoding
协议,下面以CarModel
和EngineModel
示例,CarModel
包含EngineModel
对于自定义对象,不论他是只保存这个对象还是对象的array,dictionary,set,只要此对象遵循了NSCoding协议,都可进行归档
文件路径
NSString *docuPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"归档文件路径:%@",docuPath);
NSString *customerModelFilePath = [docuPath stringByAppendingPathComponent:@"car.data"];
归档
CarModel *car = [[CarModel alloc] init];
car.price = 50000000;
car.brand = @"布加迪威龙";
car.wheelArr = @[@"左前轮",@"右前轮",@"左后轮",@"右后轮"];
EngineModel *engine = [[EngineModel alloc] init];
engine.model = @"无敌旋风";
engine.cylinderNumber = 100;
car.engine = engine;
BOOL success = [NSKeyedArchiver archiveRootObject:car toFile:customerModelFilePath];
if (success) {
NSLog(@"自定义对象归档成功");
} else {
NSLog(@"自定义对象归档失败");
}
解档
CarModel *car = [NSKeyedUnarchiver unarchiveObjectWithFile:customerModelFilePath];
NSLog(@"自定义对象的解档:车价格:%.2f--车标:%@--车轮子:%@,%@,%@,%@--发动机型号:%@--发动机气缸数:%zd",
car.price,car.brand,car.wheelArr[0],car.wheelArr[1],car.wheelArr[2],car.wheelArr[3],car.engine.model,car.engine.cylinderNumber);
4.SQLite
SQLite存储类型通常有以下几种:
NULL NULL值
REAL 浮点型(如CGFloat,float,double,不过都需要转成NSNumber存)
INTEGER 整型(如NSInteger,int,不过都需要转成NSNumber存)
TEXT 文本类(如NSString)
BLOB 二进制(如图片、文件NSData
注意这个插入的数据那些都是对象,text对应NSString,blob对应NSData,integer对应NSNumber
通常我们不直接使用系统的SQLite,而是用FMDB
,它是对SQLite的封装,使用更加优美。
下面就详细说说FMDB的使用
数据库路径及初始化FMDatabase
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"documentPath:%@",path);
NSString *dbpath = [path stringByAppendingPathComponent:@"hooyking.db"];
FMDatabase *db = [FMDatabase databaseWithPath:dbpath];
self.myDb = db;
对表操作后记得一定要调用close方法
- 创建表
- (void)createTable {
if ([_myDb open]) {
//这儿创建了一个列有 name|sex|age|nickname|phoneNum|nativePlace|photo 的名字为personTable的表
BOOL result = [_myDb executeUpdate:@"create table if not exists personTable (name text, sex integer, age integer, nickname text, phoneNum text, nativePlace text, photo blob)"];
if (result) {
NSLog(@"创建表成功");
}
else {
NSLog(@"创建表失败");
}
[_myDb close];
}
}
- 插入数据
- (void)insertData {
if ([_myDb open]) {
BOOL result = [_myDb executeUpdate:@"insert into personTable (name, sex, age, nickname, phoneNum, nativePlace, photo) values (?,?,?,?,?,?,?)",@"JDX",[NSNumber numberWithInteger:1],[NSNumber numberWithInteger:18], @"hooyking", [NSNumber numberWithInteger:13888888888],@"sichuan",[NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"picture" ofType:@"jpg"]]];
if (result) {
NSLog(@"添加数据成功");
}
else {
NSLog(@"添加数据失败");
}
[_myDb close];
}
}
- 删除数据
- (void)deleteData {
//删除这个表里nickname为hooyking的所有数据,列没有删除,依然存在
if ([_myDb open]) {
BOOL result = [_myDb executeUpdate:@"delete from personTable where nickname = ?",@"hooyking"];
if (result) {
NSLog(@"删除数据成功");
}
else {
NSLog(@"删除数据失败");
}
[_myDb close];
}
}
- 修改数据
- (void)updateData {
if ([_myDb open]) {
BOOL result = [_myDb executeUpdate:@"update personTable set age = ? where nickname = ?",[NSNumber numberWithInteger:25],@"hooyking"];
if (result) {
NSLog(@"修改数据成功");
}
else {
NSLog(@"修改数据失败");
}
[_myDb close];
}
}
- 查询数据
- (void)selectData {
if ([_myDb open]) {
//查询多条数据
FMResultSet *res = [_myDb executeQuery:@"select name, age from personTable"];
while ([res next]) {
NSString *name = [res stringForColumn:@"name"];
NSInteger age = [res intForColumn:@"age"];
NSLog(@"姓名:%@----年龄:%ld",name,age);
}
//查询一条数据
NSLog(@"年龄为25的人:%@",[_myDb stringForQuery:@"select name from personTable where age = ?",@25]);
[_myDb close];
}
}
- 建多张表,插入,查询多个数据等
- (void)moreOperate {
if ([_myDb open]) {
//创建表
NSString *createSql = @"create table if not exists studentsTable1 (id integer, name text, sex integer);"
"create table if not exists studentsTable2 (id integer, name text, sex integer);"
"create table if not exists studentsTable3 (id integer, name text, sex integer);";
BOOL createResult = [_myDb executeStatements:createSql];
if (createResult) {
NSLog(@"创建多张表成功");
}
else {
NSLog(@"创建多张表失败");
}
//*********************************插入多条数据(这里涉及到线程安全)**************************************
//**********************方法一*********************
NSString *insertSql = @"insert into studentsTable1 (id, name, sex) values ('100', '张三', '1');"
"insert into studentsTable2 (id, name, sex) values ('200', '李四', '1');"
"insert into studentsTable3 (id, name, sex) values ('300', '如花', '0');";
BOOL insertResult = [_myDb executeStatements:insertSql];
if (insertResult) {
NSLog(@"插入数据成功");
}
else {
NSLog(@"插入数据失败");
}
//*********************方法二**********************
//看这个方法:moreQueue
//查询数据
NSString *selectSql = @"select * from studentsTable1;"
"select * from studentsTable2;"
"select * from studentsTable3;";
BOOL selectResult = [_myDb executeStatements:selectSql withResultBlock:^int(NSDictionary *dictionary) {
NSLog(@"moreOperate查询到的结果:%@", [[dictionary allValues] componentsJoinedByString:@","]);
return 0;
}];
if (selectResult) {
NSLog(@"查询成功");
}
else {
NSLog(@"查询失败");
}
[_myDb close];
}
}
- 多个数据插入线程安全
- (void)moreQueue {
//方法二里面线程安全又有两种方式,ok继续看
if ([_myDb open]) {
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *dbpath = [path stringByAppendingPathComponent:@"hooyking.db"];
FMDatabaseQueue *dbQueue = [FMDatabaseQueue databaseQueueWithPath:dbpath];
//第一种(一般就用这种就能保证线程安全了,那条数据有错,那么有错那条就不会插入进去,例如id改为i,自己看效果)
[dbQueue inDatabase:^(FMDatabase *db) {
[db executeUpdate:@"insert into studentsTable1 (id, name, sex) values (?,?,?)",[NSNumber numberWithInteger:500], @"王五", [NSNumber numberWithInteger:1]];
[db executeUpdate:@"insert into studentsTable2 (id, name, sex) values (?,?,?)",[NSNumber numberWithInteger:600], @"陆六", [NSNumber numberWithInteger:1]];
[db executeUpdate:@"insert into studentsTable3 (id, name, sex) values (?,?,?)",[NSNumber numberWithInteger:700], @"史真香", [NSNumber numberWithInteger:0]];
}];
//第二种事务(当插入数据有错时,直接取消将插入的数据,可以改下列试试,例如id改为i,自己看效果)
// [dbQueue inTransaction:^(FMDatabase *db, BOOL *rollback) {
// BOOL res1 = [db executeUpdate:@"insert into studentsTable1 (id, name, sex) values (?,?,?)",[NSNumber numberWithInteger:500], @"王五", [NSNumber numberWithInteger:1]];
// BOOL res2 = [db executeUpdate:@"insert into studentsTable2 (id, name, sex) values (?,?,?)",[NSNumber numberWithInteger:600], @"陆六", [NSNumber numberWithInteger:1]];
// BOOL res3 = [db executeUpdate:@"insert into studentsTable3 (id, name, sex) values (?,?,?)",[NSNumber numberWithInteger:700], @"史真香", [NSNumber numberWithInteger:0]];
// if (!res1 || !res2 || !res3) { //我这样写就是三条任何一条有错,这三条就一条都不插入,这个判断条件若是不写,那就会默认哪一条有错,那一条就不会加入,但是其他的正确的会插入
// *rollback = YES;
// }
// [db executeUpdate:@"insert into studentsTable3 (id, name, sex) values (?,?,?)",[NSNumber numberWithInteger:800], @"你真溜", [NSNumber numberWithInteger:0]];
// }];
//查询数据
NSString *selectSql = @"select * from studentsTable1;"
"select * from studentsTable2;"
"select * from studentsTable3;";
BOOL selectResult = [_myDb executeStatements:selectSql withResultBlock:^int(NSDictionary *dictionary) {
NSLog(@"moreQueue查询到的结果:%@", [[dictionary allValues] componentsJoinedByString:@","]);
return 0;
}];
if (selectResult) {
NSLog(@"查询成功");
}
else {
NSLog(@"查询失败");
}
[_myDb close];
}
}
对列的操作
添加列
- (void)addColumn {
if ([_myDb open]) {
//这儿添加了添加名字为temp类型为text的列
BOOL addColumnRes = [_myDb executeUpdate:@"alter table studentsTable1 add temp text"];
if (addColumnRes) {
NSLog(@"添加列成功");
}
else {
NSLog(@"添加列失败");
}
[_myDb close];
}
}
删除列
SQLite不支持alter对列进行修改与删除的方法来的,所以要删除列的替代方式为新建一个没有你要删除的列的表
第一步:create table testTable(id integer, name text, sex integer);创建一个新表testTable,这个表没有列temp了;
第二步:insert into testTable select id, name, sex from studentsTable1;这儿完成了将表studentsTable1列id name sex中的全部数据插入到了表testTable中;
第三步:drop table if exists studentsTable1;删除原来的表studentsTable1;
第四步:alter table testTable rename to studentsTable1;将testTable重命名为studentsTable1,若是你要修改列名,方式和删除一样,可自己操作一下。删除表
- (void)dropTable {
//销毁这张表(即这个表删除后就不存在了)
if ([_myDb open]) {
BOOL result = [_myDb executeUpdate:@"drop table if exists personTable"];
if (result) {
NSLog(@"销毁表成功");
}
else {
NSLog(@"销毁表失败");
}
[_myDb close];
}
}
5、CoreData
新建项目时可勾选Use Core Data,勾选之后创建的项目就自带CoreData了
若是不勾选,新建的项目就没有CoreData文件,想使用CoreData就要通过New File选择Data Model
建好之后为如下图所示
点击下方Add Entity添加实体
这儿我们添加Teacher与Student实体
右侧Relationship设置
Properties
transient
:设置当前属性是否只存在于内存,不被持久化到本地,如果设置为YES,关联关系属性就不参与持久化操作。transient设置为YES一般存储一些在内存中缓存的数据,如存储临时数据,当app杀死后数据不会存在本地,该选项默认NO。
optional
: 设置向MOC保存数据时,这个属性是否必须有值。设置为NO时,MOC进行操作时,若是无值,会失败并返回一个error,该选项默认YES。
Delete Rule
设置关联属性的删除规则。一共四个值:这儿一般选后三个值,当选No Action时,Xcode也会报警告,让设置一个关联关系。
No Action
:当前实体删除后对关联实体无任何操作,也不会将关联对象的关联属性指向nil,删除后使用关联对象的关联属性,可能会导致其他问题。
Nullify
:删除后会将关联实体的关联属性指向nil,delete Rule默认值。
Cascade
:删除当前对象后,会将与之关联的实体也一并删除。
Deny
:在删除当前实体时,如果当前对象还指向其他关联对象,则当前实体不能被删除。
Type
设置当前实体与关联实体的关联关系
To One
:当前实体只可持有1个关联实体(默认值)
To Many
:当前实体只可持有多个关联实体
Count
(只有选择To Many时才有
)
设置最小值与最大值,默认值不设置
Minimum
:最小值
Maximum
:最大值
Advanced
设置索引
indexed
: 设置当前属性是否是索引。添加索引后可以提升检索速度,但是对于删除操作,删除索引后其他地方还需要做出相应的变化,所以速度会比较慢,默认值NO。
Spotlight
Store in External Record File
: 当存储二进制文件时,如果遇到比较大的文件,是否存储在存储区之外。如果选择YES,存储文件大小超过1MB的文件,都会存储在存储区之外。否则大型文件存储在存储区内,会造成SQLite进行表操作时,效率受到影响,默认值NO。
设置好关系后创建模型文件
Xcode->Editor->Create NSManageredObject Subclass 勾选两个实体,创建好后会报错,在如下图里找到文件,移除后就没了
你可以在student属性里看到如此这般
@property (nullable, nonatomic, retain) NSSet<Teacher *> *teachers;
你可以在Teacher属性里看到如此这般
@property (nullable, nonatomic, retain) NSSet<Student *> *students;
- 创建CoreData数据库
- (void)createCoreDataLite {
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"CoreData" withExtension:@"momd"];//CoreData为你自定义的CoreData文件名,momd为固定的类型
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSPersistentStoreCoordinator *store = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"coreData.sqlite"];
NSLog(@"数据库地址%@",path);
NSURL *sqlURL = [NSURL fileURLWithPath:path];
NSError *error = nil;
[store addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:sqlURL options:nil error:&error];
if (error) {
NSLog(@"错误%@",error);
} else {
NSLog(@"创建数据库成功,如果已添加此数据库,就算再次调用也不用重复添加,直接使用以前的");
}
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
context.persistentStoreCoordinator = store;
self.context = context;
}
- 添加数据
- (void)insertData {
Student *student = [NSEntityDescription insertNewObjectForEntityForName:@"Student" inManagedObjectContext:self.context];
student.name = [NSString stringWithFormat:@"Student%d",arc4random()%100];
student.age = arc4random()%20;
student.sex = arc4random()%2 == 0 ? @"女" : @"男";
Teacher *teacher = [NSEntityDescription insertNewObjectForEntityForName:@"Teacher" inManagedObjectContext:self.context];
teacher.name = [NSString stringWithFormat:@"Teacher%d",arc4random()%100];
teacher.age = arc4random()%60;
teacher.students = [NSSet setWithArray:@[student]];
NSError *error = nil;
if ([self.context save:&error]) {
NSLog(@"插入成功");
} else {
NSLog(@"插入失败");
}
}
- 删除数据
- (void)deleteData {
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Teacher"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age < 50"];
request.predicate = predicate;
NSArray *deleteArray = [self.context executeFetchRequest:request error:nil];
for (Teacher *model in deleteArray) {
NSLog(@"老师的学生%@",model.students);
[self.context deleteObject:model];
}
NSError *error = nil;
if ([self.context save:&error]) {
NSLog(@"删除成功");
} else {
NSLog(@"删除失败%@",error);
}
}
- 更新数据
- (void)updateData {
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Teacher"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age < 45"];
request.predicate = predicate;
NSArray *updateArray = [self.context executeFetchRequest:request error:nil];
for (Teacher *model in updateArray) {
model.name = @"张三";
}
NSError *error = nil;
if ([self.context save:&error]) {
NSLog(@"更新成功");
} else {
NSLog(@"更新失败");
}
}
- 查询数据
- (void)findData {
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Teacher"];
//此处可写查询条件(更多条件设定可浏览器自行搜索)
// NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age < 50"];
// request.predicate = predicate;
//此处可写排序条件(这儿按年龄排序)
// NSSortDescriptor *ageSort = [NSSortDescriptor sortDescriptorWithKey:@"age"ascending:YES];
// request.sortDescriptors = @[ageSort];
NSArray *resultArray = [self.context executeFetchRequest:request error:nil];
self.dataMArray = [resultArray mutableCopy];
[self.tableView reloadData];
}