Data Persistence

Sandbox(沙盒机制)

iOS中得沙盒机制(sandbox)是一种安全体系,它规定了应用程序只能在为该应用程序创建的文件夹内读取文件,不可以访问其他地方的内容。所有的非代码文件都保存在这个地方,如图片,声音,属性列表和文本文件等。

  • 每个应用程序都在自己的沙盒内
  • 不能随意跨越自己的沙盒去访问别的应用程序的沙盒内容
  • 应用程序向外请求或接受数据都需要经过权限认证

    一个沙盒中包含四部分

    • .app文件,即可运行的应用文件;
    • Document,苹果建议将程序创建或程序浏览的文件数据保存在该目录下,iTunes备份和恢复时会包括该目录;
    • Library,存储程序的默认设置或其它状态信息;
    • Library/Caches,存放缓存文件,iTunes不会备份此目录,此目录下的文件不会在应用退出删除;
    • tmp,创建和存放临时文件的地方,iTu不会备份此目录。

代码获取沙盒路径的方法

  1. 获取根目录

    NSString *homePath = NSHomeDirectory();

  2. 获取Document目录 NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES)[0];

  3. 获取Cache目录 NSString *cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask, YES)[0];

  4. 获取Library目录 NSString *libPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,NSUserDomainMask, YES)[0];

  5. 获取tmp目录 NSString *tmpPath = NSTemporaryDirectory();

iOS数据持久化技术

数据持久化既是,能将内存中的数据模型转换为存储模型,并能在将来需要时将存储模型还原为数据模型的一种机制。

说明 : 通俗的讲,也就是将数据保存在非易失性的设备中,并且能在需要时恢复。针对苹果设备来说,就是从闪存到内存的过程。

iOS开发中数据持久化的方法

  • Row File APIs(C语言的文件操作,iOS的NSFilemanager)
  • NSUserDefaults (默认保存文件在对应的程序包sandbox的目录下的library/Preferences)
  • Plist(属性列表)
  • NSCoding + Archiver&Unarchiver (对象归档)
  • SQLite (数据库)
  • FMDB (对SQLite的封装)

@property (nonatomic, strong) NSString *filePath;
@property (nonatomic, strong) UITextField *textField;

#define kFileName                 @"test.txt"


ROW APIs

C语言文件操作### ROW APIs
  1. 创建文件路径

     //创建文件存放路径(一般需要保存的文件存放在sandbox的Document目录下)
     - (void)setupPath
     {
         NSString *documentDirectory = NSSearchPathForDirectorieInDomains(NSDocumentDirectory,NSUserDomainMask,YES)[0];
         self.filePath = [documentDirectory stringByAppendingPathComponent:kFileName];   
     }
    
  2. 文件的写入

     - (void)saveData
     {
         // oc文件路径转化为c
         const char *filePath = [_filePath UTF8String];
         // 打开文件
         FILE *fp = fopen(filePath, "w+");
         if (NULL == fp) {
    
             perror("fopen");
             return;
         }
         // 将_textFiled的内容写到文件
         const char *content = [_textField.text UTF8String];
         size_t size = fwrite(content, BUFSIZE, 1, fp);
         fclose(fp);
         if (size > 0) {
          NSLog(@"Saved data successfully");
         }
     }
    
  3. 文件的读取

     - (void)loadData
     {
         // 文件路径
         const char *filePath = [_path UTF8String];
         NSLog(@"%s", filePath);
         // 打开文件
         FILE *fp = fopen(filePath, "r");
         if (fp == NULL) {
             perror("fopen");
             return;
         }
         //读取文件内容到内存
         char buf[BUSIZ] = {0};
         //获取文件大小
         fseek(fp,SEEK_END);
         long size = ftell(fp);
         fread(fp,size,1,buf);
         //赋值给_textField
         NSString *str = [NSString stringWithUTFString:str];
         if(str != NULL && ![str isEqualToString:@""]);{
             _textField.text = str;
         }
         fclose(fp);
     }
    
