赞
踩
在iOS开发中,数据持久化是非常重要的,因为它允许应用程序在不同会话之间保存用户数据、设置、偏好等信息。
NSUserDefault
:简单数据快速读写。Property list
: 属性列表的文件存储Archiver
:归档。SQLite
:本地数据库。CoreData
:CoreData 是基于 sqlite 的封装。数据存储的区域在内存和磁盘
内存缓存
对于使用频率比较高的数据,从网络或磁盘加载数据到内存以后,使用后并不马上销毁,下次使用直接从内存加载。
例如iOS中图片的加载
磁盘缓存
将从网络加载的,用户操作产生的数据写入到磁盘,用户下次查看、继续操作时,直接从磁盘加载使用
例如搜索历史的缓存、用户输入内容草稿的缓存
出于安全的原因,iOS 应用在安装时,为每个 App 分配了独立的目录,App 只能对自己的目录进行操作,这个目录就被称为沙盒。
应用程序只能访问自身的沙盒文件,不能访问其他应用程序的沙盒文件,当应用程序需要向外部请求或接收数据时,都需要经过权限认证,否则,无法获取到数据。所有的非代码文件都要保存在此。
例如属性文件plist、文本文件、图像、图标、媒体资源等,其原理是通过重定向技术,把程序生成和修改的文件定向到自身文件夹中。
沙盒中主要包含4个目录: MyApp.app、Documents、Library、Tmp,目录结构如下:
MyApp.app:包含了所有的资源文件和和可执行文件,上架前经过数字签名,上架后不可修改。
Documents:文档目录,要保存程序生成的数据,会自动被分到iCloud中。保存应用运行时生成的需要持久化的数据,iTunes同步设备时会备份该目录。例如,游戏应用可将游戏存档保存在该目录。(注意点:不要保存从网络上下载的文件,否则会无法上架!)
Library:
用户偏好,使用 NSUserDefault 直接读写!
如果要想数据及时写入磁盘,还需要调用一个同步方法
保存临时文件,“后续需要使用”,例如:缓存图片,离线数据(地图数据
系统不会清理 cache 目录中的文件,就要求程序开发时,“必须提供 cache 目录的清理解决方案”
Caches:存放体积大又不需要备份的数据
Preference:保存应用的所有偏好设置,iCloud会备份设置信息
Tmp:
临时文件,系统会自动清理。重新启动就会清理。保存应用运行时所需的临时数据,使用完毕后再将相应的文件从该目录删除。应用没有运行时,系统也可能会清除该目录下的文件。iTunes同步设备时不会备份该目录。
存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能
保存临时文件,“后续不需要使用”
tmp 目录中的文件,系统会自动清理
重新启动手机,tmp 目录会被清空
系统磁盘空间不足时,系统也会自动清理
获取沙盒根目录:
NSString *home = NSHomeDirectory();
每次编译代码会生成新的沙盒路径,是在编译的时候,所以模拟机和真机每次的沙盒路径都是不一样的。
上面的代码得到的就是当前应用程序目录的路径,该目录下就是应用程序的沙盒,在该目录下有4个文件夹:Documents
、Library
、SystemData
、tmp
,当前应用程序只能访问该目录下的文件。
利用 NSSearchPathForDirectoriesInDomains 函数获取沙盒目录
//文件路径搜索
FOUNDATION_EXPORT NSArray<NSString *> *NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask, BOOL expandTilde);
该方法返回值为一个数组,在iphone中由于只有一个唯一路径,所以直接取数组第一个元素即可。
参数一 NSSearchPathDirectory directory:指定搜索的目录名称,比如这里用NSDocumentDirectory表明我们要搜索的是Documents目录。如果我们将其换成NSCachesDirectory就表示我们搜索的是Library/Caches目录。
参数二 NSSearchPathDomainMask domainMask:搜索主目录的位置,NSUserDomainMask表示搜索的范围限制于当前应用的沙盒目录。还可以写成NSLocalDomainMask(表示/Library)、NSNetworkDomainMask(表示/Network)等。
参数三 BOOL expandTilde:是否获取完整的路径,该值为YES即表示写成全写形式,为NO就表示直接写成“~”。
上述两个参数的枚举值:
typedef NS_OPTIONS(NSUInteger, NSSearchPathDomainMask) { NSUserDomainMask = 1, // 用户目录 - 基本上就用这个。 NSLocalDomainMask = 2, // 本地 NSNetworkDomainMask = 4, // 网络 NSSystemDomainMask = 8, // 系统 NSAllDomainsMask = 0x0ffff // 所有 }; //常用的NSSearchPathDirectory枚举值 typedef NS_ENUM(NSUInteger, NSSearchPathDirectory) { NSApplicationDirectory = 1, // supported applications (Applications) NSDemoApplicationDirectory, // unsupported applications, demonstration versions (Demos) NSAdminApplicationDirectory, // system and network administration applications (Administration) NSLibraryDirectory, // various documentation, support, and configuration files, resources (Library) NSUserDirectory, // user home directories (Users) NSDocumentationDirectory, // Library 下的(Documentation)模拟器上没有创建 NSDocumentDirectory, // documents (Documents) };
沙盒目录获取示例:
// 搜索 Document 目录
NSArray<NSString *> *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [documentPaths firstObject];
NSLog(@"Document Directory: %@", documentDirectory);
// 搜索 Document 目录 NO
NSArray<NSString *> *documentPathsNO = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, NO);
NSString *documentDirectoryNO = [documentPathsNO firstObject];
NSLog(@"Document Directory NO: %@", documentDirectoryNO);
NSCache是苹果提供的一套缓存机制,用法和NSMutableDictionary类似,在AFNetworking,SDWebImage,Kingfisher中都有用到。
当内存不足时NSCache会自动释放内存。 NSCache设置缓存对象数量和占用的内存大小,当缓存超出了设置会自动释放内存。
NSCache是Key-Value数据结构,其中key是强引用,不实现NSCoping协议,作为key的对象不会被拷贝。
countLimit
: 能够缓存对象的最大数量,默认值是0,没有限制。
totalCostLimit
: 设置缓存占用的内存大小 evictsObjectsWithDiscardedContent
: 是否回收废弃内容,默认YES
objectForKey
: 通过key获得缓存对象。
setObject: forKey
: 缓存对象。
setObject: forKey: cost
: 缓存对象,并指定key值对应的成本,用于计算缓存中所有对象的总成本。
removeObjectForKey
: 删除指定对象。
removeAllObjects
: 删除所有缓存对象。
willEvictObject
: 缓存对象即将被清理时调用,一般开发者用来调试,不能在此方法中修改缓存
在下列场景中会被调用:
当收到内存警告,而我们又调用removeAllObjects,则无法再继续往缓存中添加数据。
不提供缓存总的大小,想知道NSCache占用的内存大小,只有通过添加缓存的cost自己计算。
NSCache自动释放内存的算法是不确定的,有时是按照LRU(最近最久未使用)释放,有时随机释放。
NSCache中的数据在APP重启后会消失,因为NSCache只是将数据保存在内存 的。
NSCache和NSMutableDictionary的区别
NSCache是线程安全的,不需要加线程锁,而NSMutableDictionary线程不安全。
示例代码:
#import <UIKit/UIKit.h> @interface ViewController : UIViewController<NSCacheDelegate> @property (nonatomic, strong)NSCache* myCache; @end - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.view.backgroundColor = [UIColor whiteColor]; self.myCache = [[NSCache alloc]init]; self.myCache.delegate = self; for (int i = 0; i<10; i++) { [self.myCache setObject:[NSString stringWithFormat:@"%d", i] forKey:@(i) cost: 1]; } for (int i = 0; i<10; i++) { NSLog(@"NSCache取出---%@", [self.myCache objectForKey:@(i)]); } /// 清除缓存 [self.myCache removeAllObjects]; /// 设置缓存限制 self.myCache.totalCostLimit = 5; NSLog(@"设置缓存限制后================="); for (int i = 0; i<10; i++) { // 设置成本数为1 [self.myCache setObject:[NSString stringWithFormat:@"%d", i] forKey:@(i) cost: 1]; } for (int i = 0; i<10; i++) { NSLog(@"NSCache取出---%@", [self.myCache objectForKey:@(i)]); } /// 清除缓存 [self.myCache removeAllObjects]; NSLog(@"设置缓存限制后但未设置成本数cost================="); for (int i = 0; i<10; i++) { [self.myCache setObject:[NSString stringWithFormat:@"%d", i] forKey:@(i)]; } for (int i = 0; i<10; i++) { NSLog(@"NSCache取出---%@", [self.myCache objectForKey:@(i)]); } /// 清除缓存 [self.myCache removeAllObjects]; } // 即将回收对象的时候进行调用,实现代理方法之前要遵守NSCacheDelegate协议。 - (void)cache:(NSCache *)cache willEvictObject:(id)obj{ NSLog(@"NSCache回收---%@", obj); } @end
从打印结果可以看看出:
willEvictObject
的代理方法。属性列表是一种XML格式的文件,拓展名为plist如果对是NSString、NSDictionary、NSArray、NSData、NSNumber等类型,就可以使用writeToFile:atomically:方法直接将对象写到属性列表文件中。
属性列表-NSDictionary的存储和读取过程:
- (void)directorfile { // 获取 Document 目录 NSArray<NSString *> *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentDirectory = [documentPaths firstObject]; // 在 Document 目录下新建一个 test.plist 文件 NSString *documentfileName = [documentDirectory stringByAppendingPathComponent:@"data.plist"]; NSLog(@"%@", documentfileName); // // 存字典,将字典数据存到刚才的 test.plist 文件 NSDictionary *dict = @{@"name": @"clearlove", @"boom": @"4396"}; [dict writeToFile:documentfileName atomically:YES]; // 取字典 NSDictionary* msgDict = [NSDictionary dictionaryWithContentsOfFile:documentfileName]; NSLog(@"%@", msgDict); }
很多iOS应用都支持偏好设置,比如保存用户名、密码、字体大小等设置,iOS提供了一套标准的解决方案来为应用加入偏好设置功能。
每个应用都有个NSUserDefaults实例,通过它来存取偏好设置。
比如,保存用户名、字体大小、是否自动登录:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:@"张三" forKey:@"username"];
[defaults setFloat:18.0f forKey:@"text_size"];
[defaults setBool:YES forKey:@"auto_login"];
读取上次保存的设置
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *username = [defaults stringForKey:@"username"];
float textSize = [defaults floatForKey:@"text_size"];
BOOL autoLogin = [defaults boolForKey:@"auto_login"];
UserDefaults设置数据时,不是立即写入,而是根据时间戳定时地把缓存中的数据写入本地磁盘。所以调用了set方法之后数据有可能还没有写入磁盘应用程序就终止了。出现以上问题,可以通过调用synchornize方法
[defaults synchornize];
强制写入。
偏好设置存储的优点:
NSKeyedArchiver(归档):归档一般都是保存自定义对象的时候,使用归档。因为plist文件不能够保存自定义对象。如果一个字典中保存有自定义对象,如果把这个对象写入到文件当中,它是不会生成 plist文件的。如果对象是NSString、NSDictionary、NSArray、NSData、NSNumber等类型,可以直接用NSKeyedArchiver进行归档和恢复。
但是在我们使用归档之前,我们必须得遵守NSSecureCoding协议才行,老版本只需要遵循NSCoding实现其归档和解档的方法就行,但是iOS13更新之后就不行了,我们就必须的遵守NSSecureCoding协议,NSSecureCoding协议也遵循了原来NSCoding这个协议,不过我们还需要遵循它的一个supportsSecureCoding方法,这样我们才能归档成功。
因为NSSecureCoding协议也遵循了原来NSCoding这个协议,所以他也就有了- (void)encodeWithCoder:(NSCoder *)coder方法和- (id)initWithCoder:(NSCoder *)coder方法:
-(void)encodeWithCoder:(NSCoder *)coder
每次归档对象时,都会调用这个方法。一般在这个方法里面指定如何归档对象中的每个实例变量。可以使用encodeObject:forKey:
方法归档实例变量。
-(id)initWithCoder:(NSCoder *)coder方法
每次从文件中会恢复(解码)对象时,都会调用这个方法。一般在这个方法里面指定如何解码文件中的数据为对象的实例变量,可以使用decodeObject:forKey
方法解码实例变量。
NSSecureCoding
协议还有+ (BOOL)supportsSecureCoding
方法,只有当这个方法为YES的时候,才可以归档成功。
下面给出示例代码:
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface Person : NSObject<NSSecureCoding> @property (nonatomic, strong) NSString *name; @property (assign) int age; - (void)saveData; - (void)readData; @end #import "Person.h" @implementation Person - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:_name forKey:@"name"]; [coder encodeInt:_age forKey:@"age"]; } - (id)initWithCoder:(NSCoder *)coder { if (self = [super init]) { _name = [coder decodeObjectForKey:@"name"]; _age = [coder decodeIntForKey:@"age"]; } return self; } // 返回YES才能成功归档 + (BOOL)supportsSecureCoding { return YES; } - (void)saveData { Person *p = [[Person alloc] init]; p.name = @"clearlove"; p.age = 24; NSError *error; // 2.归档模型对象 // 3.获得 Documents 的全路径 NSString *docu = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; // 4.获得新文件的全路径,即新建一个 person.data 文件来存储我们要归档的数据 NSString *path = [docu stringByAppendingString:@"/person.plist"]; NSLog(@"%@", path); // 5.将对象封装为 Data 数据并归档 NSData *data = [NSKeyedArchiver archivedDataWithRootObject:p requiringSecureCoding:YES error:&error]; if (error) { NSLog(@"sodufosuf%@", error); } [data writeToFile:path atomically:YES]; } // 读档,将数据从文件中读出 - (void)readData { NSError *error; // 1.获得 Documents 的全路径 NSString *docu = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; // 2.获得文件的全路径,即获取我们要解档文件的路径 NSString *path = [docu stringByAppendingString:@"/person.plist"]; // 3.从 path 路径中获取 Data 数据 NSData *unData = [NSData dataWithContentsOfFile:path]; // 4.从文件中读取Person对象 Person *person = (Person *)[NSKeyedUnarchiver unarchivedObjectOfClass:[Person class] fromData:unData error:&error]; // 打印结果 NSLog(@"name: %@ age: %d",person.name, person.age); } @end
如果父类也遵守了NSCoding协议,请注意: 应该在encodeWithCoder:
方法中加上一句[super encodeWithCode:encode];
确保继承的实例变量也能被编码,即也能被归档。 应该在initWithCoder:
方法中加上一句self = [super initWithCoder:decoder];
确保继承的实例变量也能被解码,即也能被恢复。
有时候可能想将多个对象写入到同一个文件中,那么就要使用NSData来进行归档对象。 NSData可以为一些数据提供临时存储空间,以便随后写入文件,或者存放从磁盘读取的文件内容。可以使用[NSMutableData data]创建可变数据空间。
// 新建一块可变数据区
NSMutableData *data = [NSMutableData data];
// 将数据区连接到一个NSKeyedArchiver对象
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
// 开始存档对象,存档的数据都会存储到NSMutableData中
[archiver encodeObject:person1 forKey:@"person1"];
[archiver encodeObject:person2 forKey:@"person2"];
// 存档完毕(一定要调用这个方法)
[archiver finishEncoding];
// 将存档的数据写入文件
[data writeToFile:path atomically:YES];
SQLite
:是目前主流的嵌入式关系型数据库,其最主要的特点就是轻量级、跨平台,当前很多嵌入式操作系统都将其作为数据库首选。
CoreData
:CoreData是iOS5之后才出现的一个框架,本质上是对SQLite的一个封装,它提供了对象-关系映射(ORM)的功能,即能够将OC对象转化成数据,保存在SQLite数据库文件中,也能够将保存在数据库中的数据还原成OC对象,在这个过程中不需要手动编写任何SQL语句,CoreData封装了数据库的操作过程,以及数据库中数据和OC对象的转换过程。通过CoreData管理应用程序的数据模型,可以极大程度减少需要编写的代码数量。
NSManagedObject
只要定义一个类继承该类就会创建一张与之对应的表,也就是一个继承与该类的类就对应一张表,每一个通过继承该类创建出来的对象,都是该类对应的表中的一条数据。
NSManagedObjectContext
用于操作数据库,只要有类它就能对数据库的表进行增删改查
NSPersistentStoreCoordinator
决定存储的位置
FMDB
:
是一个处理数据存储的第三方框架,框架是对sqlite
的封装,整个框架非常轻量级但又不失灵活性,而且更加面向对象。
Core data 多线程不安全
Core data 本身并不是一个并发安全的架构,所以在多线程中实现 Core data 会有问题。
序列化:把对象转化为字节序列的过程
反序列化:把字节序列恢复成对象
作用:把对象写到文件或者数据库中,并且读取出来
iOS中怎么实现序列化?
在iOS实际开发中,我们也会使用到序列化,归档就是我们在iOS开发中使用序列化的场景
在iOS中一个自定义对象是无法直接存入到文件中的,必须先转化成二进制流才行。从对象到二进制数据的过程我们一般称为对象的序列化(Serialization),也称为归档(Archive)。同理,从二进制数据到对象的过程一般称为反序列化或者反归档(解档)。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。