Data Persistence

来源:互联网 发布:韩日世界杯假球 知乎 编辑:程序博客网 时间:2024/06/10 03:33

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 = [NSMutableDictionarydictionaryWithContentsOfFile:_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 (?)", [NSNumbernumberWithInt: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(持久的)NSGlobalDomainpersistentLanguages(Identified by the language names)volatileNSRegisterationDomainvolatile

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

0 0