OC NSFileManager文件管理器操作
  1. 创建文件路径

     //创建文件存放路径(一般需要保存的文件存放在sandbox的Document目录下)
     - (void)setupPath
     {
         NSFileManager *fileManager = [NSFileManager defaultManager];
         NSString *documentDirectory = NSSearchPathForDirectorieInDomains(NSDocumentDirectory,NSUserDomainMask,YES)[0];
         //创建文件目录
         NSString *test = [documentDirectory stringByAppendingPathComponent:@"test"];
         [fileManager createDirectoryAtPath:test withIntermediateDirectories:YES attributes:nil error:nil];
         self.filePath = [documentDirectory stringByAppendingPathComponent:kFileName];   
         NSString *content = nil;
         if(![fileManager fileExistsAtPath:self.filePAth]){
             [fileManager createFileAtPath:self.filePath contents:[content dataUsingEncoding:NSUTF*String] attributes:nil];
         }
     }
    
  2. 文件的写入

     - (void)saveData
     {
         NSError *error;
         [self.textField.text writeToFile:self.filePath atomically:YE];
         if(error){
             NSLog(@"Error : %@",error);
             return;
         }
         NSLog(@"save data successfully");
     }
    
  3. 文件的读取

     - (void)loadData
     {
         NSError *error;
         NSString *content = [[NSString alloc]initWithContentsOfFile:self.filePath encoding:NSUTF*String error:&error];
         if(error){
             NSLog(@"Error : %@",error);
             return;
         }
         self.textField.text = content;
         NSLog(@"load data successfully");
     }
    

NSUserDefaults

  • 直接使用原始的文件操作API,不管是C语言的还是OC的都不太方便
  • Cocoa会为每个app自动创建一个数据库,用来存储App本身的偏好设置,如:开关 值,音量值之类的少量信息
  • NSUserDefaults使用时用 [NSUserDefaults standardUserDefaults] 接口获取单例对象
  • NSUserDefaults本质上是以Key-Value形式存成plist文件,放在App的 Library/Preferences目录下
  • 这个文件是不安全的,所以千万不要用NSUserDefaults来存储密码之类的敏感信息,用户名和密码应该使用KeyChains来存储

  • 文件的写入

     - (void)saveData
     {
         NSUserDefaults *userDefaults = [NSUserDefaults
     standardUserDefaults];
         float progress = [self.progressTextField.text floatValue];
         [userDefaults setFloat:progress forKey:@"progress"];
         [userDefaults setObject:self.inputTextField.text
         forKey:@"input"];
         database
         // keeps the in-memory cache in sync with a user’s defaults
         [userDefaults synchronize];
      }
    
  • 文件的读取

     - (void)loadConfig
     {
         NSUserDefaults *userDefaults = [NSUserDefaults
         standardUserDefaults];
         self.toggle.on = [userDefaults boolForKey:@"toggle"];
         self.progressView.progress = [userDefaults
         floatForKey:@"progress"];
         self.progressTextField.text = [NSString stringWithFormat:@"%.2f",
         self.progressView.progress];
         self.inputTextField.text = [userDefaults stringForKey:@"input"];
     }
    

    说明: 对NSUserDefaults单例对象的操作,实质上还是对PList文件 (Library/Preferences/.plist)的读写,只是Apple帮我们封装好了 读写方法。

Plist

  • NSUserDefaults只能读写Library/Preferences/.plist这个 文件
  • PList文件是XML格式的,只能存放固定数据格式的对象
  • PList文件支持的数据格式有NSString, NSNumber, Boolean, NSDate, NSData, NSArray,和NSDictionary。其中,Boolean格式事实上以[NSNumber numberOfBool:YES/NO];这样的形式表示。NSNumber支持float和int两种格式。
  1. 创建文件路径 ```

    • (void)setUpPlist { NSFileManager fileManager = [NSFileManager defaultManager]; NSStringdocumentDirectory = NSSearchPathForDirectorieInDomains(NSDocumentDirectory,NSUserDomainMask,YES)[0]; self.filePath = [documentDirectory stringByAppendingPathComponent:@"test.plist"]; 
      } ```
  2. 写入plist文件

     - (void)saveData
     {
         NSMutableDictionary *dict = [NSMutableDictionary dictionary];
         dict[@"textField"] = _textField.text;
         if (![dict writeToFile:_path atomically:YES]) {
             NSLog(@"Error!!!");
             return;         
         }
     }
    
  3. plist文件的读取

     - (void)loadData
     {
         NSMutableDictionary *dict = [NSMutableDictionary
    dictionaryWithContentsOfFile:_path];
         NSString *content = dict[@"textField"];
         if (content && content.length > 0) {
         _textField.text = content;
         }
     }
    

