最全iOS数据存储方法介绍:FMDB,SQLite3 ,Core Data,Plist,Preference偏好设置,NSKeyedArchiver归档

转载请注明本文地址:http://www.jianshu.com/p/e88880be794fgit

目的

项目准备运用的Core Data进行本地数据存储,原本打算只写一下Core Data的,不过既然说到了数据存储,干脆来个数据存储基础大总结!本文将对如下几个模块进行叙述。github

  1. 沙盒
  2. Plist
  3. Preference偏好设置
  4. NSKeyedArchiver归档 / NSKeyedUnarchiver解档
  5. SQLite3的使用
  6. FMDB
  7. Core Data

Realm的文档很详细,这篇文章就不写了: Realm的使用方法sql

下图是Core Data堆栈的图示,在这里是为了作文章的封面图片,后文会介绍Core Data的使用方法。数据库

Core Data

1、沙盒

iOS本地化存储的数据保存在沙盒中, 而且每一个应用的沙盒是相对独立的。每一个应用的沙盒文件结构都是相同的,以下图所示: 编程

沙盒目录

Documents:iTunes会备份该目录。通常用来存储须要持久化的数据。swift

Library/Caches:缓存,iTunes不会备份该目录。内存不足时会被清除,应用没有运行时,可能会被清除,。通常存储体积大、不须要备份的非重要数据。数组

Library/Preference:iTunes同会备份该目录,能够用来存储一些偏好设置。缓存

tmp: iTunes不会备份这个目录,用来保存临时数据,应用退出时会清除该目录下的数据。安全

如何拿到每一个文件夹的路径呢?我这里就只说最好的一种方法。多线程

// 这个方法返回的是一个数组,iOS中这个数组其实只有一个元素,因此咱们能够用lastObject或lastObject来拿到Documents目录的路径
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
// 获得Document目录下的test.plist文件的路径
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];
复制代码

上面的方法有三个参数,这里分别说一下。

NSDocumentDirectory: 第一个参数表明要查找哪一个文件,是一个枚举,点进去看一下发现有不少选择,为了直接找到沙盒中的Documents目录,咱们通常用NSDocumentDirectory。

NSUserDomainMask: 也是一个枚举,表示搜索的范围限制于当前应用的沙盒目录。咱们通常就选择NSUserDomainMask。

YES (expandTilde): 第三个参数是一个BOOL值。iOS中主目录的全写形式是/User/userName,这个参数填YES就表示全写,填NO就是写成‘‘~’’,咱们通常填YES。

根据上面的文字,你应用能够知道如何拿到Library/Caches目录下的文件路径了吧?没错,就是这样:

//获取Library/Caches目录路径
NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject; 
//获取Library/Caches目录下的test.data文件路径
NSString *filePath = [path stringByAppendingPathComponent:@"test.data"];
复制代码
//获取temp路径
NSString *tmp= NSTemporaryDirectory();
//获取temp下test.data文件的路径
NSString *filePath = [tmp stringByAppendingPathComponent:@"test.data"];
复制代码

因此,若是程序中有须要长时间持久化的数据,就选择Documents,若是有体积大可是并不重要的数据,就能够选择交给Library,而临时没用的数据固然是放到temp。至于Preference则能够用来保存一些设置类信息,后面会讲到偏好设置的使用方法。

推荐一个好用的分类工具集合**WHKit**,能够直接使用分类方法拿到文件路径。

//一行代码搞定
NSString *filePath = [@"test.plist" appendDocumentPath];

//使用WHKit,上面这一行代码等同于下面这两行。

NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];
复制代码

2、Plist存储

Plist文件的Type能够是字典NSDictionary或数组NSArray,也就是说能够把字典或数组直接写入到文件中。 NSString、NSData、NSNumber等类型,也可使用writeToFile:atomically:方法直接将对象写入文件中,只是Type为空。

下面就举个例子来看一下如何使用Plist来存储数据。

//准备要保存的数据
NSDictionary *dict = [NSDictionary dictionaryWithObject:@"first" forKey:@"1"];

//获取路径,你也能够利用上面说WHKit更优雅的拿到路径
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];

//写入数据
[dict writeToFile:filePath atomically:YES];
复制代码

上面的代码运行以后,在应用沙盒的Documents中就建立了一个plist文件,而且已经写入数据保存。

写入plist

数据存储了,那么如何读取呢?

//拿到plist文件路径,你也能够利用上面说WHKit更优雅的拿到路径
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];

//解析数据,log出的结果为first,读取成功
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSString *result = dict[@"1"];
NSLog(@"%@",result);
复制代码

