ios - 读写应用程序的几种方式之NSUserDefaults、文件写入、SQLite数据库

iOS应用程序中存储数据的几种方式

在iOS系统中对数据做持久化存储的方式一般有5中方式:

  • 文件写入
  • 对象归档
  • SQLite数据库
  • CoreData
  • NSUserDefaults


    ios - 读写应用程序的几种方式之NSUserDefaults、文件写入、SQLite数据库_第1张图片

闪存和沙盒

在iPhone/iPad设备上包含闪存,它的功能和一个硬盘的功能等价。当设备断电后数据还能被保存下来。应用程序可以将文件保存下来,并能从闪存中读取它们。但是我们的应用程序不能访问整个闪存,闪存上上面的一部分专门给你的应用程序,这就是你应用程序的沙盒(sandbox)。每个应用程序只能看到自己的沙盒,这就防止了对其他应用程序的文件进行读取的活动。

用户默认设置 - NSUserDefaults

Apple将整个首选项系称为应用程序首选项,用户可以通过它定制应用程序。应用程序首选项系统负责如下任务:

  • 将首选项持久化到设备中
  • 将各个应用程序的首选项彼此分开
  • 通过iTune将应用程序首选项备份到计算机,以免在需要恢复设备时用户丢失首选项。

我们可以通过一个易于使用的API与应用程序首选项交互,该API主要由单例类NSUserDefaults组成。
类NSUserDefaults的工作原理类似于NSDictionary,主要差别在于NSUserDefaults是单例类,且在它可以存储的对象类型方面受到很多的限制。应用程序的所有首选项都是以“键-值”对的方式存储在NSUserDefaults单例类中

访问应用程序首选项

访问应用程序首选项必须获取指向应用程序的NSUserDefaults单例的引用:

 NSUserDefaults * userDefault = [NSUserDefaults standardUserDefaults];

然后就可以读写默认设置数据库了。提供了以下方法写入数据以及访问数据:

- (void)setObject:(nullable id)value forKey:(NSString *)defaultName;
- (void)setInteger:(NSInteger)value forKey:(NSString *)defaultName;
- (void)setFloat:(float)value forKey:(NSString *)defaultName;
- (void)setDouble:(double)value forKey:(NSString *)defaultName;
- (void)setBool:(BOOL)value forKey:(NSString *)defaultName;
- (void)setURL:(nullable NSURL *)url forKey:(NSString *)defaultName NS_AVAILABLE(10_6, 4_0);

具体使用哪一个函数取决于要存储的数据类型。函数setObject:forKey可以存储NSString、NSData、NSArray以及其他常见的对象类型。例如使用键age存储一个整数20,用键oc存储一个字符串HelloWorld:

    //存储一个整数
    [userDefault setInteger:20 forKey:@"age"];
    
    //存储一个字符串
    [userDefault setObject:@"HelloWorld" forKey:@"oc"];

但是当我们将数据写入默认设置数据库时,并不一定会立即保存这些数据。为了确保所有数据都写入了用户默认设置,可以使用synchronize方法:

[userDefault synchronize];
示例代码
    
    NSUserDefaults * userDefault = [NSUserDefaults standardUserDefaults];
    
    //存储一个整数
    [userDefault setInteger:20 forKey:@"age"];
    
    //存储一个字符串
    [userDefault setObject:@"HelloWorld" forKey:@"oc"];
    
    // 马上存储
    [userDefault synchronize];
    
    NSInteger integer =[[NSUserDefaults standardUserDefaults] integerForKey:@"age"];
    NSString * string = [[NSUserDefaults standardUserDefaults] stringForKey:@"oc"];
    
    NSLog(@"integer-%ld",integer);
    NSLog(@"string-%@", string);
打印结果

对NSUserDefaults的简单封装

虽然该通过这样的方实现了对数据用键值对的方式进行存取,但是每次存取都要写一大堆的相同的代码。为了实现封装,可以新建一个类,在这个类实现对NSUserDefaults存储,读取和删除:

#import "LMHUserDefault.h"

@implementation LMHUserDefault

// 存储用户偏好设置到NSUserDefults
+ (void)setUserData:(id)data forKey:(NSString*)key
{
    if (data == nil)
    {
        return;
    }
    
    else
    {
        [[NSUserDefaults standardUserDefaults] setObject:data forKey:key];
        [[NSUserDefaults standardUserDefaults] synchronize];
    }
}