Archiver&Unarchiver

  • NSUserDefaults和Plist文件支持常用数据类型,但是不支持自定义的数据对象
  • Cocoa提供了NSCoding和NSKeyArchiver两个工具类,可以把我们自定义的对象编码 成二进制数据流,然后存进文件里面
  1. NSCoding协议


     //解档,解码。解档之后会生成一个该类的对象(解码后对模型的属性赋值)
     - (id)initWithCoder:(NSCoder *)aDecoder
     {
         self = [super init];
         if (self) {
             self.name = [aDecoder decodeObjectForKey:kNameKey];
             self.age = [aDecoder decodeIntForKey:kAgeKey];
             self.studyID = [aDecoder decodeObjectForKey:kStudyIDKey];
         }
         return self;
     }
    
     //归档,编码 (将模型的属性编码)
     - (id)initWithCoder:(NSCoder *)aDecoder
     {
         self = [super init];
         if (self) {
             self.name = [aDecoder decodeObjectForKey:kNameKey];
             self.age = [aDecoder decodeIntForKey:kAgeKey];
             self.studyID = [aDecoder decodeObjectForKey:kStudyIDKey];
         }
         return self;
     }
    

  2. 保存数据

     - (void)saveData{
         _student = [[YMStudent alloc] init];
         _student.name = _name.text;
         _student.age = [_age.text intValue];
         _student.studyID = _studyID.text;
         if ([NSKeyedArchiver archiveRootObject:_student toFile:self.filePath]) {
          NSLog(@"Archive successfully!");
         }
     }
    
  3. 读取数据

     - (void)loadData{
         _student = [NSKeyedUnarchiver unarchiveObjectWithFile:self.filePAth];
         if (student != nil) {
             _name.text = student.name;
             _age.text = [@(student.age) stringValue];
             _studyID.text = student.studyID;
          NSLog(@"Archive successfully!");
         }
     }
    

SQLite

SQLite

SQLite shell command

SQLite shell command

SQLite usage

创建数据库连接对象: sqlite3

创建预编译语句对象:sqlite3_stmt

  1. 打开数据

    sqlite3_open()

  2. 将SQL语句转换为预编译语句对象

    sqlite3_prepare_v2()

  3. 执行预编译语句,每次处理一次,不需要返回值的语句(如INSERT,UPDATE,DELETE),只需要执行该函数即可

    sqlite3_step()

  4. 获取数据库中得不同类型的值

    • sqlite3_column_blob()
    • sqlite3_column_bytes()
    • sqlite3_column_bytes16()
    • sqlite3_column_count()
    • sqlite3_column_double()
    • sqlite3_column_int()
    • sqlite3_column_int64()
    • sqlite3_column_text()
    • sqlite3_column_text16()
    • sqlite3_column_type()
    • sqlite3_column_value()
  5. 销毁有sqlite3_prepare_v2()函数创建的预处理语句对象

    sqlite3_finalize()

  6. 关闭数据库(即销毁数据库连接对象)

    sqlite3_close()

    FMDB

FMDB Document

Download FMDB

FMDB数据库操作类对sqlite3的操作进行了便利的封装并保证了多线程下的安全地操作数据库

FEMDB有三个主要的类
  1. FMDatabase - 表示一分单独的SQLite数据库,用来执行SQLite的命令
  2. FMResultSet - 表示FMDatabase执行查询结果集
  3. FMDatabaseQueue - 在多线程中执行多个查询或更新使用该类是线程安全的
数据库的创建

#define kDBFileName      @"database.sqlite"

NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES)[0];
NSString *DBPath = [docPath stringByAppendingPathComponent:kDBFileName];
FMDatabase *database = [FMDatabase databaseWithPath:DBPath];
打开数据库
if(![database open]){
    NSLog(@"Open database failed !");
    return;
}
执行更新
  • executeUpdate

一切不是SELECT命令的命令都是为更新。包括CREATE,UPDATE,INSERT,ALTER等。

执行结果返回一个BOOL值。YES表示成功,NO表示失败。可以调用 -lastErrorMessage和 -lastErrorCode方法获取更多的信息。

执行查询
  • executeQuery

执行结果返回FMResultSet对象,失败返回nil。同样可以调用-lastErrorMessage和 -lastErrorCode方法获取更多的信息。获得的FMResultSet对象rs后,既是只有一条记录,一样使用[rs next];