上面这段代码就读出了plist种的数据。

3、Preference偏好设置

偏好设置的使用很是方便快捷,咱们通常使用它来进行一些设置的记录,好比用户名,开关是否打开等设置。 Preference是经过NSUserDefaults来使用的,是经过键值对的方式记录设置。下面举个例子。

利用NSUserDefaults判断APP是否是首次启动。

// 启动的时候判断key有没有value,若是有,说明已经启动过了,若是没有,说明是第一次启动
if (![[NSUserDefaults standardUserDefaults] valueForKey:@"first"]) {
        
    //若是是第一次启动,就运用偏好设置给key设置一个value
    [[NSUserDefaults standardUserDefaults] setValue:@"start" forKey:@"first"];
    NSLog(@"是第一次启动");
        
} else {
    NSLog(@"不是第一次启动");
}
复制代码

经过键值对的方式很是easy的保存了数据。

**注意:**NSUserDefaults能够存储的数据类型包括:NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary。若是要存储其余类型,则须要转换为前面的类型,才能用NSUserDefaults存储。

下面的例子是用NSUserDefaults存储图片,须要先把图片转换成NSData类型。

UIImage *image=[UIImage imageNamed:@"photo"];
//UIImage对象转换成NSData
NSData *imageData = UIImageJPEGRepresentation(image, 100);

//偏好设置能够保存NSData,可是不能保存UIimage
[[NSUserDefaults standardUserDefaults] setObject:imageData forKey:@"image"];
    
// 读出data
NSData *getImageData = [[NSUserDefaults standardUserDefaults] dataForKey:@"image"];
//NSData转换为UIImage
UIImage *Image = [UIImage imageWithData:imageData];
复制代码

NSUserDefaults是否是很好用!不过有没有以为每次都写[NSUserDefaults standardUserDefaults]有点烦,那么又到了推荐环节,好用的分类工具**WHKit**,这个工具中有许多好用的宏定义,其中一个就是KUSERDEFAULT,等同于[NSUserDefaults standardUserDefaults]。 **WHKit**中还有更多好用的宏定义等着你!

4、NSKeyedArchiver归档 / NSKeyedUnarchiver解档

归档和解档会在写入、读出数据以前进行序列化、反序列化,数据的安全性相对高一些。 NSKeyedArchiver能够有三个使用情景。

1.对单个简单对象进行归档/解档

与plist差很少,对于简单的数据进行归档,直接写入文件路径。

//获取归档文件路径
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test"];

//对字符串@”test”进行归档,写入到filePath中
[NSKeyedArchiver archiveRootObject:@"test" toFile:filePath];

//根据保存数据的路径filePath解档数据
NSString *result = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
//log结构为@”test”,就是上面归档的数据
NSLog(@"%@",result);
复制代码

固然,也能够存储NSArray,NSDictionary等对象。

2.对多个对象进行归档/解档

这种状况能够一次保存多种不一样类型的数据,最终使用的是与plist相同的writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile来写入数据。

//获取归档路径
    NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
    NSString *filePath = [documentPath stringByAppendingPathComponent:@"test"];
    
    //用来承载数据的NSMutableData
    NSMutableData *data = [[NSMutableData alloc] init];
    //归档对象
    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
    
    //将要被保存的三个数据
    NSString *name = @"jack";
    int age = 17;
    double height = 1.78;
    
    //运用encodeObject:方法归档数据
    [archiver encodeObject:name forKey:@"name"];
    [archiver encodeInt:age forKey:@"age"];
    [archiver encodeDouble:height forKey:@"height"];
    //结束归档
    [archiver finishEncoding];
    //写入数据(存储数据)
    [data writeToFile:filePath atomically:YES];
    
    
    //NSMutableData用来承载解档出来的数据
    NSMutableData *resultData = [[NSMutableData alloc] initWithContentsOfFile:filePath];
    //解档对象
    NSKeyedUnarchiver *unArchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:resultData];
    
    //分别解档出三个数据
    NSString *resultName = [unArchiver decodeObjectForKey:@"name"];
    int resultAge = [unArchiver decodeIntForKey:@"age"];
    double resultHeight = [unArchiver decodeDoubleForKey:@"height"];
    //结束解档
    [unArchiver finishDecoding];
    //成功打印出结果,说明成功归档解档
    NSLog(@"name = %@, age = %d, height = %.2f",resultName,resultAge,resultHeight);
复制代码

3.归档保存自定义对象

