一、iOS常见的几种数据存储
1.plist列表(writeToFile:)
2.偏好设置(NSUserDefaults)
3.归档(NSCodeing、NSKeyedArchiver)
4.SQLite3(数据库、一般借助第三方库FMDB)
5.Core Data(对象型的数据库)
二、应用沙盒
1.简介
每一个应用都有自己的应用沙盒,也就是系统文件目录。每一个应用都必须呆在自己的应用沙盒中,不可以访问别的应用沙盒(iOS8已经允许访问其他应用沙盒)
2.结构
应用沙盒下面一级文件夹有三个:Documents、Library、tmp;Library文件夹下有二级文件有两个:Caches、Preferences
1)Documents:保存应⽤运行时生成的需要持久化的数据,iTunes同步设备时会备份该目录
2)Library/Caches:保存应用运行时⽣成的需要持久化的数据,iTunes同步设备时不会备份该目录(一般存储体积大、不需要备份的非重要数据)
3)Library/Preference:保存应用的所有偏好设置,iOS的Settings(设置) 应⽤会在该⺫录中查找应⽤的设置信息。iTunes同步设备时会备份该目录
4)tmp:保存应⽤运行时所需的临时数据,使⽤完毕后再将相应的文件从该目录删除。应用没有运行时,系统也可能会清除该目录下的文件。iTunes同步设备时 不会备份该目录
3.应用沙盒目录的获取方法
1 // 1.获取沙盒根目录
2 NSString *homePath = NSHomeDirectory();
3
4 // 2.获取Documents目录
5 // 第一种方法:根目录直接拼接(不建议采用,原因是如果苹果公司将文件夹名称改了,那就会很麻烦)
6 NSString *docPath1 = [homePath stringByAppendingPathComponent:@"Documents"];
7 // 第二种方法:方法获取(推荐使用)
8 // 参数1:NSDocumentDirectory 查找Documents文件夹
9 // 参数2:NSUserDomainMask 在用户目录下查找
10 // 参数3:YES 是否展开用户目录(NO就是~,YES就是展开)
11 NSString *docPath2 = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
12
13 // 3.拼接文件路径
14 // 假设Documents文件夹下面存储一张图片image.png
15 NSString *filePath = [docPath2 stringByAppendingPathComponent:@"image.png"];
三、plist列表
1.简介
1)plist列表只能存储一些系统自定义的数据类型,例如:NSString、NSArray、NSNumber、NSDictionary、NSData等等
2)plist列表不能存储一些我们自定义的对象类型,例如:自定义的Person对象
3)我们一般将plist文件保存在沙河目录Documents文件夹下,文件后缀名为.plist
2.实际应用
1 #pragma mark - 数据保存
2 - (void)saveData
3 {
4 // 1.获取Documents目录
5 NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
6
7 // 2.拼接文件路径
8 // 数组形式保存文件名:user1.plist
9 // 字典形式保存文件名:user2.plist
10 NSString *filePath1 = [docPath stringByAppendingPathComponent:@"user1.plist"];
11 NSString *filePath2 = [docPath stringByAppendingPathComponent:@"user2.plist"];
12
13 // 3.保存数据
14 // 第一种情况:数组形式保存
15 NSArray *userArr = @[@"Frank", @25, @"M", @"湖北黄冈"];
16 [userArr writeToFile:filePath1 atomically:YES];
17 // 第二种情况:字典形式保存
18 NSDictionary *userDic = @{@"name": @"Frank", @"age": @25, @"sex": @"M", @"home": @"湖北黄冈"};
19 [userDic writeToFile:filePath2 atomically:YES];
20 }
21
22 #pragma mark - 读取数据
23 - (void)readData
24 {
25 // 1.获取Documents目录
26 NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
27
28 // 2.拼接文件路径
29 // 数组形式保存文件名:user1.plist
30 // 字典形式保存文件名:user2.plist
31 NSString *filePath1 = [docPath stringByAppendingPathComponent:@"user1.plist"];
32 NSString *filePath2 = [docPath stringByAppendingPathComponent:@"user2.plist"];
33
34 // 3.读取数据
35 // 第一种情况:数组形式
36 NSArray *userArr = [NSArray arrayWithContentsOfFile:filePath1];
37 NSLog(@"userArr = %@", userArr);
38 // 第二种情况:字典形式
39 NSDictionary *userDic = [NSDictionary dictionaryWithContentsOfFile:filePath2];
40 NSLog(@"userDic = %@", userDic);
41 }
四、偏好设置
1.简介
1)很多应用都支持偏好设置,iOS提供了一套标准的解决方案来为应用加入偏好设置。
2)每一个应用都有一个实例NSUserDefault,iOS就是通过它来存储偏好设置
3)文件存储在沙河目录Library下文件夹Preferences,文件后缀名为.plist
4)注意我们的偏好设置是将所有的东西都保存在同一个文件中,且主要用来保存应用的一些设置信息
5)偏好设置同plist列表一样只能保存iOS系统自定义的一些数据类型
2.实际应用
1 #pragma mark - 数据保存
2 - (void)saveData
3 {
4 // 1.获取NSUserDefaults对象
5 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
6
7 // 2.保存数据
8 [defaults setObject:@"Frank" forKey:@"name"];
9 [defaults setInteger:25 forKey:@"age"];
10 [defaults setDouble:1.69 forKey:@"height"];
11 [defaults setFloat:148.2f forKey:@"weight"];
12 [defaults setObject:@"M" forKey:@"sex"];
13
14 // 3.同步数据(强制数据立即保存) -- 有点类似我们刷新表
15 // 若没有同步数据,系统会在将来某一时间点自动将数据保存到Preferences文件夹下
16 [defaults synchronize];
17 }
18
19 #pragma mark - 读取数据
20 - (void)readData
21 {
22 // 1.获取NSUserDefaults对象
23 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
24
25 // 2.读取保存的数据
26 NSString *name = [defaults objectForKey:@"name"];
27 NSInteger age = [defaults integerForKey:@"age"];
28 double height = [defaults doubleForKey:@"height"];
29 CGFloat weight = [defaults floatForKey:@"weight"];
30 NSString *sex = [defaults objectForKey:@"sex"];
31
32 // 3.打印输出数据
33 NSLog(@"name=%@, age=%ld, height=%.2f, weight=%.2f, sex=%@", name, age, height, weight, sex);
34 }
五、NSKeydeArchiver归档
1.简介
1)与前面两中数据保存方法均不能针对自定义对象类型,归档是专门针对自定义对象类型的数据保存
2)我们一般将归档文件保存在沙盒目录下的Documents文件夹下,其文件后缀名可以是任意的
3)通过plist存储的文件在文件中打开是直接显示的,而归档存储的文件在文件打开时是乱码的,更加安全
2.实际应用
1)自定义对象:UserModel类
1 // UserModel.h文件
2 #import <Foundation/Foundation.h>
3
4 #pragma mark - 归档对象必须遵循<NSCoding>协议
5 @interface UserModel : NSObject<NSCoding>
6
7 @property (nonatomic, copy) NSString *userId; // 用户ID
8 @property (nonatomic, copy) NSString *userName; // 用户名
9 @property (nonatomic, copy) NSString *password; // 用户密码
10 @property (nonatomic, assign) NSInteger age; // 用户的年龄
11 @property (nonatomic, assign) double weight; // 用户的体重
12 @property (nonatomic, assign) float height; // 用户的身高
13 @property (nonatomic, copy) NSString *userTel; // 用户的电话号码
14 @property (nonatomic, copy) NSString *userEmail; // 用户的邮箱
15
16 @end
17
18
19 // UserModel.m文件
20 #import "UserModel.h"
21
22 @implementation UserModel
23
24 #pragma mark - 必须实现的协议方法<NSCoding>
25 // 1.归档
26 - (void)encodeWithCoder:(NSCoder *)aCoder
27 {
28 [aCoder encodeObject:_userId forKey:@"userId"]; // 用户ID
29 [aCoder encodeObject:_userName forKey:@"userName"]; // 用户名
30 [aCoder encodeObject:_password forKey:@"password"]; // 用户密码
31 [aCoder encodeInteger:_age forKey:@"age"]; // 用户的年龄
32 [aCoder encodeDouble:_weight forKey:@"weight"]; // 用户的体重
33 [aCoder encodeFloat:_height forKey:@"height"]; // 用户的身高
34 [aCoder encodeObject:_userTel forKey:@"userTel"]; // 用户的电话号码
35 [aCoder encodeObject:_userEmail forKey:@"userEmail"]; // 用户的邮箱
36 }
37 // 2.解档
38 - (id)initWithCoder:(NSCoder *)aDecoder
39 {
40 if (self = [super init]) {
41 _userId = [aDecoder decodeObjectForKey:@"userId"]; // 用户ID
42 _userName = [aDecoder decodeObjectForKey:@"userName"]; // 用户名
43 _password = [aDecoder decodeObjectForKey:@"password"]; // 用户密码
44 _age = [aDecoder decodeIntegerForKey:@"age"]; // 用户的年龄
45 _weight = [aDecoder decodeDoubleForKey:@"weight"]; // 用户的体重
46 _height = [aDecoder decodeFloatForKey:@"height"]; // 用户的身高
47 _userTel = [aDecoder decodeObjectForKey:@"userTel"]; // 用户的电话号码
48 _userEmail = [aDecoder decodeObjectForKey:@"userEmail"]; // 用户的邮箱
49 }
50 return self;
51 }
52
53 @end
2)保存数据、读取数据工具类:UserDataSaveTool类
1 // UserDataSaveTool.h文件
2 #import <Foundation/Foundation.h>
3 #import "UserModel.h"
4
5 @interface UserDataSaveTool : NSObject
6
7 #pragma mark - 保存用户数据
8 + (void)saveUserModel:(UserModel *)userModel;
9
10 #pragma mark - 获取用户数据
11 + (UserModel *)getUserModel;
12
13 @end
14
15
16 // UserDataSaveTool.m文件
17 #import "UserDataSaveTool.h"
18
19 @implementation UserDataSaveTool
20
21 #pragma mark - 用户信息保存的沙盒路径
22 + (NSString *)getAccoutPath
23 {
24 // 归档文件名为:account.archive
25 NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"account.archive"];
26 return filePath;
27 }
28
29 #pragma mark - 保存用户数据
30 + (void)saveUserModel:(UserModel *)userModel
31 {
32 // 用户信息(用户信息model:accout)归档 -- 执行这个就会自动跳转执行用户model里的归档协议
33 [NSKeyedArchiver archiveRootObject:userModel toFile:[self getAccoutPath]];
34 }
35
36 #pragma mark - 获取用户数据
37 + (UserModel *)getUserModel
38 {
39 // 取出账号(账号存的就是一个model) -- 执行这个就会自动跳转执行用户model里的解档协议
40 UserModel *userModel = [NSKeyedUnarchiver unarchiveObjectWithFile:[self getAccoutPath]];
41 return userModel;
42 }
43
44 @end
3)实际调用
1 #pragma mark - 数据保存
2 - (void)saveData
3 {
4 // 1.初始化自定义对象模型
5 UserModel *userModel = [[UserModel alloc] init];
6 userModel.userId = @"20099830215";
7 userModel.userName = @"Frank";
8 userModel.password = @"9830215";
9 userModel.age = 25;
10 userModel.weight = 110.0;
11 userModel.height = 1.69;
12 userModel.userTel = @"15112256959";
13 userModel.userEmail = @"1058842360@qq.com";
14
15 // 2.借助UserDataSaveTool工具保存对象模型
16 [UserDataSaveTool saveUserModel:userModel];
17 }
18
19 #pragma mark - 读取数据
20 - (void)readData
21 {
22 // 1.借助UserDataSaveTool工具获取对象模型
23 UserModel *userModel = [UserDataSaveTool getUserModel];
24
25 // 2.打印输出
26 NSLog(@"userModel = %@", userModel);
27 }
3.注意
1)对象必须遵循NSCoding协议,并且重写协议的两个方法
2)如果是继承,在子类中也是要重写NSCoding协议的两个方法,而且协议方法中必须先调用父类的方法
例如:给上面的UserModel添加一个属性"sex"
1 // PersonModel.h文件
2 #import "UserModel.h"
3
4 @interface PersonModel : UserModel
5
6 #pragma mark - 给子类的属性
7 @property (nonatomic, copy) NSString *sex; // 性别
8
9 @end
10
11
12 // PersonModel.m文件
13 #import "PersonModel.h"
14
15 @implementation PersonModel
16
17 #pragma mark - 重写协议方法<NSCoding>
18 // 1.归档
19 - (void)encodeWithCoder:(NSCoder *)aCoder
20 {
21 // 先调用父类的归档协议方法
22 [super encodeWithCoder:aCoder];
23 [aCoder encodeObject:@"M" forKey:@"sex"];
24 }
25 // 2.解档
26 - (instancetype)initWithCoder:(NSCoder *)aDecoder
27 {
28 // 先初始化父类
29 if (self = [super initWithCoder:aDecoder]) {
30 [aDecoder decodeObjectForKey:@"sex"];
31 }
32 return self;
33 }
34
35 @end
六、SQLite3
1.前言
在我们的实际开发中都需要做一些离线数据的处理,即离线缓存。上面三个方法都可以实现离线缓存,但是它们都有一个致命的弱点:无法存储大批量的数据,存在一些性能的问题。除了这个关键性的问题外,它们还存在各种各样不同的约束问题。例如:对于归档,它只支持一次性写入,一次性读取,这对于大数据的存储有着很大的问题。针对这些问题,数据库SQLite3可以解决。
2.SQLite3的简介
1)3是SQLite的版本号
2)SQLite3是一种轻量级的嵌入式数据库,目前安卓和iOS使用的都是SQLite数据库
3)SQLite的占用资源非常低、处理速度也较MySql、PostgreSQL这两款著名的数据库快
4)SQLite中可以有多张表,表里的元素都是以字段存储和读取
3.SQL语句
1)SQL语句中字段的类型
integer:整型
real:浮点类型
text:文本字符串
blob:二进制数据(比如文件)
2)SQL语句的基本的操作
a.创表
create table 表名(字段名1 字段类型, 字段名2 字段类型, 字段名3 字段类型, ...);
create table if not exists 表名(字段名1 字段类型, 字段名2 字段类型, 字段名3 字段类型, ...);
// 实际上SQLite是无类型的,就算声明为integer类型,还是能存储字符串文本(主键除外),创表时可以不用去声明字段类型
b.删表
drop table 表明;
drop table if exists 表名;
c.插入数据
insert into 表名(字段1, 字段2, ...) values (字段1的值, 字段2的值, ...);
例:insert into t_student(name, age, sex) values ('Frank', 25, 'M');
// 注意:SQL中字符串的值必须使用单引号''引上
d.更新数据
update 表名 set 字段1 = 字段1的值, 字段2 = 字段2的值, ...;
例:update t_student set name = 'Frank', age = 25;
// 上面语句是将表t_student中所有记录的字段name的值改为"Frank",所有记录的字段age的值改为25
e.删除数据
delete from 表名;
例:delete from t_student;
// 上面语句是将表t_student中的所有记录删除
d.条件语句
where 指定字段 = 指定值;
where 指定字段 is 指定值;
where 指定字段 != 指定值;
where 指定字段 is not 指定值;
where 指定字段 > 指定值;
where 指定字段1 = 指定值1 and 指定字段2 > 指定值2;
where 指定字段1 = 指定值1 or 指定字段2 = 指定值2;
e.查找语句
select 字段1, 字段2, ... from 表名; // 查询指定字段
select * from 表名; // 查询所有字段
f.排序
select * from 表名 order by 字段; // 查询结果按照指定字段升序排列(默认)
select * from 表名 order by 字段 desc; // 查询结果按照指定字段降序排列
select * from 表名 order by 字段 asc; // 查询结果按照指定字段升序排列
select * from 表名 order by 字段1 desc, 字段2 asc; // 查询结果按照字段1降序排列,按照字段2升序排列
g.使用limit精准控制查询结果的数量
select * from 表名 limit 数值; // 指定获取查询结果的前几条数据
select * from 表名 limit 数值1, 数值2; // 指定跳过前面几条(数值1)数据,然后取指定条(数值2)数据
h.普通字段约束
创表时,可以给一些特定的字段设置一些约束条件,常见的有:
not null:规定字段值不能为null
unique:规定字段的值必须唯一
default:指定字段的默认值
例:create table t_student (id integer, name text not null unique, age integer not null default 1);
// name字段:不能为null,并且唯一
// age字段:不能为null,默认值为1
i.主键约束
主键用来唯一标识某一条记录。主键可以是一个字段或多个字段
只要声明为primary key ,就说明是一个主键字段
主键字段默认就包含了not null和unique两个约束
主键自动增长,应该增加autoincrement
例:create table t_student (id integer primary key autoincrement, name text, age integer);
// 自动增长的主键
j.外键约束
外键约束可以用来建立表与表之间的联系
例:create table t_student (id integer primary key autoincrement, name text, class_id integer, constraint fk_student_class foreign key (class_id) references t_class (id));
// t_student表中的外键:fk_t_student_class_id_t_class_id
// 作用:用t_student表中的class_id字段引用t_class表的id字段
4.FMDB
1)什么是FMDB
a.FMDB是iOS平台的SQLite数据库框架
b.FMDB以OC的方式封装了SQLite的C语言API
2)FMDB的优点
a.使用起来更加面向对象,省去了很多麻烦、冗余的C语言代码
b.对比苹果自带的CoreData框架,更加轻量级和灵活
c.提供了多线程安全的数据库操作方法,有效地防止数据混乱
3)FMDB的核心类
a.FMDatabase:一个FMDatabase对象就代表一个单独的SQLite数据库。用来执行SQL语句
b.FMResultSet:使用FMDatabase执行查询后的结果集
c.FMDdatabaseQueue:用于多线程中执行多个查询或更新,它是线程安全的
4)打开数据库(根据文件路径)
FMDatabase *db = [FMDatabase databaseWithPath:dbPath];
if (![db open]) {
NSLog(@"数据库打开失败!");
}
// 文件路径有三种情况:
a.文件路径具体:如果不存在该数据库文件会自动创建
b.文件路径为空字符串@"":会在临时目录下创建一个空的数据库;当FMDatabase连接关闭时,数据库文件也被删除
c.文件路径为nil:会创建一个内存中临时数据库;当FMDatabase连接关闭时,数据库会被销毁
5)FMDB的用法
a.在项目中导入系统框架libsqlite3.dylib
b.将第三方库FMDB拖入到项目中
6)FMDB管理工具类
1 // FMDBManager.h文件
2 #import <Foundation/Foundation.h>
3 #import "FMDatabaseQueue.h"
4 #import "FMDatabaseAdditions.h"
5 #import <objc/runtime.h>
6
7 @interface FMDBManager : NSObject
8
9
10 // 工具类特点:
11 // 1.根据实体对象创表,每张表的主键为实体类的首个属性名,主键为自增NSInteger类型
12 // 2.删除和插入可以灵活地传入实体对应的任何一个属性作为依据字段,根据该字段来判断删除的记录是否存在和判断是插入还是更新操作
13 // 3.查找方法可以将一个字符串作为参数传递,该字符串为条件查找的条件语句;方法内部会根据该字符串是否为nil,来判断是条件查询还是全部查询
14
15 // 工具类的要求:
16 // 1.实体对应的首字段取名最好为“idNum”
17 // 2.实体所有属性最好都是NSString类型
18
19 #pragma mark - 数据库管理类对象
20 + (FMDBManager *)sharedInstace;
21
22 #pragma mark - 创建表
23 - (void)creatTable:(id)model;
24
25 #pragma mark - 数据库更新或插入数据
26 - (void)insertAndUpdateModelToDatabase:(id)model selectColumn:(NSString *)selectColumn;
27
28 #pragma mark - 删除元素
29 - (void)deleteModelInDatabase:(id)model selectColumn:(NSString *)selectColumn;
30
31 #pragma mark - 全部查找
32 - (NSArray *)selectModelArrayInDatabase:(NSString *)className factorString:(NSString *)factorString;
33
34 @end
35
36
37 // FMDBManager.m文件
38 #import "FMDBManager.h"
39
40 // 通过实体获取类名
41 #define KCLASS_NAME(model) NSStringFromClass([model class])
42 // 通过实体获取属性数组
43 #define KMODEL_PROPERTYS [self getAllProperties:model]
44 // 通过实体获取属性数组的数目
45 #define KMODEL_PROPERTYS_COUNT [[self getAllProperties:model] count]
46
47 @implementation FMDBManager
48 {
49 FMDatabaseQueue *_dbQueue; // 数据库操作队列
50 }
51
52 #pragma mark - 数据库管理类对象
53 + (FMDBManager *)sharedInstace
54 {
55 static dispatch_once_t onceToken;
56 static FMDBManager *manager = nil;
57 dispatch_once(&onceToken, ^{
58 manager = [[FMDBManager alloc] init];
59 });
60 return manager;
61 }
62
63 #pragma mark - 获取沙盒路径
64 - (NSString *)getDBFilePath
65 {
66 NSString *dbFilePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject stringByAppendingPathComponent:@"db.sqlite"];
67 NSLog(@"%@", dbFilePath);
68 return dbFilePath;
69 }
70
71 #pragma mark - 创建数据库操作队列
72 // 一个数据库文件对应一个数据库操作队列
73 - (void)creatDBQueue
74 {
75 // 创建数据库操作队列的同时还会判断数据库是否存在,如果不存在,系统自动创建数据库
76 _dbQueue = [[FMDatabaseQueue alloc] initWithPath:[self getDBFilePath]];
77 }
78
79 #pragma mark - 获取实体对象的属性数组
80 // 参数:model 实体对象
81 // 返回值:实体对象属性数组
82 - (NSArray *)getAllProperties:(id)model
83 {
84 u_int count;
85 objc_property_t *properties = class_copyPropertyList([model class], &count);
86 // 定义一个可变的属性数组
87 NSMutableArray *propertiesArray = [NSMutableArray array];
88 for (int i = 0; i < count ; i++)
89 {
90 const char* propertyName = property_getName(properties[i]);
91 [propertiesArray addObject: [NSString stringWithUTF8String: propertyName]];
92 }
93 free(properties);
94 return propertiesArray;
95 }
96
97 #pragma mark - 创表
98 // 参数1:model 实体对象
99 - (void)creatTable:(id)model
100 {
101 // 判断数据库操作队列是否存在
102 if (!_dbQueue) {
103 // 数据库操作队列不存,就得创建
104 [self creatDBQueue];
105 }
106
107 // 使用数据库操作队列操作数据库
108 [_dbQueue inDatabase:^(FMDatabase *db) {
109 // 打开数据库
110 if (![db open]) {
111 // 数据库打开失败
112 NSLog(@"数据库打开失败");
113 return;
114 }
115
116 // 为数据设置缓存,提高查询效率
117 [db setShouldCacheStatements:YES];
118
119 // 判断数据库中是否已经存在该实体对应的表
120 // 不存在该实体对应的表,才创建表
121 if (![db tableExists:KCLASS_NAME(model)]) {
122 // 创表语句的第一部分
123 // %@ integer primary key autoincrement(设置主键为实体model的首字段,integer类型,自增)
124 // 主键
125 NSString *primaryKey = KMODEL_PROPERTYS[0];
126 NSString *creatTableOneStr = [NSString stringWithFormat:@"create table %@(%@ integer primary key autoincrement", KCLASS_NAME(model), primaryKey];
127
128 // 创表语句的第二部分
129 NSMutableString *createTableTwoStr = [NSMutableString string];
130 // 遍历实体对象的属性数组
131 // 直接跳过实体的首字段
132 for (int i = 1; i < KMODEL_PROPERTYS_COUNT; i++) {
133 // 实体对应的字段都是以文本字符串类型存储到数据库表中
134 NSString *popertyName = KMODEL_PROPERTYS[i];
135 [createTableTwoStr appendFormat:@", %@ text", popertyName];
136 }
137
138 // 创建表总语句
139 NSString *createTableStr = [NSString stringWithFormat:@"%@%@)", creatTableOneStr, createTableTwoStr];
140
141 // 创表方法
142 if ([db executeUpdate:createTableStr]) {
143 NSLog(@"创表成功");
144 }
145 }
146
147 // 关闭数据库
148 [db close];
149 }];
150 }
151
152 #pragma mark - 数据库更新或插入数据
153 // 参数1:model 实体对象
154 // 参数2:selectColumn 依据字段
155 - (void)insertAndUpdateModelToDatabase:(id)model selectColumn:(NSString *)selectColumn
156 {
157 // 判断数据库操作队列是否存在
158 if (!_dbQueue) {
159 // 数据库操作队列不存,就得创建
160 [self creatDBQueue];
161 }
162
163 // 使用数据库操作队列操作数据库
164 [_dbQueue inDatabase:^(FMDatabase *db) {
165 // 打开数据库
166 if (![db open]) {
167 // 数据库打开失败
168 NSLog(@"数据库打开失败");
169 return;
170 }
171
172 // 为数据设置缓存,提高查询效率
173 [db setShouldCacheStatements:YES];
174
175 // 判断是否存在该实体对象所对应的表
176 if (![db tableExists:KCLASS_NAME(model)]) {
177 // 不存在该实体对象所对应的表就去创表
178 [self creatTable:model];
179 }
180
181 // 按照传进来的查找字段查找该条记录是否存在
182 NSString *selectStr = [NSString stringWithFormat:@"select * from %@ where %@ = ?", KCLASS_NAME(model), selectColumn];
183 // 获取实体对象的首个属性对应的值
184 NSString *selectColumnValue = [model valueForKey:selectColumn];
185 // 查找
186 FMResultSet *resultSet = [db executeQuery:selectStr, selectColumnValue];
187 // 判断查找结果是否为空
188 // 如果有查找结果,对应实体对象就是更新操作
189 if ([resultSet next]) {
190 // 更新语句第一部分
191 NSString *updateOneStr = [NSString stringWithFormat:@"update %@ set ",KCLASS_NAME(model)];
192
193 // 更新语句第二部分
194 NSMutableString *updateTwoStr = [NSMutableString string];
195 // 遍历实体对象的属性数组
196 // 跳过实体对应的首字段
197 for (int i = 1; i < KMODEL_PROPERTYS_COUNT; i++) {
198 // 获取实体属性字段
199 NSString *propertyName = KMODEL_PROPERTYS[i];
200 if ([propertyName isEqualToString:selectColumn]) {
201 continue;
202 }
203 [updateTwoStr appendFormat:@"%@ = ?", propertyName];
204 // 判断是否遍历到属性数组的最后一个
205 if (i != KMODEL_PROPERTYS_COUNT - 1) {
206 [updateTwoStr appendFormat:@", "];
207 }
208 }
209
210 // 更新语句第三部分
211 NSString *updateThreeStr = [NSString stringWithFormat:@" where %@ = %@",selectColumn, selectColumnValue];
212
213 // 更新总语句
214 NSString *updateStr = [NSString stringWithFormat:@"%@%@%@", updateOneStr, updateTwoStr, updateThreeStr];
215
216 // 属性值数组
217 NSMutableArray *propertyValueArr = [NSMutableArray array];
218 // 遍历属性数组
219 // 跳过实体对应的首字段
220 for (int i = 1; i < KMODEL_PROPERTYS_COUNT; i++) {
221 NSString *propertyName = KMODEL_PROPERTYS[i];
222 if ([propertyName isEqualToString:selectColumn]) {
223 continue;
224 }
225 NSString *propertyValue = [model valueForKey:propertyName];
226 if (propertyValue == nil) {
227 propertyValue = @"";
228 }
229 [propertyValueArr addObject:propertyValue];
230 }
231
232 // 调用更新方法
233 if([db executeUpdate:updateStr withArgumentsInArray:propertyValueArr])
234 {
235 NSLog(@"数据更新成功");
236 }
237 } else {
238 // 插入操作
239
240 // 插入语句第一部分
241 NSString *insertOneStr = [NSString stringWithFormat:@"insert into %@ (",KCLASS_NAME(model)];
242
243 // 插入语句第二部分
244 NSMutableString *insertTwoStr =[NSMutableString string];
245 // 跳过实体对应的首字段
246 for (int i = 1; i < KMODEL_PROPERTYS_COUNT; i++) {
247 // 获取实体属性字段
248 NSString *propertyName = KMODEL_PROPERTYS[i];
249 [insertTwoStr appendFormat:@"%@", propertyName];
250 if (i != KMODEL_PROPERTYS_COUNT - 1) {
251 [insertTwoStr appendFormat:@", "];
252 }
253 }
254
255 // 插入语句第三部分
256 NSString *insertThreeStr =[NSString stringWithFormat:@") values ("];
257
258 // 插入语句第四部分
259 NSMutableString *insertFourStr =[NSMutableString string];
260 // 跳过实体对应的首字段
261 for (int i = 1; i < KMODEL_PROPERTYS_COUNT; i++) {
262 [insertFourStr appendFormat:@"?"];
263 if (i != KMODEL_PROPERTYS_COUNT - 1) {
264 [insertFourStr appendFormat:@", "];
265 }
266 }
267
268 // 插入整个语句
269 NSString *insertStr = [NSString stringWithFormat:@"%@%@%@%@)", insertOneStr, insertTwoStr, insertThreeStr, insertFourStr];
270
271 // 属性值数组
272 NSMutableArray *propertyValueArr = [NSMutableArray array];
273 // 遍历属性数组
274 // 跳过实体对应的首字段
275 for (int i = 1; i < KMODEL_PROPERTYS_COUNT; i++) {
276 // 获取实体属性字段
277 NSString *propertyName = KMODEL_PROPERTYS[i];
278 // 通过实体的字段获取对应的值
279 NSString *propertyValue = [model valueForKey:propertyName];
280 // 判断这个值是否为nil
281 if (propertyValue == nil) {
282 propertyValue = @"";
283 }
284 [propertyValueArr addObject:propertyValue];
285 }
286
287 // 调用插入方法
288 if([db executeUpdate:insertStr withArgumentsInArray:propertyValueArr])
289 {
290 NSLog(@"插入成功");
291 }
292
293 // 关闭数据库
294 [db close];
295 }
296 }];
297 }
298
299 #pragma mark - 删除元素
300 // 参数1:model 实体对象
301 // 参数2:selectColumn 依据字段
302 - (void)deleteModelInDatabase:(id)model selectColumn:(NSString *)selectColumn
303 {
304 // 判断数据库操作队列是否存在
305 if (!_dbQueue) {
306 // 数据库操作队列不存,就得创建
307 [self creatDBQueue];
308 }
309
310 // 使用数据库操作队列操作数据库
311 [_dbQueue inDatabase:^(FMDatabase *db) {
312 // 打开数据库
313 if (![db open]) {
314 // 数据库打开失败
315 NSLog(@"数据库打开失败");
316 return;
317 }
318
319 // 为数据设置缓存,提高查询效率
320 [db setShouldCacheStatements:YES];
321
322 // 判断是否存在该实体对象所对应的表
323 if (![db tableExists:KCLASS_NAME(model)]) {
324 // 不存在该实体对象所对应的表就去创表
325 [self creatTable:model];
326 }
327
328 // 删除语句(根据实体首字段的首属性字段去删)
329 NSString *deleteStr = [NSString stringWithFormat:@"delete from %@ where %@ = ?", KCLASS_NAME(model), selectColumn];
330
331 // 执行删除方法
332 // 实体字段对应的值
333 NSString *selectColumnValue = [model valueForKey:selectColumn];
334 if([db executeUpdate:deleteStr, selectColumnValue])
335 {
336 NSLog(@"删除成功");
337 }
338
339 // 关闭数据库
340 [db close];
341
342 }];
343 }
344
345 #pragma mark - 查找
346 // 参数1:tableName 表名
347 // 参数2:factorString 条件查找语句
348 // 返回值:直接返回实体数组
349 - (NSArray *)selectModelArrayInDatabase:(NSString *)tableName factorString:(NSString *)factorString
350 {
351 // 判断数据库操作队列是否存在
352 if (!_dbQueue) {
353 // 数据库操作队列不存,就得创建
354 [self creatDBQueue];
355 }
356
357 // 需要返回的实体数组(block里面需要改变其值,所以必须使用__block修饰)
358 __block NSMutableArray *modelArray = [NSMutableArray array];
359
360 // 使用数据库操作队列操作数据库
361 [_dbQueue inDatabase:^(FMDatabase *db) {
362 // 打开数据库
363 if (![db open]) {
364 // 数据库打开失败
365 NSLog(@"数据库打开失败");
366 return;
367 }
368
369 // 为数据设置缓存,提高查询效率
370 [db setShouldCacheStatements:YES];
371
372 // 条件查询
373 NSString * selectStr = nil;
374 if (factorString != nil) {
375 selectStr = [NSString stringWithFormat:@"SELECT * FROM %@ %@",tableName, factorString];
376 }else {
377 // 全局查询
378 selectStr = [NSString stringWithFormat:@"select * from %@",tableName];
379 }
380
381 FMResultSet *resultSet = [db executeQuery:selectStr];
382 while([resultSet next]) {
383 // 使用表名作为类名创建对应的类的对象
384 id model = [[NSClassFromString(tableName) alloc] init];
385 for (int i = 0; i < KMODEL_PROPERTYS_COUNT; i++) {
386 // 值是从我们的数据表的Column字段取出来
387 // 获取表中字段
388 NSString *propertyName = KMODEL_PROPERTYS[i];
389 NSString *propertyValue = [resultSet stringForColumn:propertyName];
390 // 设置model对应属性的对应值
391 [model setValue:propertyValue forKey:propertyName];
392 }
393 [modelArray addObject:model];
394 }
395 }];
396 return modelArray;
397 }
398
399 @end
7)事务
例:银行转账系统,张三和李四账户上都有1000元钱,张三现在给李四转账500元
update t_account set money = 500 where name = @"张三";
update t_account set money = 1500 where name = @"李四";
张三给李四转账500元,这一操作就会执行上面两条SQL语句,但是考虑到安全性,我们必须要求两条语句同生同死。这个时候我们可以将他放在同一个事务中
事务:把多条语句放到同一个事务中,就会同生共死(即使前面的SQL语句执行成功,而后面的SQL执行失败,系统也会回滚,使它们全部失败)
1 [_dbQueue inDatabase:^(FMDatabase *db) {
2 // 创建事务
3 [db beginTransaction];
4 [db executeUpdate:@"insert into t_person (name, age) values (?, ?);", @"Frank", @22];
5 [db executeUpdate:@"insert into t_person (name, age) values (?, ?);", @"Kin", @15];
6 // 提交事务
7 [db commit];
8 }];