eg: FMResultSet *rs = [db executeQuery:@"SELECT Name, Age, FROM PersonList"];
while ([rs next]) {
    NSString *name = [rs stringForColumn:@"Name"];
    int age = [rs intForColumn:@"Age"];
}
FMResultSet根据类型提取数据
- objectForColumnName:
- longForColumn:
- nlongLongIntForColumn:
- boolForColumn:
- doubleForColumn:
- stringForColumn:
- dateForColumn:
- dataForColumn:
- dataNoCopyForColumn:
- UTF8StringForColumnName:


以上方法,都有个{type}ForColumnIndex:版本,根据column的位置提取数据

有些时候,只是需要query某一个row里特定的一个数值(比如只是要找John的年龄),FMDB 提供了几个比较简便的方法。这些方法定义在FMDatabaseAdditions.h,如果要使用,记得先 import进来

//找地址
NSString *address = [db stringForQuery:@"SELECT Address FROM PersonList WHERE Name = ?",@"John”];
NSString *address = [db stringForQuery:@"SELECT Address FROM PersonList WHERE Name = ?",@"John”];
//找年齡
int age = [db intForQuery:@"SELECT Age FROM PersonList WHERE Name    = ?",@"John”];
关闭数据库
  • [FMDatabase close];
数据库的批量操作

使用FMDatabase 的executeStatements:或者executeStatements:withResultBlock:(是否需 要返回结果)

NSString *sql = @"create table bulktest1 (id integer primary key autoincrement, x text);"
"create table bulktest2 (id integer primary key autoincrement, y text);"
"create table bulktest3 (id integer primary key autoincrement, z text);"
"insert into bulktest1 (x) values ('XXX');"
"insert into bulktest2 (y) values ('YYY');"
"insert into bulktest3 (z) values ('ZZZ');";
success = [database executeStatements:sql];

或者

sql = @"select count(*) as count from bulktest1;"
}];
"select count(*) as count from bulktest2;"
"select count(*) as count from bulktest3;";
success = [self.db executeStatements:sql withResultBlock:^int(NSDictionary *dictionary) {
NSInteger count = [dictionary[@"count"] integerValue];
XCTAssertEqual(count, 1, @"expected one record for dictionary %@",
dictionary);
return 0;
}];
参数绑定
**INSERT INTO myTable VALUES (?, ?, ?)**    

问号只是占位,执行操作可以使用NSArray, NSDictionary, or a va_list来匹配参数 你也可以选择使用命名参数语法:INSERT INTO myTable VALUES (:id, :name, :value) 参数名必须以冒名开头。SQLite本身支持其他字符($,@),Dictionary key的内部实现是冒号 开头。注意你的NSDictionary key不要包含冒号

NSDictionary *argsDict = @{@"name":@"Jason"};
[db executeUpdate:@"INSERT INTO myTable VALUES (:name)"withParameterDictionary:argsDict];
FMDatabaseQueue 及线程安全

不能使⽤用同⼀个FMDatabase在不同线程中操作,多线程的操作是通过FMDatabaseQueue实现

首先创建队列,然后把单任务包装到事务里,串行执行

FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
[queue inDatabase:^(FMDatabase *db) {
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]];
FMResultSet *rs = [db executeQuery:@"select * from foo"];
while([rs next]) {
...
}

事务的回滚:(当前的队列的操作的取消)

[queue inTransaction:^(FMDatabase *db, BOOL *rollback) { 
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]];
if (whoopsSomethingWrongHappened) {
}
*rollback = YES;
return;
// etc...
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber
numberWithInt:4]];
}];

FMDatabaseQueue会在同一个队列里 同步执行任务, GCD也会按它接收的块的顺序来执行

关于Setting Bundles

  • Setting Bundle的概念更多地应该是在App的配置选择上
  • Setting Bundle可以给用户提供一个从《设置》应用里去配置应用程序的方式
  • 从开发者的角度来看,一般需要频繁修改的配置选项,如游戏的音量和控制选项等最好 放到app内部的设置页里,而类似于邮箱应用中的邮件地址和服务器的设置等不需要频 繁更改的配置项可以放到Setting Bundle里
  • 从《设置》应用中进行设置,实际上是操作iOS配置系统中的应用程序域(Application Domain),是持久的

iOS的配置系统中存在如下一些域,将来查询时严格按照如下列出域的顺序进行查找

DomainState
  
NSArgumentDomainvolatile(易失的)
Application(Identified by the app's identifier)persistent(持久的)
NSGlobalDomainpersistent
Languages(Identified by the language names)volatile
NSRegisterationDomainvolatile

registerDefaults:方法是在NSRegistrationDomain域上进行配置的,所以仅仅是存在于 内存中的,易失的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值