我认为这是归档最常使用的情景。 定义一个Person类,若是想对person进行归档解档,首先要让Person遵照协议。

/********Person.h*********/

#import <Foundation/Foundation.h>

//遵照NSCoding协议
@interface Person : NSObject<NSCoding>

@property (nonatomic, copy) NSString *name;

@property (nonatomic, assign) NSInteger age;

//自定义的归档保存数据的方法
+(void)savePerson:(Person *)person;

//自定义的读取沙盒中解档出的数据
+(Person *)getPerson;

@end
复制代码

NSCoding协议有2个方法:

  • (void)encodeWithCoder:(NSCoder *)aCoder 归档时调用这个方法,在方法中使用encodeObject:forKey:归档变量。
  • (instancetype)initWithCoder:(NSCoder *)aDecoder 解档时调用这个方法,在方法中石油decodeObject:forKey读出变量。
/******Person.m*******/

#import "Person.h"

@implementation Person

//归档,Key建议使用宏代替,这里就不使用了
- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:self.name forKey:@"name"];
    [aCoder encodeInteger:self.age forKey:@"age"];
}

//解档
-(instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self=[super init]) {
        self.name = [aDecoder decodeObjectForKey:@"name"];
        self.age = [aDecoder decodeIntegerForKey:@"age"];
    }
    return self;
}

//类方法,运用NSKeyedArchiver归档数据
+(void)savePerson:(Person *)person {
    NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
    NSString *path=[docPath stringByAppendingPathComponent:@"Person.plist"];
    [NSKeyedArchiver archiveRootObject:person toFile:path];
}

//类方法,使用NSKeyedUnarchiver解档数据
+(Person *)getPerson {
    NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
    NSString *path=[docPath stringByAppendingPathComponent:@"Person.plist"];
    Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
    return person;
}

@end
复制代码

下面就能够在须要的地方归档或解档Person对象。

/*******ViewController.m*******/

    //建立Person对象
    Person *person = [Person new];
    person.name = @"jack";
    person.age = 17;
    //归档保存数据
    [Person savePerson:person];
    //解档拿到数据
    Person *resultPerson = [Person getPerson];
    //打印出结果,证实归档解档成功
    NSLog(@"name = %@, age = %ld",resultPerson.name,resultPerson.age);
复制代码

5、SQLite3的使用

一、首先须要添加库文件libsqlite3.0.tbd 二、导入头文件#import <sqlite3.h> 三、打开数据库 四、建立表 五、对数据表进行增删改查操做 六、关闭数据库

上代码以前,有些问题你须要了解。

  • SQLite 不区分大小写,但也有须要注意的地方,例如GLOB 和 glob 具备不一样做用。

  • SQLite3有5种基本数据类型 text、integer、float、boolean、blob

  • SQLite3是无类型的,在建立的时候你能够不声明字段的类型,不过仍是建议加上数据类型

create table t_student(name, age);
复制代码
create table t_student(name text, age integer);
复制代码

下面的代码就是SQLite3的基本使用方法,带有详细注释。 代码中用到了一个Student类,这个类有两个属性name和age。

/****************sqliteTest.m****************/

#import "sqliteTest.h"
//1.首先导入头文件
#import <sqlite3.h>

//2.数据库
static sqlite3 *db;

@implementation sqliteTest

/** 3.打开数据库 */
+ (void)openSqlite {
    
    //数据库已经打开
    if (db != nil) {
        NSLog(@"数据库已经打开");
        return;
    }
    
    //建立数据文件路径
    NSString *string = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
    NSString *path = [string stringByAppendingPathComponent:@"Student.sqlite"];
    NSLog(@"%@",path);
    
    //打开数据库
    int result = sqlite3_open(path.UTF8String, &db);
    
    if (result == SQLITE_OK) {
        NSLog(@"数据库打开成功");
    } else {
        NSLog(@"数据库打开失败");
    }
}

/** 4.建立表 */
+ (void)createTable {
    
    //建立表的SQLite语句,其中id是主键,not null 表示在表中建立纪录时这些字段不能为NULL
    NSString *sqlite = [NSString stringWithFormat:@"create table if not exists t_student (id integer primary key autoincrement, name text not null, age integer)"];
    
    //用来记录错误信息
    char *error = NULL;
    
    //执行SQLite语句
    int result = sqlite3_exec(db, sqlite.UTF8String, nil, nil, &error);
    
    if (result == SQLITE_OK) {
        NSLog(@"建立表成功");
    }else {
        NSLog(@"建立表失败");
    }
}

