iOS数据存储

一、iOS沙盒机制

  • iOS的每个应用都有属于自己的存储空间,即沙盒
    应用只能访问自己的沙盒,不可访问其他区域

  • 沙盒主要作用就是文件存储。如果应用需要进行文件操作,则必须将文件存放在沙盒中,尤其是数据库文件,在电脑上操作时,可以去访问,但是如果要装在真机上可以使用,必须将数据库文件拷贝至沙盒中。

二、应用沙盒

2.1、沙盒路径下的三个文件

沙河路径.png
  • 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];

查看沙盒文件,如下图:

image

读取刚存储的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);
}
image.png

3.3、NSKeyedArchiver归档存储

归档一般都是保存自定义对象的时候,使用归档.因为plist文件不能够保存自定义对象.
plist 与 NSUserDefaults(个人偏好设置)两种类型的储存只适用于系统自带的一些常用类型,而且前者必须拿到文件路径,后者也只能储存应用的主要信息。
如果一个字典当中保存有自定义对象,如果把这个字典写入到文件当中,它是不会生成plist文件的。
所以如果有自定义对象的时候,就需要归档了,归档一般都是保存自定义对象的。

  • 首先新建Person类,并遵守NSCoding协议
    @interface Person : NSObject<NSCoding> //尊村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 <ArgDevice *> *)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 <ArgDevice *> *)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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335