美文网首页平时收藏iOS Developer程序员
ios - 读写应用程序的几种方式之NSUserDefaults

ios - 读写应用程序的几种方式之NSUserDefaults

作者: NotFunGuy | 来源:发表于2017-05-13 19:05 被阅读113次

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

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

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


    闪存和沙盒

    在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

        本文链接:https://www.haomeiwen.com/subject/gxbotxtx.html