/** 5.添加数据 */
+ (void)addStudent:(Student *)stu {
    
    //增添数据的SQLite语句
    NSString *sqlite = [NSString stringWithFormat:@"insert into t_student (name,age) values ('%@','%ld')",stu.name,stu.age];
    char *error = NULL;
    
    int result = sqlite3_exec(db, [sqlite UTF8String], nil, nil, &error);
    
    if (result == SQLITE_OK) {
        NSLog(@"添加数据成功");
    } else {
        NSLog(@"添加数据失败");
    }
}

/** 6.删除数据 */
+ (void)deleteStuWithName:(NSString *)name {
    
    //删除特定数据的SQLite语句
    NSString *sqlite = [NSString stringWithFormat:@"delete from t_student where name = '%@'",name];
    char *error = NULL;
    
    int result = sqlite3_exec(db, sqlite.UTF8String, nil, nil, &error);
    
    if (result == SQLITE_OK) {
        NSLog(@"删除数据成功");
    } else {
        NSLog(@"删除数据失败");
    }
}

/** 7.更改数据 */
+ (void)upDateWithStudent:(Student *)stu WhereName:(NSString *)name {
    
    //更新特定字段的SQLite语句
    NSString *sqlite = [NSString stringWithFormat:@"update t_student set name = '%@', age = '%ld' where name = '%@'",stu.name,stu.age,name];
    char *error = NULL;
    
    int result = sqlite3_exec(db, sqlite.UTF8String, nil, nil, &error);
    
    if (result == SQLITE_OK) {
        NSLog(@"修改数据成功");
    } else {
        NSLog(@"修改数据失败");
    }
}

/** 8.根据条件查询 */
+ (NSMutableArray *)selectWithAge:(NSInteger)age {
    
    //可变数组,用来保存查询到的数据
    NSMutableArray *array = [NSMutableArray array];
    
    //查询全部数据的SQLite语句
    NSString *sqlite = [NSString stringWithFormat:@"select * from t_student where age = '%ld'",age];
    
    //定义一个stmt存放结果集
    sqlite3_stmt *stmt = NULL;
    
    //执行
    int result = sqlite3_prepare(db, sqlite.UTF8String, -1, &stmt, NULL);
    
    if (result == SQLITE_OK) {
        NSLog(@"查询成功");
        
        //遍历查询到的全部数据,并添加到上面的数组中
        while (sqlite3_step(stmt) == SQLITE_ROW) {
            Student *stu = [[Student alloc] init];
            //得到第1列的姓名,第0列是id
            stu.name = [NSString stringWithUTF8String:(const char *)sqlite3_column_text(stmt, 1)];
            //得到第2列的年龄
            stu.age = sqlite3_column_int(stmt, 2);
            [array addObject:stu];
        }
    } else {
        NSLog(@"查询失败");
    }
    
    //销毁stmt,防止内存泄漏
    sqlite3_finalize(stmt);
    return array;
}

/** 9.查询全部数据 */
+ (NSMutableArray *)selectStudent {
    
    //可变数组,用来保存查询到的数据
    NSMutableArray *array = [NSMutableArray array];
    
    //查询全部数据的SQLite语句
    NSString *sqlite = [NSString stringWithFormat:@"select * from t_student"];
    
    //定义一个stmt存放结果集
    sqlite3_stmt *stmt = NULL;
    
    //执行
    int result = sqlite3_prepare(db, sqlite.UTF8String, -1, &stmt, NULL);
    
    if (result == SQLITE_OK) {
        NSLog(@"查询成功");
        
        //遍历查询到的全部数据,并添加到上面的数组中
        while (sqlite3_step(stmt) == SQLITE_ROW) {
            Student *stu = [[Student alloc] init];
            //得到第1列的姓名,第0列是id
            stu.name = [NSString stringWithUTF8String:(const char *)sqlite3_column_text(stmt, 1)];
            //得到第2列的年龄
            stu.age = sqlite3_column_int(stmt, 2);
            [array addObject:stu];
        }
    } else {
        NSLog(@"查询失败");
    }
    
    //销毁stmt,防止内存泄漏
    sqlite3_finalize(stmt);
    return array;
}

/** 10.删除表中的全部数据 */
+ (void)deleteAllData {
    NSString *sqlite = [NSString stringWithFormat:@"delete from t_student"];
    char *error = NULL;
    int result = sqlite3_exec(db, sqlite.UTF8String, nil, nil, &error);
    if (result == SQLITE_OK) {
        NSLog(@"清除数据库成功");
    } else {
        NSLog(@"清除数据库失败");
    }
}