//读取用户偏好设置
+ (id)readUserDataWithKey:(NSString*)key
{
    id temp = [[NSUserDefaults standardUserDefaults] objectForKey:key];
    
    if(temp != nil)
    {
        return temp;
    }
    
    return nil;
}

//删除用户偏好设置
+ (void)removeUserDataWithkey:(NSString*)key
{
    [[NSUserDefaults standardUserDefaults] removeObjectForKey:key];
}

那么在要用到的地方只需要将这个类的头文文件导入,然后调用相应的类方法即可。以上的代码就可以用下面的方式实现:

- (void)viewDidLoad {
    [super viewDidLoad];

    //存储一个整数
    [LMHUserDefault setUserData:@(20) forKey:@"age"];
    //存储一个字符串
    [LMHUserDefault setUserData:@("HelloWorld") forKey:@"oc"];
    
    // 读取
    NSInteger integer = (NSInteger)[LMHUserDefault readUserDataWithKey:@"age"];
    NSString * string  = (NSString *)[LMHUserDefault readUserDataWithKey:@"oc"];
    
    // 打印
    NSLog(@"age----%ld", integer);
    NSLog(@"oc-----%@", string);
}

打印结果:


直接访问文件系统

通过NSUserDefaults存储的对象有一些限制,但是铜鼓直接范文文件系统的方式可以存储任何类型的数据。
直接访问文件系统是指打开文件并读写其内容。例如从Internet下载的文件、应用程序创建的文件扥,但并非能存储到任何地方。因为iOS中有沙盒的限制。
应用程序目录中有四个位置专门用来存取应用程序的数据:

  • Library/Caches

    • 用户缓存从网络获取的数据、通过大量计算得到的数据。
    • 该目录存放的数据将在应用程序关闭时得以保留,所以** 如果希望下载的文件永久有效(除非用户卸载软件),那么可以放在这里。**
  • Library/Preference

  • Documents

    • 应用程序数据的主要存储位置
    • 设备与iTunes同步的时候,该目录将备份到计算机中,因此不能将大文件放在这里,备份起来很麻烦
    • 另外,审核的时候如果发现将下载的东西放在这里,审核不会通过
  • tmp

    • 应用程序储存临时文件夹。
    • 由于是临时文件,所以不靠谱,里面的东西随时都可能被清理掉。

获取文件路径

每个文件都有路径,这个路径是文件在文件系统中的准确位置。要让应用程序能够读写其沙盒中的文件,需要指定该文件的完整路径。在Core Foundation中提供了一个名为NSS eachPathForDirectoriesInDomains的C语言函数,它返回的是指向应用程序的目录Documents或Library/Caches路径。由于这个函数可以返回多个目录,因此该函数调用的结果是一个NSArray对象。使用该函数来获取指向目录Documents或Library/Caches路径时,它返回的数组将只包含一个NSString;要从数组中获取该路径的NSString,可以将数组的索引设置为0.

获取Library/Caches的文件路径
    // 获取文件路径
    NSString * cacheDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];

另外NSString的stringByAppendingPathComponent可以将两个路径拼接起来。

获取Documents的文件路径

如果要获取Documents中的特定文件路径,只需要将NSCachesDirectory换成NSDocumentDirectory即可:

    // 获取Documents目录中的文件路径
    NSString * docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
获取tmp的文件路径

获取tmp的文件路径可以通过Core Foundation中的另一套C语言函数NSTemporaryDirectory

    // 获取tmp的文件路径
    NSString * tmpFile = NSTemporaryDirectory();

读取数据

要读写文件中的数据,首先要检查文件是否存在,如果不存在则需要的话需要先创建它。

    // 检查myPath的文件是否存在
    
    if ([[NSFileManager defaultManager] fileExistsAtPath:myPath]) {
        
        // 存在
    }else{
        // 不存在
    }

然后可以使用类NSFileHandle提供的方法获取文件的引用,再进行读取和写入数据,NSFileHandle中提供的获取文件引用的方法:

+ (nullable instancetype)fileHandleForReadingAtPath:(NSString *)path;
+ (nullable instancetype)fileHandleForWritingAtPath:(NSString *)path;
+ (nullable instancetype)fileHandleForUpdatingAtPath:(NSString *)path;

