• [iOS]-数据持久化


    参考博客:

    iOS 沙盒机制
    [iOS开发]iOS持久化
    iOS面试:数据持久化(八)
    iOS 数据持久化 - Sandbox | 8月更文挑战
    iOS 沙盒机制及数据存储等操作

    沙盒机制

    iOS 每个 APP 都有自己的存储空间,这个存储空间叫做沙盒. APP可以在自己的沙盒中进行数据存取操作,但不能访问其他 App 的沙盒空间.对 App 做一些数据存储或者文件缓存时,一般都保存在沙盒中.

    目录结构

    沙盒机制根据访问权限和功能区别分为不同的目录: Documents,Library,tmp,SystemData, 其中library又包含 CachesPreferencesApplication SupportCooikesSplashBoardWebKit

    详细介绍如下:

    • Documents存放的是app数据库文件
      iOS11 以后新增了一个 文件 APP,集中管理 iOS 上应用内创建的文件,以及各个云盘服务中保存的文件。在 iOS 工程 info.plist 中设置 Application supports iTunes file sharingSupports opening documents in place 这两个选项为 YES(默认为 NO),就可以将该应用的沙盒 Documents路径下的文件暴露在文件 APP 中。
    • Library 默认存放设置和其他状态信息。除了caches子目录之外其他目录都会被iclude同步
      • Application Support此目录包含应用程序用来运行但应对用户隐藏的文件,如游戏的新关卡等文件。 iTunesiCloud 会备份该目录。
      • Caches保存应用运行时生成的需要持久化的数据,一般存储体积大、不需要备份的非重要数据,如网络请求的音视频与图片等的缓存。
        iOS 5.0 及以后版本中,Caches 当系统磁盘空间非常低时,系统可能会在极少数情况下该删除目录(APP 正在运行时不会发生),所以尽量保证该路径的文件在 APP 在重新运行时可以得到重新创建。iTunes、iCloud 不会备份该目录
      • Preference保存应用的所有偏好设置。如果看过上篇文章,应该就会记得UserDefaults生成的plist文件就会保存该目录下。 iTunesiCloud 会备份该目录
      • SplashBoard存储启动屏缓存,缓存文件格式为 ktx,本质上就是图片,如果启动屏不生效的问题可以考虑从删除该路径下相关缓存文件这个角度解决
      • WebKit存储 WKWebView 相关的一些数据,如 IndexDBLocalStorageWebSQL
    • tmp保存应用运行时产生的一些临时数据;应用程序退出、系统空间不够、手机重启等情况下系统都会自动清除该目录的数据。 iTunes- 不会备份该目录。
    • AppGroup宿主程序与扩展程序数据共享区域,子目录Library/Preferences,默认没有该目录,当创建 groupUserDefaults 时会创建该目录,UserDefaults 对应 plist 的名称为 group 名称
    • SystemData存放系统数据,无对外暴露的接口

    上方提到的文件App为下方软件(Files):
    软件名称叫Files
    举一个我们在文件App中设置info之后看到的项目的Documents路径下的文件(如果配置完info之后发现文件App里面没有这个项目的对应文件,那就去先手动往这个项目的沙盒中存入一些字符串等数据,存完之后就可以在文件App里看到了):
    在这里插入图片描述

    操作方式

    获取路径地址:

    /// 沙盒主目录
    let path = NSHomeDirectory()
    
    /// Documtents目录
    let documtentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
    
    /// Library目录
    let libraryPath = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true).first!
    
    /// Application Support目录
    let applicationSupportPath = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!
    
     /// Caches目录
    let cachesPath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first!
    
    /// tmp目录
    let tmpPath = NSTemporaryDirectory()
    
    /**
    --------------------------------------------------------
    上面形式获取路径返回值为 String 形式,下面形式返回值为 URL 形式
    可根据实际情况选择合适的方式
    --------------------------------------------------------
    */
    
    let dataURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("sqliteName").appendingPathExtension("sqlite")
    
    
    
    /// AppGroup目录路径
    let appGroupIdentifier = "group.com.star.LTXiOSUtils.extension"
    let groupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appgroupIdentifier)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    比如说我们要获取沙盒根目录,那我们就用(将上方样例中的let替换成NSStringURL就行):

        // 获取沙盒根目录路径
        NSString *homeDir = NSHomeDirectory();
    
    • 1
    • 2

    请添加图片描述

    数据存取

    获取到路径后就可以对数据进行存取了,可以直接进行存取操作的数据结构有:

    • NSMutableArrayNSArray
    • NSDataDataNSMutableData
    • StringNSString
    • NSDictionaryNSMutableDictionary

    字符串存储、读取:

    - (void)saveString{
        //获取Document目录
        NSString *string = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
        //在沙盒文件下创建文件
        NSString *newPath = [string stringByAppendingString:@"/text.txt"];
        //要存入的内容
        NSString *name = @"UMR";
        //写入文件
        [name writeToFile:newPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
    }
    
    - (void)readingString{
        //获取路径
        NSString *str = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
        NSString *newPath = [str stringByAppendingString:@"/text.txt"];
        //读取数据
        NSString *string = [NSString stringWithContentsOfFile:newPath encoding:NSUTF8StringEncoding error:nil];
        NSLog(@"%@",string);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    数组存储、读取:

    - (void)saveArray{
        //1.获取路径
        //获取目录
        NSString *string = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
        //在沙盒文件下创建文件
        NSString *newPath = [string stringByAppendingString:@"/array.plist"];
        //要存入的内容
        NSArray *array = @[@"zhangsan",@"lisi",@"wangwu"];
        //写入
        [array writeToFile:newPath atomically:YES];
        NSLog(@"%@",newPath);
    }
    - (void)readingArray{
        //获取路径
        NSString *str = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
        NSString *newPath = [str stringByAppendingString:@"/array.plist"];
        //读取数据
        NSArray *array = [NSArray arrayWithContentsOfFile:newPath];
        NSLog(@"%@",array);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    字典存储、读取:

    - (void)saveDictionary{
        //获取路径
        NSString *string = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
        NSString *newPath = [string stringByAppendingString:@"/dictionary.plist"];
        //存储内容
        NSDictionary *dict = @{@"n1":@[@"1",@"2",@"3"],@"n2":@{@"a":@"d",@"s":@"g"},@"n3":@"uuummmrrr"};
        [dict writeToFile:newPath atomically:YES];
        NSLog(@"%@",newPath);
    }
    - (void)readingDictionary{
        //获取路径
        NSString *string = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
        NSString *newPath = [string stringByAppendingString:@"/dictionary.plist"];
        //读取内容
        NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:newPath];
        NSLog(@"%@",dic);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    data存储、读取:

    - (void)saveData{
        //获取路径
        NSString *string = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
        //存储路径
        NSString *newPath = [string stringByAppendingString:@"/data.plist"];
        //存储内容
        NSString *str = @"UMR";
        //将string转换成data
        NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
        //写入文件夹
        [data writeToFile:newPath atomically:YES];
        NSLog(@"%@",newPath);
    }
    - (void)readingData{
        //获取路径
        NSString *string = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
        //存储路径
        NSString *newPath = [string stringByAppendingString:@"/data.plist"];
        //从文件读取data
        NSData *data = [NSData dataWithContentsOfFile:newPath];
        //将data转换成为string
        NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@",str);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    image存储 - 需转换为data进行存储和读取(同样也可以转换为base64编码然后以字符串的方式进行存储):

    - (void)saveImage{
        //获取路径
        NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
        //在文件路径下面创建该文件夹
        NSString *newPath = [path stringByAppendingString:@"/image.png"];
        //将.png转为data格式
        NSData *imageData = UIImagePNGRepresentation([UIImage imageNamed:@"name6.png"]);
    //    NSData *imageD = UIImageJPEGRepresentation([UIImage imageNamed:@"name6.png"], 1); 如果是JPG格式的,后面的CGFloat为压缩系数;
        //将data文件写入文件夹
        [imageData writeToFile:newPath atomically:YES];
        NSLog(@"%@",newPath);
    }
    - (void)readingImage{
        //获取路径,获取文件
        NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
        NSString *newPath = [path stringByAppendingString:@"/image.png"];
        NSData *imageData = [NSData dataWithContentsOfFile:newPath];
        //将data转为图片格式
        UIImage *image = [UIImage imageWithData:imageData];
        //获取 - 赋值
        UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(120, 120, 120, 120)];
        imageView.image = image;
        [self.view addSubview:imageView];
        NSLog(@"%@",newPath);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    归档与反归档 - 复杂数据的沙盒存储

    先创建一个Person对象,声明相关属性:

    //归档必须遵守NSCoding协议
    @interface Person : NSObject<NSCoding>
    @property (nonatomic,strong) NSString *name;
    @property (nonatomic,strong) NSString *gender;
    @property (nonatomic,assign) NSInteger age;
    @end```
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    .m文件中实现这两个NSCoding协议方法:

    @implementation Person
    
    //对对象进行归档的时候调用
    - (void)encodeWithCoder:(NSCoder *)aCoder{
    	[aCoder encodeObject:self.name forKey:@"name"];
    	[aCoder encodeObject:self.gender forKey:@"gender"];
    	[aCoder encodeInteger:self.age forKey:@"age"];
    }
    //对对象进行反归档的时候调用
    - (instancetype)initWithCoder:(NSCoder *)aDecoder{
    	if (self = [super init]) {
    		self.name = [aDecoder decodeObjectForKey:@"name"];
    		self.gender = [aDecoder decodeObjectForKey:@"gender"];
    		self.age = [aDecoder decodeIntegerForKey:@"age"];
    	}
    	return self;
    }
    @end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    归档:

    - (void)saveData{
        Person *person = [[Person alloc] init];
        person.name = @"umr";
        person.gender = @"m";
        person.age = 18;
        //进行规档操作
        //1.创建一个NSMutableData来存储归档后的数据
        NSMutableData *Data = [[NSMutableData alloc] init];
        //2.创建归档器
        NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:Data];
        //3.进行归档
        [archiver encodeObject:person forKey:@"person"];
        //4.归档结束
        [archiver finishEncoding];
        //获取沙盒路径
        NSString *SDPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
        NSString *newPath = [SDPath stringByAppendingString:@"/archiver.plist"];
        //写入文件
        [Data writeToFile:newPath atomically:YES];
        NSLog(@"%@",newPath);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    反归档:

    - (void)readData{
    	//获取沙盒路径
    	NSString *path = [[self documentPath] stringByAppendingString:@"/archiver.plist"];
    	//获取数据
    	NSData *data = [NSData dataWithContentsOfFile:path];
    	NSKeyedUnarchiver *Unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
    	Person *person1 = [Unarchiver decodeObjectForKey:@"person"];
    	[Unarchiver finishDecoding];
    	NSLog(@"%@ %ld %@",person1.name,person1.age,person1.gender);
    }
    
    - (NSString *)documentPath{
        return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    对象数组的归档与反归档

    - (void)codeArray{
    	//创建两个person对象
    	Person *person1 = [[Person alloc] init];
    	person1.name = @"huahua";
    	Person *person2 = [[Person alloc] init];
    	person2.name = @"UMR";
    	//#存入数组 - 数组中的复杂对象必须遵守NSCoding协议,就可以直接对数组进行归档
    	NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:person1,person2, nil];
    	//获取路径
    	NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
    	NSString *path = [documentPath stringByAppendingPathComponent:@"array.plist"];
    	NSLog(@"%@",path);
    	//归档
    	[NSKeyedArchiver archiveRootObject:array toFile:path];
    	//反归档
    	NSArray *arr = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
    	//    Person *per = arr[0];
    	NSLog(@"%@",arr);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    持久化数据存储的方式

    XML属性列表(plist)

    属性列表是一种XML格式的文件,拓展名为plist

    如果对象是NSString、NSDictionary、NSArray、NSData、NSNumber等类型,就可以使用
    writeToFile:atomically:方法直接将对象写到属性列表文件中

    例如我们要存一个字符串进去(上方已经讲过了):

    - (void)saveString{
        //获取Document目录
        NSString *string = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
        //在沙盒文件下创建文件
        NSString *newPath = [string stringByAppendingString:@"/text.txt"];
        //要存入的内容
        NSString *name = @"UMR";
        //写入文件
        [name writeToFile:newPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
    }
    
    - (void)readingString{
        //获取路径
        NSString *str = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
        NSString *newPath = [str stringByAppendingString:@"/text.txt"];
        //读取数据
        NSString *string = [NSString stringWithContentsOfFile:newPath encoding:NSUTF8StringEncoding error:nil];
        NSLog(@"%@",string);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    我们看一下存入文件App后的效果:
    在这里插入图片描述请添加图片描述
    我们再来看一下程序的运行结果:
    请添加图片描述

    Preferences偏好设置(UserDefaults)

    很多iOS应用都支持偏好设置,提供了一套标准的解决方案来为应用加入偏好设置功能,比如保存用户名,字体大小,密码,是否自动登录等。

    特点:不会自动删除,iclude\itune同步,不适合存大数据。即时操作需要注意同步问题,不需要设置路径和文件名

    其本身的创建类似于单例模式 我们在后面用不同的属性名再次申请创建,会覆盖之前的数据

    我们使用UserDefaults存放一个账号密码试一下:

    - (void)registerUserData {
        // 获取偏好设置对象
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
         
        //存储数据
        [defaults setObject:@"carry" forKey:@"name"];
        [defaults setObject:@"123456" forKey:@"password"];
         
        // 同步调用,立刻写到文件中,不写这个方法会异步,有延迟
        [defaults synchronize];
    }
    
    - (void)judgeUserData {
        //需要验证账号密码的地方
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
        NSString *name = [defaults objectForKey:@"name"];
        NSString *password = [defaults objectForKey:@"password"];
        NSLog(@"%@ %@", name, password);
    }
    
    然后我们按照这个顺序调用:
    [self registerUserData];
    [self judgeUserData];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    我们来看一下打印结果:
    请添加图片描述

    NSKeyedArchiver归档

    如果对象是NSString、NSDictionary、NSArray、NSData、NSNumber等类型,可以直接用NSKeyedArchiver进行归档和恢复

    特点: 存入Document,不会自动删除,可存放大型数据,iclude/iTune同步,存放用户产生的数据,如游戏,操作记录等等。

    使用时重要的点就是:

    1. 遵循NSCoding协议
    2. 实现encodeWithCoderinitWithCoder协议方法,这两个方法是NSCoding协议所规定的

    详细使用案例可见上方方归档与反归档部分的例子讲解。

    通过SQlite或CoreData保存在对象数据集

    SQlite就是一个轻量级的数据库

    CoreData是苹果推出的一个数据存储框架,本质上是对SQLite的一个封装。CoreData提供了一种对象关系映射(ORM)的存储关系,类似于Javahibernate框架。CoreData可以将OC对象存储到数据库中,也可以将数据库中的数据转化为OC对象,在这个过程中不需要手动编写任何SQL语句,CoreData封装了数据库的操作过程,以及数据库中数据和OC对象的转换过程。 所以在使用CoreData的过程中,很多操作就像是对数据库进行操作一样,也有过滤条件、排序等操作

    SQLiteCoreData的区别

    • CoreData可以在一个对象更新时,其关联的对象也会随着更新,相当于你更新一张表时,其关联的其他表的也回随着更新
    • CoreData供更简单的性能管理机制,可以限制查询记录的总数,这个类会自动更新其缓存
    • 多表查询方面,CoreData没有SQL直观,没有类似外连接,左连接等操作

    总结一下iOS中数据持久化方案

    • NSUserDefault 简单数据快速读写
    • Property list (XML属性列表)文件存储
    • Archiver (归档)
    • SQLite 本地数据库
    • CoreData

    一些面试题目和iOS持久化相关的简单问题可见该博客:iOS面试:数据持久化(八)

  • 相关阅读:
    C++模板初阶
    数字图像处理(九)双边滤波
    JVM虚拟机(整体架构、类文件结构)我来了~~~
    JavaScript设计模式(三)——单例模式、装饰器模式、适配器模式
    保姆级cat系统搭建过程
    使用hutool工具发送带附件的邮件(特简单)
    Java JVM 运行机制及基本原理
    Day11.2:标签的使用
    modbusTCP【codesys】
    不知道照片如何拼图?这3个方法能帮上你
  • 原文地址:https://blog.csdn.net/m0_52192682/article/details/126027683