/** 11.删除表 */
+ (void)dropTable {
    NSString *sqlite = [NSString stringWithFormat:@"drop table if exists t_student"];
    char *error = NULL;
    int result = sqlite3_exec(db, sqlite.UTF8String, nil, nil, &error);
    if (result == SQLITE_OK) {
        NSLog(@"删除表成功");
    } else {
        NSLog(@"删除表失败");
    }
}

/** 12.关闭数据库 */
+ (void)closeSqlite {
    int result = sqlite3_close(db);
    if (result == SQLITE_OK) {
        NSLog(@"数据库关闭成功");
    } else {
        NSLog(@"数据库关闭失败");
    }
}
复制代码

附上SQLite的基本语句

  • 建立表: create table if not exists 表名 (字段名1, 字段名2...);

create table if not exists t_student (id integer primary key autoincrement, name text not null, age integer)

  • 增长数据: insert into 表名 (字段名1, 字段名2, ...) values(字段1的值, 字段2的值, ...);

insert into t_student (name,age) values (@"Jack",@17);

  • 根据条件删除数据: delete from 表名 where 条件;

delete from t_student where name = @"Jack";

  • 删除表中全部的数据: delete from 表名

delete from t_student

  • 根据条件更改某个数据: update 表名 set 字段1 = '值1', 字段2 = '值2' where 字段1 = '字段1的当前值'

update t_student set name = 'lily', age = '16' where name = 'Jack'

  • 根据条件查找: select * from 表名 where 字段1 = '字段1的值'

select * from t_student where age = '16'

  • 查找全部数据: select * from 表名

select * from t_student

  • 删除表: drop table 表名

drop table t_student

  • 排序查找: select * from 表名 order by 字段

select * from t_student order by age asc (升序,默认) select * from t_student order by age desc (降序)

  • 限制: select * from 表名 limit 值1, 值2

select * from t_student limit 5, 10 (跳过5个,一共取10个数据)

SQLite3还有事务方面的使用,这里就不作说明了,下面的FMDB中会有事务的使用。

6、FMDB

FMDB封装了SQLite的C语言API,更加面向对象。 首先须要明确的是FMDB中的三个类。

FMDatabase:能够理解成一个数据库。

FMResultSet:查询的结果集合。

FMDatabaseQueue:运用多线程,可执行多个查询、更新。线程安全。

FMDB基本语法

查询:executeQuery: SQLite语句命令。

[db executeQuery:@"select id, name, age from t_person"]
复制代码

其他的操做都是“更新”:executeUpdate: SQLite语句命令。

// CREATE, UPDATE, INSERT, DELETE, DROP,都使用executeUpdte
[db executeUpdate:@"create table if not exists t_person (id integer primary key autoincrement, name text, age integer)"]
复制代码

FMDB的基本使用

在项目中导入FMDB框架和sqlite3.0.tbd,导入头文件。

1. 打开数据库,并建立表

初始化FMDatabase:FMDatabase *db = [FMDatabase databaseWithPath:filePath]; 其中的filePath是提早准备好要存放数据的路径。

打开数据库:[db open]

建立数据表:[db executeUpdate:@"create table if not exists t_person (id integer primary key autoincrement, name text, age integer)"];

#import "ViewController.h"
#import <FMDB.h>

@interface ViewController ()

@end

@implementation ViewController{
    FMDatabase *db;
}

- (void)openCreateDB {

    //存放数据的路径
    NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
    NSString *filePath = [path stringByAppendingPathComponent:@"person.sqlite"];
    
    //初始化FMDatabase
    db = [FMDatabase databaseWithPath:filePath];
    
    //打开数据库并建立person表,person中有主键id,姓名name,年龄age
    if ([db open]) {
        BOOL success = [db executeUpdate:@"create table if not exists t_person (id integer primary key autoincrement, name text, age integer)"];
        if (success) {
            NSLog(@"创表成功");
        }else {
            NSLog(@"建立表失败");
        }
    } else {
        NSLog(@"打开失败");
    }
}
复制代码

2. 插入数据

运用executeUpdate方法执行插入数据命令: [db executeUpdate:@"insert into t_person(name,age) values(?,?)",@"jack",@17]

-(void)insertData {
    BOOL success = [db executeUpdate:@"insert into t_person(name,age) values(?,?)",@"jack",@17];
    if (success) {
        NSLog(@"添加数据成功");
    } else {
        NSLog(@"添加数据失败");
    }
}
复制代码

3. 删除数据

