美文网首页
iOS 数据持久化

iOS 数据持久化

作者: Ray0218 | 来源:发表于2019-11-29 17:53 被阅读0次

    iOS常见的数据持久化方式主要有以下几点:

    • plist (属性列表)
    • NSUserDefaults 偏好设置 单例
    • writeToFile 写入文件
    • SQLite 数据库
    • CoreData

    plist存储

    通常叫做plist文件,用于存储在程序中不经常修改、数据量小的数据,不支持自定义对象存储,支持数据存储的类型为:Array,Dictionary,String,Number,Data,Date,Boolean,通常用来存放接口名、城市名、银行名称、表情名等极少修改的数据
    直接创建.plist文件, 如果文件类型是Array就用数组获取,如果是dictionary就用字典获取

    //获取plist文件路径
        NSString *path = [[NSBundle mainBundle] pathForResource:@"testProperty" ofType:@"plist"];
        NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
       // NSArray *array = [NSArray arrayWithContentsOfFile:path];
    
    

    偏好设置(NSUserDefaults)

    用于存储用户的偏好设置,同样适合于存储轻量级的用户数据,数据会自动保存在沙盒的Libarary/Preferences目录下,本质上就是一个plist文件,所以同样的不支持自定义对象存储,支持数据存储的类型为:Array,Dictionary,String,Number,Data,Date,Boolean,可以用做检查版本是否更新、是否启动引导页、自动登录、版本号等等,需要注意的是NSUserDefaults是定时的将缓存中的数据写入磁盘,并不是即时写入,为了防止在写完NSUserDefaults后,程序退出导致数据的丢失,可以在写入数据后使用synchronize强制立即将数据写入磁盘

    //存
    [[NSUserDefaults standardUserDefaults] setObject:phoneNumber forKey:@"key"];
      [[NSUserDefaults standardUserDefaults] synchronize];
    //取
    [[NSUserDefaults standardUserDefaults] objectForKey:@"key"];
    
    

    文件写入(writeToFile)

    一 、沙盒路径

    沙盒主路径:是程序运行期间系统会生成一个专属的沙盒路径,应用程序在使用期间非代码的文件都存储在当前的文件夹路径里面
    Documents:用来存储永久性的数据的文件 程序运行时所需要的必要的文件都存储在这里(数据库)iTunes会自动备份这里面的文件
    Library:用于保存程序运行期间生成的文件
    Caches:文件夹位于Library中用于保存程序运行期间产生的缓存文件
    Preferences:文件夹位于Library中,主要是保存一些用户偏好设置的信息,一般情况下,我们不直接打开这个文件夹 而是通过NSUserDefaults进行偏好设置的存储
    tmp:临时文件夹---程序运行期间产生的临时岁骗会保存在这个文件夹中 通常文件下载完之后或者程序退出的灰自动清空此文件夹iTunes不会备份这里的数据。

    //获取沙盒路径
     NSString *homePath =NSHomeDirectory();
        NSLog(@"%@",homePath);
    
        //第一个参数:要查询的文件的路径
        //第二个参数:要查询路径所属的用户 iOS是单用户
        //第三个参数的意思 YES是绝对路径 NO是相对路径
        //区别于OS-X系统 iOS应用文件夹中通常只有一个文件路径 由于OC同时支持的苹果系列产品的开发 在MacOS里面会同时存在很多软件 通常生成的路径放在一个数组里面
        //iOS端一次只有一个应用 所以取数组唯一的一个元素即可
        NSArray *documentArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, NO);
        NSLog(@"%@",documentArray);//打印的结果是 "~/Documents"
        NSString *documentPath = [documentArray firstObject];
        NSLog(@"documentPath = %@",documentPath);//结果是 ~/Documents
    
       对比以上我们可以打印试着获取几个路径
        NSString *libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES)firstObject];
        NSLog(@"libraryPath = %@",libraryPath);
        NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)firstObject];
        NSLog(@"ChchesPath = %@",cachesPath);
        NSString *preferencePath =[libraryPath stringByAppendingString:@"/Preferences"];
        NSLog(@"%@",preferencePath);
    

    二、简单对象写入文件

    文件的写入会替换调原有文件的内容

    -(void)writeToFiled:(NSString*)appendPath data:(id)data{
       
       
       //第一个参数:要查询的文件的路径
       //第二个参数:要查询路径所属的用户 iOS是单用户
       //第三个参数的意思 YES是绝对路径 NO是相对路径
       //区别于OS-X系统 iOS应用文件夹中通常只有一个文件路径 由于OC同时支持的苹果系列产品的开发 在MacOS里面会同时存在很多软件 通常生成的路径放在一个数组里面
       //iOS端一次只有一个应用 所以取数组唯一的一个元素即可
       //writetofile 保存的数组和字典是以plist格式书写的
       NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask ,YES).firstObject;
       
       NSString *filePath = [documentPath stringByAppendingString:appendPath];
       
       if (![[NSFileManager defaultManager]fileExistsAtPath:filePath]) {
           [[NSFileManager defaultManager]createFileAtPath:filePath contents:nil attributes:nil] ;
       }
       
       NSError *error ;
       BOOL isSuccess =   NO;
       
       if ([data isKindOfClass:[NSString class]]) {
           isSuccess =   [data writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
    //    取
       //    NSString *contentString = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
    
       }else if([data isKindOfClass:[NSArray class]] || [data isKindOfClass:[NSDictionary class]]  || [data isKindOfClass:[NSData class]]){
           isSuccess = [data writeToFile:filePath atomically:YES];
    //    取
       //    NSArray *readArray = [NSArray arrayWithContentsOfFile:path];
    // NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:path];
    // NSData *data = [NSData dataWithContentsOfFile:path];
       }
       
       if (isSuccess && error == nil) {
           NSLog(@"成功") ;
       }
       else{
           
           NSLog(@"error == %@",error) ;
       }
       
    }
    
    

    如果需要将数据持续写入,通过NSFileHandle进行操作

    -(void)writeAddToFiled:(NSString*)appendPath data:(id)data{
      
        NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask ,YES).firstObject;
        
        NSString *filePath = [documentPath stringByAppendingString:appendPath];
        
        if (![[NSFileManager defaultManager]fileExistsAtPath:filePath]) {
            [[NSFileManager defaultManager]createFileAtPath:filePath contents:nil attributes:nil] ;
        }
        NSData *rData = nil ;
        if ([data isKindOfClass:[NSData class]]) {
            rData = data ;
        }else  if ([data isKindOfClass:[NSArray class]] || [data isKindOfClass:[NSDictionary class]]) {
            rData = [NSJSONSerialization dataWithJSONObject:data options:NSJSONWritingPrettyPrinted error:nil];
            
        }else if ([data isKindOfClass:[NSString class]]){
            rData = [data dataUsingEncoding:NSUTF8StringEncoding] ;
        }
        
        NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath] ;
        if (fileHandle) {
            [fileHandle seekToEndOfFile];
            [fileHandle writeData:rData] ;
            [fileHandle closeFile];
        }
        
    }
    

    二、复杂对象写入文件

    在这里有一点是需要我们区分的,归档并不是数据持久化的方式 而是辅助复杂对象转化成简单对象的一种方式 其实真正实现数据持久化的仍然是写入文件writeToFile,归档后的文件是加密的,也更加的安全

    创建Model类继承自NSObject ,遵守NSCoding或者NSSecureCoding协议,重写两个方法。 如果采用NSSecureCoding协议,必须重写supportsSecureCoding 方法并返回YES

    @interface KLUserModel : NSObject<NSSecureCoding>
    @property(nonatomic,strong)NSString *rName;
    @property(nonatomic,assign)int rAge;
    @end
    
    @implementation KLUserModel
    
    
    - (void)encodeWithCoder:(nonnull NSCoder *)coder {
        NSLog(@"encodeWithCoder");
        [coder encodeObject:_rName forKey:@"name"];
        [coder encodeInt:_rAge forKey:@"age"];
    }
    
    - (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder {
        if (self = [super init]) {
                    NSLog(@"initWithCoder");
            self.rName = [coder decodeObjectForKey:@"name"];
            self.rAge = [coder decodeIntForKey:@"age"];
    
        }
        return self ;
    }
    +(BOOL)supportsSecureCoding{
        
        return YES ;
    }
    
    @end
    
    
    //取数据
    -(void)pvt_getModel{
        //会调用对象的initWithCoder方法
        NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES).firstObject ;
        
        NSData *data = [NSData dataWithContentsOfFile:[documentPath stringByAppendingString:@"/user.text"]] ;
        
        if (@available(iOS 11.0, *)) {
            NSError *error ;
            
            id content = [NSKeyedUnarchiver unarchivedObjectOfClass:[KLUserModel class] fromData:data error:&error];
            
        } else {
            //Fallback on earlier versions
            
            //       id content = [NSKeyedUnarchiver unarchiveObjectWithData:data];
            id content = [NSKeyedUnarchiver unarchiveObjectWithFile:[documentPath stringByAppendingString:@"/user.text"]];
            
        }
        
    }
    //存数据
    -(void)pvt_saveModel {
            //会调用对象的encodeWithCoder方法
        KLUserModel *model = [KLUserModel new] ;
        model.rName = @"the name";
        model.rAge = 19;
        
        NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES).firstObject ;
        
        if (@available(iOS 11.0, *)) {
            NSError *error ;
            
            NSData *data = [NSKeyedArchiver  archivedDataWithRootObject:model requiringSecureCoding:YES error:&error] ;
            
            if (error) {
                return;
            }
            
            [data writeToFile:[documentPath stringByAppendingString:@"/user.text"] atomically:YES];
            
        }else{
            
            BOOL isSuccess = [NSKeyedArchiver archiveRootObject:model toFile:[documentPath stringByAppendingString:@"/user.text"]];
            if (isSuccess) {
                NSLog(@"Success") ;
            }
            
        }
        
        
    }
    

    CoreData 数据存储

    1. 导入 CoreData 框架:

    Build Phases -> Link Binary With Libraries -> 添加 CoreData.framework

    2. 创建xcdatamodeld 文件

    File -> New -> File -> iOS -> Core Data -> Data Model -> XXX.xcdatamodeld

    3. 创建实体

    XXX.xcdatamodeld -> Add Entity -> 添加对应的 Attributes
    创建好实体对象XXX.xcdatamodeld之后,右侧属性栏Code Generation下面的Language默认为Swift,这里使用OC,就改成Objective-C;

    • Codegen 默认为Class Definition,如果使用自动创建实体关联文件,则无需更改。通过Xcode的Build会自动生成对应的实体关联文件,但是这些文件不会在目录中显示出来,但对应的格式为:
    //格式
    实体名(表名)+CoreDataClass.h
    实体名(表名)+CoreDataClass.m
    实体名(表名)+CoreDataProperties.h
    实体名(表名)+CoreDataProperties.m
    //例如,实体名(表名)为Userinfo,对应的关联文件为:
    Userinfo+CoreDataClass.h
    Userinfo+CoreDataClass.m
    Userinfo+CoreDataProperties.h
    Userinfo+CoreDataProperties.m
    
    • 如果使用手动创建实体关联文件,Codegen一定得设置为Manual/None,否则会报文件重复错误,选中实体,点击Editor,点击Create NSManagedObject Subclass…生成实体关联文件:
    Userinfo+CoreDataClass.h
    Userinfo+CoreDataClass.m
    Userinfo+CoreDataProperties.h
    Userinfo+CoreDataProperties.m
    
    4. 创建数据库
    /**
     管理数据的对象
     */
    @property (nonatomic, strong) NSManagedObjectContext *rContext;
    
    -(void)pvt_createDB{
        
        NSURL *pathURL = [[NSBundle mainBundle]URLForResource:@"KLCoreModel" withExtension:@"momd"];;
        NSManagedObjectModel *mode = [[NSManagedObjectModel alloc]initWithContentsOfURL:pathURL]; ;
        
         NSPersistentStoreCoordinator *coord = [[NSPersistentStoreCoordinator alloc]initWithManagedObjectModel:mode];
        
        NSString *dpPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingString:@"/userInfo.sqlite"];
        NSURL *url = [NSURL fileURLWithPath:dpPath];
        NSError *error;
        [coord addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error];
        
        if (error == nil) {
            NSLog(@"数据库添加成功");
        } else {
            NSLog(@"数据库添加失败%@",error);
        }
        
        _rContext = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSMainQueueConcurrencyType] ;
        _rContext.persistentStoreCoordinator = coord ;
    }
    
    5. 插入数据
    -(void)pvt_addData{
        Userinfo *rperson = [NSEntityDescription insertNewObjectForEntityForName:@"Userinfo" inManagedObjectContext:self.rContext];
        rperson.rName = @"ray";
        rperson.rAge = 18;
        
       [self.rContext save:nil];
     }
    
    6. 删除数据,修改数据,查询数据
    -(void)pvt_changeData{
        
        NSFetchRequest * fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Userinfo"] ;
        
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"rAge=%d",18] ;
        fetchRequest.predicate = predicate ;
        NSError *error;
    //查询数据,如不设置predicate 返回所有数据
        NSArray *results = [self.rContext executeFetchRequest:fetchRequest error:&error] ;
        if (results) {
            for (Userinfo *user in results) {
    //修改数据
                user.rAge = 11 ;
    //删除数据
              //  [_context deleteObject:user];
            }
            
         [self.rContext save:nil];
         }
        
    }
    

    参考:https://www.jianshu.com/p/5b13960eefa2
    https://www.jianshu.com/p/e41e73f4edec
    https://www.jianshu.com/p/e98daf9fec78
    https://www.jianshu.com/p/1cf3de935d21

    相关文章

      网友评论

          本文标题:iOS 数据持久化

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