Sandbox(沙盒机制)
iOS中得沙盒机制(sandbox)是一种安全体系,它规定了应用程序只能在为该应用程序创建的文件夹内读取文件,不可以访问其他地方的内容。所有的非代码文件都保存在这个地方,如图片,声音,属性列表和文本文件等。
- 每个应用程序都在自己的沙盒内
- 不能随意跨越自己的沙盒去访问别的应用程序的沙盒内容
-
应用程序向外请求或接受数据都需要经过权限认证
一个沙盒中包含四部分
- .app文件,即可运行的应用文件;
- Document,苹果建议将程序创建或程序浏览的文件数据保存在该目录下,iTunes备份和恢复时会包括该目录;
- Library,存储程序的默认设置或其它状态信息;
- Library/Caches,存放缓存文件,iTunes不会备份此目录,此目录下的文件不会在应用退出删除;
- tmp,创建和存放临时文件的地方,iTu不会备份此目录。
代码获取沙盒路径的方法
-
获取根目录
NSString *homePath = NSHomeDirectory();
-
获取Document目录 NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES)[0];
-
获取Cache目录 NSString *cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask, YES)[0];
-
获取Library目录 NSString *libPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,NSUserDomainMask, YES)[0];
- 获取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
-
创建文件路径
//创建文件存放路径(一般需要保存的文件存放在sandbox的Document目录下) - (void)setupPath { NSString *documentDirectory = NSSearchPathForDirectorieInDomains(NSDocumentDirectory,NSUserDomainMask,YES)[0]; self.filePath = [documentDirectory stringByAppendingPathComponent:kFileName]; }
-
文件的写入
- (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"); } }
-
文件的读取
- (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文件管理器操作
-
创建文件路径
//创建文件存放路径(一般需要保存的文件存放在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]; } }
-
文件的写入
- (void)saveData { NSError *error; [self.textField.text writeToFile:self.filePath atomically:YE]; if(error){ NSLog(@"Error : %@",error); return; } NSLog(@"save data successfully"); }
-
文件的读取
- (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两种格式。
-
创建文件路径 ```
- (void)setUpPlist { NSFileManager fileManager = [NSFileManager defaultManager]; NSStringdocumentDirectory = NSSearchPathForDirectorieInDomains(NSDocumentDirectory,NSUserDomainMask,YES)[0]; self.filePath = [documentDirectory stringByAppendingPathComponent:@"test.plist"];
} ```
- (void)setUpPlist { NSFileManager fileManager = [NSFileManager defaultManager]; NSStringdocumentDirectory = NSSearchPathForDirectorieInDomains(NSDocumentDirectory,NSUserDomainMask,YES)[0]; self.filePath = [documentDirectory stringByAppendingPathComponent:@"test.plist"];
-
写入plist文件
- (void)saveData { NSMutableDictionary *dict = [NSMutableDictionary dictionary]; dict[@"textField"] = _textField.text; if (![dict writeToFile:_path atomically:YES]) { NSLog(@"Error!!!"); return; } }
-
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两个工具类,可以把我们自定义的对象编码 成二进制数据流,然后存进文件里面
-
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; }
-
保存数据
- (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!"); } }
-
读取数据
- (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 shell command
SQLite usage
创建数据库连接对象: sqlite3
创建预编译语句对象:sqlite3_stmt
-
打开数据
sqlite3_open()
-
将SQL语句转换为预编译语句对象
sqlite3_prepare_v2()
-
执行预编译语句,每次处理一次,不需要返回值的语句(如INSERT,UPDATE,DELETE),只需要执行该函数即可
sqlite3_step()
-
获取数据库中得不同类型的值
- 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()
-
销毁有sqlite3_prepare_v2()函数创建的预处理语句对象
sqlite3_finalize()
-
关闭数据库(即销毁数据库连接对象)
sqlite3_close()
FMDB
FMDB数据库操作类对sqlite3的操作进行了便利的封装并保证了多线程下的安全地操作数据库
FEMDB有三个主要的类
- FMDatabase - 表示一分单独的SQLite数据库,用来执行SQLite的命令
- FMResultSet - 表示FMDatabase执行查询结果集
- 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的配置系统中存在如下一些域,将来查询时严格按照如下列出域的顺序进行查找
Domain | State |
---|---|
NSArgumentDomain | volatile(易失的) |
Application(Identified by the app's identifier) | persistent(持久的) |
NSGlobalDomain | persistent |
Languages(Identified by the language names) | volatile |
NSRegisterationDomain | volatile |
registerDefaults:方法是在NSRegistrationDomain域上进行配置的,所以仅仅是存在于 内存中的,易失的