删除姓名为lily的数据:[db executeUpdate:@"delete from t_person where name = 'lily'"]

-(void)deleteData {
    BOOL success = [db executeUpdate:@"delete from t_person where name = 'lily'"];
    if (success) {
        NSLog(@"删除数据成功");
    } else {
        NSLog(@"删除数据失败");
    }
}
复制代码

4. 修改数据

把年龄为17岁的数据,姓名改成lily:[db executeUpdate:@"update t_person set name = 'lily' where age = 17"]

-(void)updateData {
    BOOL success = [db executeUpdate:@"update t_person set name = 'lily' where age = 17"];
    if (success) {
        NSLog(@"更新数据成功");
    } else {
        NSLog(@"更新数据失败");
    }
}
复制代码

5. 查询数据

执行查询语句,用FMResultSet接收查询结果:FMResultSet *set = [db executeQuery:@"select id, name, age from t_person"]

遍历查询结果:[set next]

拿到每条数的姓名:NSString *name = [set stringForColumnIndex:1];

也能够这样拿到每条数据的姓名:NSString *name = [result stringForColumn:@"name"];

FMResultSet *set = [db executeQuery:@"select id, name, age from t_person"];
    while ([set next]) {
        int ID = [set intForColumnIndex:0];
        NSString *name = [set stringForColumnIndex:1];
        int age = [set intForColumnIndex:2];
        NSLog(@"%d,%@,%d",ID,name,age);
    }
}
复制代码

6. 删除表

删除指定表:[db executeUpdate:@"drop table if exists t_person"]

-(void)dropTable {
    BOOL success = [db executeUpdate:@"drop table if exists t_person"];
    if (success) {
        NSLog(@"删除表成功");
    } else {
        NSLog(@"删除表失败");
    }
}
复制代码

FMDatabaseQueue基本使用

FMDatabase是线程不安全的,当FMDB数据存储想要使用多线程的时候,FMDatabaseQueue就派上用场了。

初始化FMDatabaseQueue的方法与FMDatabase相似

//数据文件路径
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [path stringByAppendingPathComponent:@"student.sqlite"];
    
//初始化FMDatabaseQueue
FMDatabaseQueue *dbQueue = [FMDatabaseQueue databaseQueueWithPath:filePath];
复制代码

在FMDatabaseQueue中执行命令的时候也是很是方便,直接在一个block中进行操做

-(void)FMDdatabaseQueueFunction {
    
    NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
    //数据文件路径
    NSString *filePath = [path stringByAppendingPathComponent:@"student.sqlite"];
    //初始化FMDatabaseQueue
    FMDatabaseQueue *dbQueue = [FMDatabaseQueue databaseQueueWithPath:filePath];
    
    //在block中执行SQLite语句命令
    [dbQueue inDatabase:^(FMDatabase * _Nonnull db) {
        //建立表
        [db executeUpdate:@"create table if not exists t_student (id integer primary key autoincrement, name text, age integer)"];
        //添加数据
        [db executeUpdate:@"insert into t_student(name,age) values(?,?)",@"jack",@17];
        [db executeUpdate:@"insert into t_student(name,age) values(?,?)",@"lily",@16];
        //查询数据
        FMResultSet *set = [db executeQuery:@"select id, name, age from t_student"];
        //遍历查询到的数据
        while ([set next]) {
            int ID = [set intForColumn:@"id"];
            NSString *name = [set stringForColumn:@"name"];
            int age = [set intForColumn:@"age"];
            NSLog(@"%d,%@,%d",ID,name,age);
        }
        
    }];
}
复制代码

FMDB中的事务

什么是事务?

事务(Transaction)是不可分割的一个总体操做,要么都执行,要么都不执行。 举个例子,幼儿园有20位小朋友由老师组织出去春游,返校的时候,全部人依次登上校车,这时候若是有一位小朋友没有上车,车也是不能出发的。因此哪怕19人都上了车,也等于0人上车。20人是一个总体。 固然这个例子可能不是很精准。

FMDB中有事务的回滚操做,也就是说,当一个总体事务在执行的时候出了一点小问题,则执行回滚,以后这套事务中的全部操做将总体无效。

下面代码中,利用事务循环向数据库中添加2000条数据,假如在添加的过程当中出现了一些问题,因为执行了*rollback = YES的回滚操做,数据库中一个数据都不会出现。 若是第2000条数据的添加出了问题,哪怕以前已经添加了1999条数据,因为执行了回滚,数据库中依然一个数据都没有。

//数据库路径
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [path stringByAppendingPathComponent:@"student.sqlite"];

