一、iOS沙盒机制
iOS的每个应用都有属于自己的存储空间,即沙盒
应用只能访问自己的沙盒,不可访问其他区域。沙盒主要作用就是文件存储。如果应用需要进行文件操作,则必须将文件存放在沙盒中,尤其是数据库文件,在电脑上操作时,可以去访问,但是如果要装在真机上可以使用,必须将数据库文件拷贝至沙盒中。
二、应用沙盒
2.1、沙盒路径下的三个文件
- Document:适合存储重要且持久化的"轻量级"的数据, iTunes会同步应用时会同步该文件下的内容,(比如游戏中的存档,大型数据不能存放在这里,一旦存放,iOS审核不会通过);
- Library/Preferences:通常保存应用设置信息, iTunes会同步(iOS的settings(设置)应用会在该目录中查找应用的设置信息,一般用于存储账号密码等信息);
- Library/Caches:适合存储体积大,不需备份的非重要持久化数据,iTunes不会同步该文件(即iTunes同步时不会备份该目录)(一般把缓存下载好的文件放在这里);
tmp:保存应用运行时需要的临时数据的,用完就删除,系统可能在应用未运行时删除该目录下的文件,iTunes不会同步。
2.2、获取沙盒路径的两种方式
- 1、适合Library/Caches和Documents沙河路径的获取方式
- 获取沙河路径-直接获取方式
NSArray *cachesPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); //NSDocumentDirectory NSString *cachePath = [cachesPaths lastObject];
- 获取沙河路径-拼接方式:
//沙河路径 NSString *sandPath = NSHomeDirectory(); //拼接一个文件名:自动加一个斜杠,拼接文件专用 [sandPath stringByAppendingPathComponent:@"Library/Caches"];//"Documents" 和tmp/
- 获取沙河路径-直接获取方式
- 2、适合tmp的获取方式
- 获取沙河路径-直接获取方式
NSString *tmpPath = NSTemporaryDirectory(); NSLog(@"tmpPath -- %@",tmpPath);
- 获取沙河路径-拼接方式
NSString *sandPath = NSHomeDirectory(); [sandPath stringByAppendingPathComponent:@"tmp/"];//"Documents" 和tmp/Library/Caches
- 获取沙河路径-直接获取方式
- 3、Library/Perferences
使用NSUserDefault来保存的数据都存在这个文件夹下,格式是plist,不需要关心路径
2.3、沙盒文件操作-NSFileManager和NSFileHandle
- NSFileManager是用来管理文件系统的,它可以进行常见的文件/文件夹操作:拷贝、剪切、创建;
- NSFileManager使用了single单列模式[NSFileManager defaultManager];
- 文件的存储路径可设置;
- 可以存储大量的数据,对数据格式没有限制。
- 但是由于数据的存取必须是一次性的全部操作,所以在频繁操作数据方面性能有所欠缺。
3.4.1、 NSFileManage
- 单列获取
NSFileManager *fm = [NSFileManager defaultManager];
- 增加-创建目录
NSString *createDirPath = @"/Users/Feng/Desktop/aaa/ccc/bbb/love.txt";
// 创建路径的时候,YES自动创建路径中缺少的目录,NO的不会创建缺少的目录;
//attributes:属性的字典;
//error:错误对象
NSError *error = nil;
NSString *path = [NSString stringWithFormat:@"%@/test",[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]];
BOOL isYES = [fm createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&error];
if (isYES) {
NSLog(@"成功");
}
- 增加-创建文件--一次性写入数据
NSData *data = [@"hello word,my name is Feng" dataUsingEncoding:NSUTF8StringEncoding];//数据
BOOL isYes;
NSString *path = [NSString stringWithFormat:@"%@/test/log.txt", [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]];
isYes = [fm createFileAtPath:createDirPath contents:data attributes:nil];
NSLog(@"isYes = %d",isYes);
- 删-删除文件
NSString *targetPath = @"/Users/Feng/Desktop/aaa/ccc/love.txt";
NSString *sourcePath = @"/Users/Feng/Desktop/aaa/love.txt";
[fm removeItemAtPath:targetPath error:nil];
- 修改-copy文件
NSString *targetPath = @"/Users/Feng/Desktop/aaa/ccc/love.txt";
NSString *sourcePath = @"/Users/Feng/Desktop/aaa/love.txt";
[fm copyItemAtPath:sourcePath toPath:targetPath error:nil];
- 修改-移动文件
NSString *targetPath = @"/Users/Feng/Desktop/aaa/ccc/love.txt";
NSString *sourcePath = @"/Users/Feng/Desktop/aaa/love.txt";
[fm moveItemAtPath:sourcePath toPath:targetPath error:nil];
- 查看-判断是否存在
// 1、判断文件是否存在
[fm fileExistsAtPath:filePath]; // YES:存在
//2、判断是否是一个目录
[fm fileExistsAtPath:filePath isDirectory:&isDir];YES:是一个目录【首先要判断文件是否存在在判断是否是个目录】
//3、判断文件是否可读
[fm isReadableFileAtPath:filePath];//YES :可读
//4、判断文件是否可写
[fm isWritableFileAtPath:filePath];//YES :可写
- 查看-遍历文件
//1、如何获取文件的信息(属性)
NSDictionary *dict = [fm attributesOfItemAtPath:@""/Users/Feng/Desktop/arr.plist""error:nil];
//2、获取指定目录下文件及子目录
//方法一、使用递归的方式 获取当前目录及子目录下的所有的文件及文件夹
NSArray *subPaths = [fm subpathsAtPath:dirPath];
//方法二、(推荐使用)subpathsOfDirectoryAtPath 不是使用递归的方式获取的
subPaths = [fm subpathsOfDirectoryAtPath:dirPath error:nil];
NSLog(@"subPaths = %@",subPaths);
//3、获取指定目录下的文件及目录信息(不在获取后代路径)
subPaths = [fm contentsOfDirectoryAtPath:dirPath error:nil];
3.4.2、 NSFileHandle-读写文件操作
-
打开文件
NSString *path = [NSString stringWithFormat:@"%@/test/log.txt", [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]]; //1、打开文件 NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:path ]; //只读方式打开 //fileHandle = [NSFileHandle fileHandleForWritingAtPath:path]; //只写方式打开 //fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:path]; //更新方式打开
读数据
//光标开始位置到结尾,默认光标在文件最开始
NSLog(@"%@",[fileHandle readDataToEndOfFile]);
//部分数据
//[fileHandle seekToFileOffset:0]; //移动光标到最开始位置,0为最开始
//NSLog(@"%@",[fileHandle readDataOfLength:3]);
- 写数据
NSString *string = @"update"; //seek后可以从指定的位置开始写入,不seek默认从头部开始写 //[fileHandle seekToEndOfFile];//直接seek到文末 //[fileHandle seekToFileOffset:20]; //设置偏移量 [fileHandle writeData:[string dataUsingEncoding:NSUTF8StringEncoding]];/ //实现实时更新文件 [fileHandle synchronizeFile]; [fileHandle closeFile];
- 关闭文件
//打开文件要关闭 [fileHandle closeFile];
三、iOS常见的五种存储方式
- XML 属性列表plist文件存储:必须拿到文件路径【writeToFile】
- NSUserDefaults的Preference偏好设置:本质是plist文件存储,但是默认创建了plist文件,即固定文件名/文件位置Library/Preferences/
- NSKeyedArchiver归档存储:存储自定义对象【NSKeyedArchiver】
- NSFileManager/NSFileHandle文件存储:适合大量一次性写入;
- 数据库存储:SQLite 、FMDB、CoreData;在无网络的状态下,数据库常常发挥着非常大的作用
3.1、XML 属性列表plist文件存储
plist文件(XML属性列表存储),plist文件存储一般都是存取字典和数组,直接写成plist文件,把它存到应用沙盒当中。只有在ios当中才有plist存储,它是ios特有的存储方式。
-
可以存储的类型:Array、Dictionary、String、NSData、NSNumber、NSString、NSDate、BOOL。
- 可变的Array、Dictionary、String也是可以直接存储的,只是读取出来是不变的。
- Date、Number和Boolean没有writeToFile方法,需要转成string或data或包裹在Array和Dictionary中存储,一般选择后者;
- 其实String、Data、Date、Number、Boolean都是包裹在Array和Dictionary。【因为Root下只能是Array和Dictionary】
-
用途:
- 储存用户的设置
- 适合存储项目中常用但不常修改的轻量数据类
- 不适合存储大量数据,而且只能存储基本数据类型。
- 虽然可以实现 :增、删、改、查等操作,但由于数据的存取必须是一次性全部操作,所以性能方面表现并不好。
实际应用,如下代码:
NSDictionary *dict = @{
@"name":@"zhangsan",
@"age" : @(18),
};
//获取documenth路径
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
//在document路径下创建一个test.plist文件
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];// // 拼接一个文件名:自动加一个斜杠,拼接文件专用
//写入数据
[dict writeToFile:filePath atomically:YES];
查看沙盒文件,如下图:
读取刚存储的plist数据:
//获取documenth路径
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
//在document路径下创建一个test.plist文件
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];
//读取test.plist数据
NSDictionary *listDict = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSLog(@"listDict -- %@",listDict);
对其参数进行解读:
NSDocumentDirectory:第一个参数代表要查找哪个文件,是一个枚举。
NSUserDomainMask:表示搜索的范围限制于当前应用的沙盒目录
YES (expandTilde): 第三个参数是一个BOOL值。iOS中主目录的全写形式是/User/userName,这个参数填YES就表示全写,填NO就是写成‘‘~’’
3.2、NSUserDefaults的Preference偏好设置
特点:
- 文件格式为:.plist 【应用程序启动后,会在沙河Library/Preferences/下默认生成以工程为名字的.plist文件】
- 沙河路径为:Library/Preferences/【自动生成,不需要关心文件名】
- NSuserDefault适合存储轻量级的本地数据,支持的数据类型有: Array/NSMutableArray、Dictionary/NSMutableDictionary、
String/NSMutableString、NSData、NSNumber、NSString、NSDate、BOOL - 底层就是封装了一个字典,利用字典的方式生成plist文件,本质就是操作plist文件,所以性能方面的考虑同plist文件数据储存。但是不需要自行创建plist文件,不需要关心文件名
- 常用于存储用户的个人偏好设置,一般用于存储账号密码等信息。
- 存储用的是什么key存的,读的时候就要用什么Key️取,存的时候使用的什么类型,取的时候也用什么类型。
优点:
- 不需要关心文件名,自动创建了,固定了路径固定了文件名;
- 快速进行key-value存储
- 直接存储基本数据类型
缺点:
- 不能存储自定义数据;
- 取出的数据都是不可变的;
- 数据的存取必须是一次性,只能存储基本数据类型。
示范:
#pragma mark --写数据
- (void)writeData {
NSArray *testArray = @[@"test1", @"test2", @"test3"];
[[NSUserDefaults standardUserDefaults] setObject:testArray forKey:@"arrayKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
NSDictionary *dic = [NSDictionary dictionaryWithObject:@"Feng" forKey:@"name"];
[[NSUserDefaults standardUserDefaults] setObject:dic forKey:@"dicKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:YES] forKey:@"boolKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInt:88] forKey:@"numberKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
[[NSUserDefaults standardUserDefaults] setObject:@"hello world" forKey:@"strKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
[[NSUserDefaults standardUserDefaults] setObject:[NSData dataWithData:[@"I am fine" dataUsingEncoding:NSUTF8StringEncoding]] forKey:@"dataKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
[[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:@"DateKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
#pragma mark --读数据数据
- (void)readData {
NSArray *testArray = [[NSUserDefaults standardUserDefaults] objectForKey:@"arrayKey"];
NSLog(@"data:%@",testArray);
}
3.3、NSKeyedArchiver归档存储
归档一般都是保存自定义对象的时候,使用归档.因为plist文件不能够保存自定义对象.
plist 与 NSUserDefaults(个人偏好设置)两种类型的储存只适用于系统自带的一些常用类型,而且前者必须拿到文件路径,后者也只能储存应用的主要信息。
如果一个字典当中保存有自定义对象,如果把这个字典写入到文件当中,它是不会生成plist文件的。
所以如果有自定义对象的时候,就需要归档了,归档一般都是保存自定义对象的。
- 首先新建Person类,并遵守NSCoding协议
@interface Person : NSObject
//尊村NSCoding协议 @property(nonatomic, strong)NSString *name; @property(nonatomic, strong)NSString *age; @end - 实现两个协议,归档和解档
@implementation Person - (instancetype)initWithCoder:(NSCoder *)coder { //归档时调用这个方法 self = [super init]; if (self) { _name = [coder decodeObjectForKey:@"name"]; _age = [coder decodeObjectForKey:@"age"]; } return self; //方法二: //self = [super init]; //return [self yy_modelInitWithCoder:aDecoder]; //YYmodel归档 } - (void)encodeWithCoder:(NSCoder *)coder { //解档时调用这个方法 [coder encodeObject:self.name forKey:@"name"]; [coder encodeObject:self.age forKey:@"age"]; //方法二: [self yy_modelEncodeWithCoder:aCoder]; //YYmodel解档 } @end
- 归档存储和解档读取数据
#pragma mark --归档写数据 - (void)archive:(id)sender { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentFilePath = paths.firstObject; NSString *filePath = [documentFilePath stringByAppendingPathComponent:@"personModel"]; Person *p1 = [[Person alloc] init]; p1.name = @"ran"; p1.age = @"18"; [NSKeyedArchiver archiveRootObject:p1 toFile:filePath]; //转换成NSData,使用UserDefaults存储 // NSData * data = [NSKeyedArchiver archivedDataWithRootObject:[Person yy_modelToJSONObject]]; // [[NSUserDefaults standardUserDefaults]setValue:data forKey:@"testKey"]; //[[NSUserDefaults standardUserDefaults]synchronize]; } #pragma mark --解档读数据 - (void)unarchive:(id)sender { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentFilePath = paths.firstObject ; NSString *filePath = [documentFilePath stringByAppendingPathComponent:@"personModel"]; Person *p1 = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath] ; NSLog(@"name:%@ age:%@", p1.name, p1.age); //NSData * tempData = [[NSUserDefaults standardUserDefaults]valueForKey:@"testKey"]; //Person *p2 = [NSKeyedUnarchiver unarchiveObjectWithData:tempData]; }
但是此方法只能存储一个,要存储多个对象要采用如下的方法:
- (void)archiveManyObject:(id)sender {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentFilePath = paths.firstObject ;
NSString *filePath = [documentFilePath stringByAppendingPathComponent:@"personModel"];
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; //将数据区连接到NSKeyedArchiver对象
Person *p1 = [[Person alloc] init];
p1.name = @"ran1";
p1.age = @"18";
[archiver encodeObject:p1 forKey:@"person1"];
Person *p2 = [[Person alloc] init];
p2.name = @"ran2";
p2.age = @"19";
[archiver encodeObject:p2 forKey:@"person2"];
[archiver finishEncoding];
[data writeToFile:filePath atomically:YES];
}
- (void)unarchiveManyObject:(id)sender {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentFilePath = paths.firstObject ;
NSString *filePath = [documentFilePath stringByAppendingPathComponent:@"personModel"];
NSData *data = [NSData dataWithContentsOfFile:filePath];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
Person *p1 = [unarchiver decodeObjectForKey:@"person1"];
Person *p2 = [unarchiver decodeObjectForKey:@"person2"];
[unarchiver finishDecoding];
NSLog(@"name:%@ age:%@", p1.name, p1.age);
}
3.4、系统提供的数据存储方式的弊端
1、一次性存储,不方便操作大量的数据
- 系统提供的数据存储方式都是一次性存储,会覆盖存储的。新的数据会覆盖旧的数据,而且档数据量非常大的时,如果要添加新的数据,必须先把旧数据全部加载在内存中。
2、不方便查找大量的数据 - 当数据量非常庞大时,要查询其中某些数据,旧非常困难
而数据库可以轻松解决以上弊端
3.5、数据库存储
以上的三种存储,需要对文件/文件夹操作都是通过NSFileManager来实现,而数据的存取必须是一次性的全部操作,所以在频繁操作数据方面性能有所欠缺。利用数据库可以解决这个问题。
3.5.1、数据存储使用的SQLite:
它是一款轻型的嵌入式数据库,安卓和ios开发使用的都是SQLite数据库
占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了
它的处理速度比Mysql、PostgreSQL这两款著名的数据库都还快。
3.5.2、FMDB
- FMDB以OC的方式封装了SQLite3的C语言API;
- FMDB是正式基于 SQLite 开发的一套iOS平台的SQLite数据库开源库;
- 使用的时候需要写一些简单的SQLite语句。
- 优点:
- 适用时面向对象,避免了复杂的C语言代码;
- 对比苹果官方的Core Data框架,更加轻量级和灵活,性能高;
- 提供了多线程安全的数据库操作方式,保证多线程安全跟数据准确性;
- 缺点:
- 因为是OC语言开发,只能在iOS平台使用,所以实现跨平台操作时存在限制;
- 数据库/数据报表查看工具:Navicat
- (https://github.com/ccgus/fmdb "Git下载地址")
- 主要结构
- FMDatabase:一个FMDatabase对象代表一个单独的SQLite数据库,通过SQLite语句执行数据库的增删改查操作
- FMResultSet:使用FMDatabase对象查询数据库后的结果集
- FMDatabaseQueue:用于多线程操作数据库,它保证线程安全
3.5.2.1、FMDB使用
使用FMDB的时候首先将FMDB使用cocopods导入到我们的工程
pod 'FMDB'
之后在我们的工程中导入需要的头文件即可:
#import "FMDatabase.h"
开发中我们一般会把数据存储到沙河路径下:
NSString * docPath =
[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];//获取Document沙河地址
- 【使用】第一步 设置数据库名DB
NSString * dbName = @"myDataBase.db" //数据库名称
NSString * fileName=[docPath stringByAppendingPathComponent: dbName];
FMDatabase * fmdb=[FMDatabase databaseWithPath:fileName];//创建并获取数据库信息
if ([fmdb open]) {
NSLog(@"数据库打开成功!");
}else{
NSLog(@"数据库打开失败!");
}
- 【使用】第二步创建数据表Table-executeUpdate
BOOL ret = [m_fmdb open]; //.在数据库中进行增删改查操作时,需要判断数据库是否open,如果open失败,可能是权限或者资源不足
if (ret) {
NSString * tableName = @"myTable" //数据表名称
//create table if not exists myTable:如果不存在myTable,则创建;
//数据表字段
//'id' INTEGER PRIMARY KEY AUTOINCREMENT:数据类型为integer,并且为基建自增;【键要想实现自动增长,不能是text类型】
// name:数据类型为字符串text,默认值为NULL(空)
//age:数据类型为整形integer,默认值为NULL(空)
//sex:数据类型为字符串text,默认值为NULL(空)
NSString *sql = [NSString stringWithFormat:@"create table if not exists %@ ('id' INTEGER PRIMARY KEY AUTOINCREMENT,'name' TEXT NOT NULL,'age' integer NOT NULL,'sex' TEXT NOT NULL)", tableName];
ret = [fmdb executeUpdate: sql];
if (ret) {
NSLog(@"创建表成功");
} ret {
NSLog(@"创建表失败");
}
}
-
【使用】第三步数据增删改查
- 建表、增删改操作都是使用
executeUpdate
,查询是另外的方法executeQuery
; - 星号(*)是选取所有列的快捷方式;
- SQL语句对大小写不敏感,SELECT等效于select;
- 建表、增删改操作都是使用
-
添加数据INSERT INTO
- INSERT INTO 语句用于向表格中插入新的行。
- 语法:INSERT INTO 表格名 VALUES(值1,值2,...)
- 也可以向执行的列插入数据:INSERT INTO 表格名 (列1,列2,...)VALUES(值1,值2,...)
- 注意:
* 列名称和值名称的顺序要一一对应
* 如果列名类型是字符串类型NSString,则需要加单引号''括住,例如:'%@'
* 对数据库进行操作增删改查之前要先打开数据库,操作完毕要关闭数据库。这只是一种严谨的写法,最好这样,否则可能会操作失败。
/**
//1.sql语句中跟columnname 绑定的value 用 ?表示,不加‘’,可选参数是对象类型如:NSString,不是基本数据结构类型如:int,方法自动匹配对象类型
- (BOOL)executeUpdate:(NSString*)sql, ...;
//2.sql语句中跟columnname 绑定的value 用%@或%d表示,不加‘’
- (BOOL)executeUpdateWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2);
//3.sql语句中跟columnname 绑定的value 用 ?表示的地方依次用 (NSArray *)arguments 对应的数据替代
- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments;
//4.同3 ,区别在于多一个error指针,记录更新失败
- (BOOL)executeUpdate:(NSString*)sql values:(NSArray * _Nullable)values error:(NSError * _Nullable __autoreleasing *)error;
//5.同3,区别在于用 ? 表示的地方依次用(NSDictionary *)arguments中对应的数据替代
- (BOOL)executeUpdate:(NSString*)sql withParameterDictionary:(NSDictionary *)arguments;
- (BOOL)executeUpdate:(NSString*)sql withVAList: (va_list)args;
*/
-(BOOL)executeInsertWithTableName:(NSString *) tableName person::(FengPeron *)person{
//当操作数据库的时候,要先打开数据库,使用完之后关闭
NSString * tableName = @"myTable" //数据表名称
NSString *sql = [NSString stringWithFormat:@"insert into '%@' (name,age,sex) values(:name,:age,:sex)",myTable];
return [m_fmdb executeUpdate:sql withParameterDictionary:[person yy_modelToJSONObject]]; //字典方式
}
-(BOOL)executeInsertWithTableName:(NSString *) tableName name:(NString *)name age:(NSInteger)age sex:(NString *)sex{
NSString *sql = [NSString stringWithFormat:@"insert into '%@' (name,age,sex) values('%@',%zd,'%@')",myTable,name,age,sex];
return [m_fmdb executeUpdate:sql];
}
- 删除数据DELETE
- DELETE语句用于删除中的行。
- 语法:DELETE FROM 表名称 WHERE 列名称 = 值;
- 删除多有行:DELETE FROM 表名称 或 DELETE * FROM 表名称。
-(BOOL)executeDeleteWithTableName:(NSString *) tableName name:(NSString *)name {
NSString *sql = [NSString stringWithFormat:@"delete from %@ where name = '%@'", tableName, name]; //删除name为传入的值 这行
return [m_fmdb executeUpdate:sql];
}
- 修改数据UPDATE
- UPDATE语句用于修改表中的数据
- 语法:
* 更新某行的一列:UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值
* 更新某行的若干列:UPDATE 表名称 SET 列名称1 = 新值1,列名称2 = 新值2 WHERE 列名称 = 某值
-(BOOL)executeUpdateWithTableName:(NSString *) tableName name:(NSString *)name sex:(NSString *)sex{
NSString *sql = [NSString stringWithFormat:@"update %@ set age = %d where name = '%@'", tableName,age,name];
return [m_fmdb executeUpdate:sql];
}
- 查找数据SELECT
- SELECT语句用于从表中选取数据。结果被存储在一个结果表中(称为结果集)
- 语法:
- 查询表所有
- SELECT 列名称 FROM 表名称
- SELECT * FROM 表名称
- 模糊查询:
- SELECT * FROM 表名称 WHERE name LIKE '%%%@%%' AND age LIKE '%zd'
- SELECT * FROM 表名称 WHERE age < %zd
- 查询表所有
- 提醒:
- 获取结果集列顺序的时候,一定要取对类型,如果用键值对取值,一定要取对类型,字符串用stringForColumnIndex,整型用intForColumnIndex,长整型用longForColumnIndex,类型不对,会导致返回的结果不对
- 模糊查询时,需要自己拼接产讯语句,不要使用框架提供的;
- '%' 是特殊字符,此时需要使用%转义.即 '%%';
- 自己拼接查询语句时,就需要自己添加单引号把字符串引起来。
-(void)executeQueryWithTableName:(NSString *) tableName {
//错误的拼接和执行模糊查询的方式:
FMResultSet *resultSet = [_db executeQueryWithFormat:@"select * from %@ where name like '%%%@%%'",tableName,keyWord];
// 正确的拼接和执行模糊查询的方式:
NSString *sql = [NSString stringWithFormat:@"select * from %@ where name like '%%%@%%'",tableName,keyWord];
FMResultSet *resultSet = [m_fmdb executeQuery:sql];
/**
FMResultSet根据column name获取对应数据的方法
intForColumn:
longForColumn:
longLongIntForColumn:
boolForColumn:
doubleForColumn:
stringForColumn:
dataForColumn:
dataNoCopyForColumn:
UTF8StringForColumnIndex:
objectForColumn:
*/
-(NSMutableDictionary *)executeQueryWithTableName:(NSString *) tableName {
NSMutableDictionary *mutalDic = [[NSMutableDictionary alloc] init];
NSString *sql = [NSString stringWithFormat:@"select * from %@",tabbleName]; //查询整个表
FMResultSet * resultSet = [m_fmdb executeQuery:sql];
if([resultSet next]) { //如果为真
FengPerson *person = [FengPerson yy_modelWithDictionary:[resultSet resultDictionary]];
[mutalDic setValue: person forKey: person.name];
}
[resultSet close];
return mutalDic;
}
【使用】第四步删除数据表Table-executeUpdate
-(BOOL)executeDeleteWithTableName:(NSString *) tableName {
NSString *sql = [NSString stringWithFormat:@"drop table if exists %@",tabbleName]; //如果表格存在 则销毁
BOOL ret = [fmdb executeUpdate:sql];
return ret;
}
【使用】第五步关闭数据库DB
-(BOOL)closeDbFile{
BOOL ret = YES;
if (fmdb) {
ret= [fmdb close];
}
fmdb = nil;
return ret;
}
3.5.2.2、FMDB的事务
事务(Transaction)是兵法操作的基本单位,是指单个逻辑工作单位执行的一系列操作序列,这些操作要不都成功,要不不就都不成功,事务是数据库维护数据一致性的单位,在每个事物结束时,都能保持数据一致性与准确性,通常事务跟程序是两个不同的概念,一个程序中包含多个事务,事务主要解决并发条件下操作数据库,保证数据一致性。
- 特征
- 原子性(Atomic):事务包含的一系列操作被看成是一个逻辑单元,这个逻辑单元要全部成功,要不全部失败。
- 一致性(Consistency):事务中包含一系列操作,只会合法的数据被写入数据库,一些列操作失败之后,事务会滚到最初创建事务的状态。
- 隔性(Isolation):对数据进行修改的多个事务之间是隔离的,每个事务是独立的,不应该以任何方式来影响其他事务。
- 持久性(Durability):事务完成之后,事务处理的结果必须得到固化,它对于系统的影响是永久的,该修改即使出现系统更新也一直保留,真实的修改了数据库。
- 事务语句
transaction:事务 开启一个事务执行多个任务,效率高
1.fmdb 封装transaction 方法,操作简单
- (BOOL)beginTransaction;//开启事务
- (BOOL)beginDeferredTransaction;
- (BOOL)beginImmediateTransaction;
- (BOOL)beginExclusiveTransaction;
- (BOOL)commit;//提交事务
- (BOOL)rollback; //会滚事务
- 事务语句
开启一个事务,执行多个任务,利用事务处理一系列数据库操作,省时效率高。-(void)executeInsertWithDevices:(NSArray
*)devices { NSString *sql = [NSString stringWithFormat:@"insert into '%@' (deviceId,mac,image,name,productKey,houseId,roomId,nodeType) values(:deviceId,:mac,:image,:name,:productKey,:houseId,:roomId,:nodeType)",FMDB_House_Table_NAME(m_houseId)]; [m_fmdb beginTransaction]; //1.开启事务 BOOL rollBack = NO; NSDate *begin = [NSDate date]; @try { //2.在事务中执行任务 for (ArgDevice *device in devices) { ret = [m_fmdb executeUpdate:sql withParameterDictionary:[device yy_modelToJSONObject]]; } } @catch (NSException *exception) { //3.在事务中执行任务失败,退回开启事务之前的状态 rollBack = YES; [m_fmdb rollback]; } @finally { //4. 在事务中执行任务成功之后 rollBack = NO; [m_fmdb commit]; } NSDate *end = [NSDate date]; NSTimeInterval time = [end timeIntervalSinceDate:begin]; NSLog(@"在事务中执行插入任务 所需要的时间 = %f",time); }
未使用事务,一系列操作
-(void)executeInsertWithDevices:(NSArray *)devices {
NSString *sql = [NSString stringWithFormat:@"insert into '%@' (deviceId,mac,image,name,productKey,houseId,roomId,nodeType) values(:deviceId,:mac,:image,:name,:productKey,:houseId,:roomId,:nodeType)",FMDB_House_Table_NAME(m_houseId)];
NSDate *begin = [NSDate date];
for (ArgDevice *device in devices) {
[m_fmdb executeUpdate:sql withParameterDictionary:[device yy_modelToJSONObject]];
}
NSDate *end = [NSDate date];
NSTimeInterval time = [end timeIntervalSinceDate:begin];
NSLog(@"不在事务中执行插入任务 所需要的时间 = %f",time);
}
学习资料:https://www.jianshu.com/p/7958d31c2a97
3.5.3、CoreData
CoreData 是苹果给出的一套基于 SQLite 的数据存储方案.
不需要自己写任何SQLite语句
该功能依赖于 CoreData.framework 框架,该框架已经很好地将数据库表和字段封装成了对象和属性,表之间的一对多、多对多关系则封装成了对象之间的包含关系。
Core Data的强大之处就在于这种关系可以在一个对象更新时,其关联的对象也会随着更新,相当于你更新一张表的时候,其关联的其他表也会随着更新。
Core Data的另外一个特点就是提供了更简单的性能管理机制,仅提供几个类就可以管理整个数据库
由于直接使用苹果提供的CoreData容易出错,这里提供一个很好的三方库 MagicalRecord 。
学习资料:https://www.jianshu.com/p/5818f70a37cf
三、总结
接口数据缓存存储方式:
-
文件读写
- 大部分接口数据解析之后写入文件保存(读写操作最好 GCD 子线程操作)
-
归档
- 整个应用需要用到的重要数据模型可以考虑采用归档方式(标记状态的数据模型)
-
个人偏好设置(NSUserDefaults)
- 与用户相关的信息、单个标记标识等采用个人偏好设置。
当然对于一些涉及查询、删除、更新等操作的数据模型,就需要使用数据库操作。
使用FMDB写一些SQL语句来执行
推荐使用 CoreData 的封装库 MagicalRecord