+ (nullable instancetype)fileHandleForReadingFromURL:(NSURL *)url error:(NSError **)error NS_AVAILABLE(10_6, 4_0);
+ (nullable instancetype)fileHandleForWritingToURL:(NSURL *)url error:(NSError **)error NS_AVAILABLE(10_6, 4_0);
+ (nullable instancetype)fileHandleForUpdatingURL:(NSURL *)url error:(NSError **)error NS_AVAILABLE(10_6, 4_0);

如果要写入数据,可以使用NSFileHandle中的writeData方法.
在最后还要关闭手柄。所以总体而言,分为四个步骤:

  • 检查指定文件是否存在
  • 获取该文件的引用
  • 读取文件/写入文件
  • 关闭手柄
    // 检查myPath的文件是否存在
    if ([[NSFileManager defaultManager] fileExistsAtPath:myPath]) {
        
        // 存在
    }else{
        // 不存在
    }
    
    // 获取文件引用
    NSFileHandle * fileHandle = [NSFileHandle fileHandleForWritingAtPath:myPath];
    
    // 写入数据
    [fileHandle writeData:stringData];
    
    // 关闭手柄
    [fileHandle closeFile];

使用SQlite3存储和读取数据

SQLite3是嵌入在IOS中的关系型数据库,对于存储大型的数据很有效。SQLite3不必将每个对象都加载到内存中。在iOS中,对SQLite3有如下基本操作:

  • 打开或者创建数据库
    // 打开或创建数据库
    sqlite3 * database;
    if (sqlite3_open([self.databaseFilePath UTF8String], &database) != SQLITE_OK) {
        sqlite3_close(database);
        NSAssert(0, @"打开数据库失败!");
    }
  • 关闭数据库
    // 关闭数据库
    sqlite3_close(database);
  • 创建一个表格
    // 创建数据库表
    NSString * createSQL = @"CREATE TABLE IF NOT EXISTS FIELDS (TAG INTEGER PRIMARY KEY, FIELD_DATA TEXT);";
    char * errorMsg;
    if (sqlite3_exec(database, [createSQL UTF8String], NULL, NULL, &errorMsg) != SQLITE_OK) {
        sqlite3_close(database);
        NSAssert(0, @"创建数据库表格错误:%s", errorMsg);
    }
  • 查询操作
    // 执行查询
    NSString * query = @"SELECT TAG, FIELD_DATA FROM FIELDS ORDER BY TAG";
    sqlite3_stmt * statement;
    if (sqlite3_prepare_v2(database, [query UTF8String], -1, &statement, nil) == SQLITE_OK) {
        
        // 依次读取数据数据库表格FIELDS中每行的内容,并显示在对应的textFiled上
        while (sqlite3_step(statement) == SQLITE_ROW) {
            
            // 获取数据
            int tag = sqlite3_column_int(statement, 0);
            char * rowData = (char *)sqlite3_column_text(statement, 1);
            
            // 根据tag获得对应的textField
            UITextField * textfield = (UITextField *)[self.view viewWithTag:tag];
            
            // 设置文本
            textfield.text = [[NSString alloc] initWithUTF8String:rowData];
            
        }
        sqlite3_finalize(statement);
    }
  • 使用约束变量
    实际操作中经常使用叫做约束变量的东西来构造SQL字符串,从而进行增删改查的操作。
    // 向表格插入4行数据
    for (int i = 0; i < 4; i++) {
        
        //根据tag获取textfiel
        UITextField * textfield = (UITextField *)[self.view viewWithTag:i];
        
        // 使用约束变量插入数据
        char * updata = "INSERT OR REPLACE INTO FIELDS(TAG, FIELDS_DATA) VALUES(?, ?);";
        sqlite3_stmt * stmt;
        
        if (sqlite3_prepare_v2(database, updata, -1, &stmt, nil) == SQLITE_OK) {
            
            sqlite3_bind_int(stmt, 1, i);
            sqlite3_bind_text(stmt, 2, [textfield.text UTF8String], -1, NULL);
        }
        
        char * errorMsg = NULL;
        if (sqlite3_step(stmt) != SQLITE_OK) {
            NSAssert(0, @"更新数据库表FIELDS出错:%s",errorMsg);
        }
        sqlite3_finalize(stmt);
    }

你可能感兴趣的:(ios - 读写应用程序的几种方式之NSUserDefaults、文件写入、SQLite数据库)