//初始化FMDatabaseQueue
FMDatabaseQueue *dbQueue = [FMDatabaseQueue databaseQueueWithPath:filePath];

//FMDatabaseQueue的事务inTransaction
[dbQueue inTransaction:^(FMDatabase * _Nonnull db, BOOL * _Nonnull rollback) {
        //建立表
        [db executeUpdate:@"create table if not exists t_student (id integer primary key autoincrement, name text, age integer)"];
        //循环添加2000条数据
        for (int i = 0; i < 2000; i++) {
            BOOL success = [db executeUpdate:@"insert into t_student(name,age) values(?,?)",@"jack",@(i)];
            //若是添加数据出现问题,则回滚
            if (!success) {
                //数据回滚
                *rollback = YES;
                return;
            }
        }
    }];
复制代码

7、Core Data

Core Data有着图形化的操做界面,而且是操做模型数据的,更加面向对象。当你了解并熟悉它的时候,相信你会喜欢上这个数据库存储框架。

利用Core Data快速实现数据存储

1. 图形化建立模型

建立项目的时候,勾选下图中的Use Core Data选项,工程中会自动建立一个数据模型文件。固然,你也能够在开发中本身手动建立。

自动建立模型文件

下图就是自动建立出来的文件

建立出来的文件

若是没有勾选,也能够在这里手动建立。

手动建立

点击Add Entity以后,至关一张数据表。表的名称本身在上方定义,注意首字母要大写。 在界面中还能够为数据实体添加属性和关联属性。

建立一个数据表

Core Data属性支持的数据类型以下

数据类型

编译以后,Xcode会自动生成Person的实体代码文件,而且文件不会显示在工程中,若是下图中右侧Codegen选择Manual/None,则Xcode就不会自动生成代码,咱们能够本身手动生成。

6.png

手动生成实体类代码,选中CoreDataTest.xcdatamodeld文件,而后在Mac菜单栏中选择Editor,以下图所示。一路Next就能够了。 若是没有选择Manual/None,依然进行手动建立的话,则会与系统自动建立的文件发生冲突,这点须要注意。 你也能够不要选择Manual/None,直接使用系统建立好的NSManagedObject,一样会有4个文件,只是在工程中是看不到的,使用的时候直接导入#import "Person+CoreDataClass.h"头文件就能够了。

手动建立NSManagedObject

手动建立出来的是这样4个文件

10.png

还要注意编程语言的选择,Swift或OC

编程语言

2.Core Data堆栈的介绍与使用

下面要作的就是对Core Data进行初始化,实现本地数据的保存。 须要用到的类有三个:

NSManagedObjectModel 数据模型的结构信息 NSPersistentStoreCoordinator 数据持久层和对象模型协调器 NSManagedObjectContext 模型managedObject对象的上下文

以下图所示,一个context内能够有多个模型对象,不过在大多数的操做中只存在一个context,而且全部的对象存在于那个context中。 对象和他们的context是相关联的,每一个被管理的对象都知道本身属于哪一个context,每一个context都知道本身管理着哪些对象。

Core Data从系统读或写的时候,有一个持久化存储协调器(persistent store coordinator),而且这个协调器在文件系统中与SQLite数据库交互,也链接着存放模型的上下文Context。

Core Data

下图是比较经常使用的方式:

Core Data堆栈

下面咱们来一步步实现Core Data堆栈的建立。

  • 首先在AppDelegate中定义一个NSManagedObjectModel属性。而后利用懒加载来建立NSManagedObjectModel对象。而且要注意建立时候的后缀用momd,代码以下:
//建立属性
@property (nonatomic, readwrite, strong) NSManagedObjectModel *managedObjectModel;

//懒加载
- (NSManagedObjectModel *)managedObjectModel {
    if (!_managedObjectModel) {
        //注意扩展名为 momd
        NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"CoreDataTest" withExtension:@"momd"];
        _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    }
    return _managedObjectModel;
}
复制代码
  • 建立协调器NSPersistentStoreCoordinator,一样的先在AppDelegate中来一个属性,而后懒加载。
//属性
@property (nonatomic, readwrite, strong) NSPersistentStoreCoordinator *persistentStoreCoordinator;

//懒加载
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    
    if (!_persistentStoreCoordinator) {
        
        //传入以前建立好了的Model
        _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
        
        //指定sqlite数据库文件
        NSURL *sqliteURL = [[self documentDirectoryURL] URLByAppendingPathComponent:@"CoreDataTest.sqlite"];
        
        //这个options是为了进行数据迁移用的,有兴趣的能够去研究一下。
        NSDictionary *options=@{NSMigratePersistentStoresAutomaticallyOption:@(YES),NSInferMappingModelAutomaticallyOption:@(YES)};
        NSError *error;
        
        [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                  configuration:nil
                                                            URL:sqliteURL
                                                        options:options
                                                          error:&error];
        if (error) {
            NSLog(@"建立协调器失败: %@", error.localizedDescription);
        }
    }
    return _persistentStoreCoordinator;
}


//获取document目录
- (nullable NSURL *)documentDirectoryURL {
    return [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject;
}
复制代码
  • 建立NSManagedObjectContext,一样的是属性+懒加载。
//属性
@property (nonatomic, readwrite, strong) NSManagedObjectContext *context;

//懒加载
- (NSManagedObjectContext *)context {
    if (!_context) {
        
        _context = [[NSManagedObjectContext alloc ] initWithConcurrencyType:NSMainQueueConcurrencyType];
        
        // 指定协调器
        _context.persistentStoreCoordinator = self.persistentStoreCoordinator;
    }
    return _context;
}
复制代码

3.运用Core Data对数据进行增删改查

  • 添加数据 使用NSEntityDesctiption类的一个方法建立NSManagedObject对象。参数一是实体类的名字,参数二是以前建立的Context。 为对象赋值,而后存储。
@implementation ViewController {
    NSManagedObjectContext *context;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //拿到managedObjectContext
    context = [AppDelegate new].context;
    
    //运用NSEntityDescription建立NSManagedObject对象
    Person *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:context];
    //为对象赋值
    person.name = @"Jack";
    person.age = 17;
    NSError *error;
    //保存到数据库
    [context save:&error];
}
复制代码
  • 查询数据 Core Data从数据库中查询数据,会用到三个类:

**NSFetchRequest:**一条查询请求,至关于 SQL 中的select语句 **NSPredicate:**谓词,指定一些查询条件,至关于 SQL 中的where **NSSortDescriptor:**指定排序规则,至关于 SQL 中的 order by

NSFetchRequest中有两个属性:

**predicate:**是NSPredicate对象 **sortDescriptors:**它是一个NSSortDescriptor数组,数组中前面的优先级比后面高。能够有多个排列规则。

//更多NSFetchRequest属性
    fetchLimit:结果集最大数,至关于 SQL 中的limit
    fetchOffset:查询的偏移量,默认为0
    fetchBatchSize:分批处理查询的大小,查询分批返回结果集
    entityName/entity:数据表名,至关于 SQL中的from
    propertiesToGroupBy:分组规则,至关于 SQL 中的group by
    propertiesToFetch:定义要查询的字段,默认查询所有字段
复制代码

设置好NSFetchRequest以后,调用NSManagedObjectContext的executeFetchRequest方法,就会返回结果集了。

//Xcode自动建立的NSManagedObject会生成fetchRequest方法,能够直接获得NSFetchRequest
    NSFetchRequest *fetchRequest = [Person fetchRequest];
    //也能够这样得到:[NSFetchRequest fetchRequestWithEntityName:@"Student"];
    
    //谓词
    fetchRequest.predicate = [NSPredicate predicateWithFormat:@"age == %@", @(16)];
    
    //排序
    NSArray<NSSortDescriptor *> *sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"age" ascending:YES]];
    
    fetchRequest.sortDescriptors = sortDescriptors;

    //运用executeFetchRequest方法获得结果集
    NSArray<Person *> *personResult = [context executeFetchRequest:fetchRequest error:nil];
复制代码

Xcode本身有一个NSFetchRequest的code snippet,“fetch”,出现的结果以下图。

snippet

NSFetchRequest的code snippet: “fetch”

  • 更新数据 更新数据比较简单,查询出来须要修改的数据以后,直接修改值,而后用context save就能够了。
for (Person *person in personResult) {
        //直接修改
        person.age = 26;
    }

//别忘了save一下
[context save:&error];
复制代码
  • 删除数据 查询出来须要删除的数据以后,调用 NSManagedObjectContext 的deleteObject方法就能够了。
for (Person *person in personResult) {
        //删除数据
        [context deleteObject:person];
    }
//别忘了save
[context save:&error];
复制代码

至此Core Data的基础内容就讲完了。


后记:

iOS中有多种数据持久化的方法,要根据具体情景选用最合适的技术。

推荐简单又好用的分类集合:WHKit github地址:github.com/remember17

相关文章
相关标签/搜索