Objective-C的数据持久化学习笔记

沙箱目录

iOS有一套完整的数据安全体系,iOS应用程序只能访问自己的目录,这个目录称为沙箱目录,而应用程序间禁止数据的共享。访问一些特定的应用时,如联系人应用,必须通过特定的API访问。

沙箱目录是一种数据安全策略,很多系统都采用沙箱设计,实现HTML5规范的一些浏览器也采用沙箱设计,沙箱目录设计的原理就是只允许自己的应用访问目录,而不允许其它的应用访问。沙箱目录有3个子目录,分别为Documents,Library,tmp。

FOUNDATION_EXPORT NSString *NSHomeDirectory(void);

函数描述 :获取沙箱目录的主目录。

\color{red}{获取沙箱目录的主目录 :}

NSString *path = NSHomeDirectory();
NSLog(@"path : %@", path);

Documents目录:该目录用于存储非常大的文件或需要非常频繁更新的数据,能够进行iTunes或iCloud备份。

\color{red}{例如获取Documents目录位置:}

NSArray *documentArray = NSSearchPathForDirectoriesInDomains(NSDocumentationDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [documentArray lastObject];
NSLog(@"%@",documentsPath);

Library目录:在Library目录目录下面有Preferences和Caches目录,其中Preferences用于存放应用程序的设置数据,Caches可以用来存放应用程序的数据,用来存储缓存文件。

\color{red}{例如获取Library目录位置:}

NSArray *pathArray = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSLog(@"path : %@", pathArray.lastObject);

tem目录:临时文件目录,用户可以访问它,它不能够进行iTunes或iCloud备份。

\color{red}{例如获取tem目录位置:}

NSString *temPath = NSTemporaryDirectory();

NSUserDefaults -- 用户默认数据库

NSUserDefaults是一个分层的持久化进程间(可选的分布式)键值存储,为存储用户设置而优化。定义的是一个用户默认数据库的接口。

NSUserDefaults是一个单例,每个应用都有一个(也只有一个)NSUserDefaults对象。向NSUserDefaults类发送standardUserDefaults消息可以得到该对象。

使用NSUserDefaults对象读取信息时,NSUserDefaults会缓存信息,设置信息时,它将在当前进程中同步更改,并在其他进程异步更改为持久存储。存储在NSUserDefaults中的本地数据在应用程序重新启动时保持不变。

对于存储在其中的任何键,都可以使用键值观察来观察NSUserDefaults,当NSUserDefaults 实例所存储的数据变更时,系统会发送 NSUserDefaultsDidChangeNotification 的通知,通知会返回当前更改的 NSUserDefaults 实例对象回来。

NSUserDefaults适合存储轻量级的经常查询并偶尔修改的本地数据,使用的Key必须是NSString对象,一个键对应一项设置(preference)。存取的对象必须是对象或者基本类型的数值。如果是对象,则必须是可序列化的(serializable)对象。支持的数据类型有:NSNumber,NSString,NSDate,NSArray,NSDictionary,BOOL。注意:NSUserDefaults 存储的对象全是不可变的

NSUserDefaults存储文件的沙盒路径为: Library/Preferences,存储文件的格式为: .plist格式。例如 :

截屏2020-06-17下午11.24.55.png

UserDefaults数据库中其实是由多个层级的域组成的,当读取一个键值的数据时,NSUserDefaults从上到下透过域的层级寻找正确的值,不同的域有不同的功能,有些域是可持久的,有些域则不行。默认包含五个Domain,如下:

  • 1、参数域( argument domain)有最高优先权。
  • 2、应用域(application domain)是最重要的域,它存储着你app通过NSUserDefaults set...forKey添加的设置。
  • 3、全局域(global domain)则存储着系统的设置。
  • 4、语言域(language-specific domains)则包括地区、日期等。
  • 5、注册域(registration domain)仅有较低的优先权,只有在应用域没有找到值时才从注册域去寻找。

例如:当某个界面是第一次运行时,这时的key值(键)所对应的值还不存在,所以NSUserDefaults对象会使用默认值为0。但是对于某些设置,可能需要临时的、非0的默认值(如"出厂设置"),否则应用将无法正确的运行。一般默认值都是存放于注册域中。默认情况下,应用域是空的,没有键也没有值。当用户第一次修改某个设置时,相应的值会通过指定的键加入应用域。当通过NSUserDefaults对象获取某个设置的值时,该对象先会在应用域查找,如果没有,NSUserDefaults则会在注册域中查找并返回默认值。查询顺序:参数域 -> 应用域 -> 全局域 -> 语言域 ->注册域。

NSUserDefaults的优缺点

优点:

  • 不需要关心文件名。
  • 快速进行键值对存储。
  • 直接存储基本数据类型。

缺点:

  • 不能存储自定义数据。
  • 取出的数据都是不可变的。
NSuserDefault提供的常用属性与函数
@property (class, readonly, strong) NSUserDefaults *standardUserDefaults;

属性描述 :返回NSUserDefaults的全局实例

- (void)setObject:(nullable id)value forKey:(NSString *)defaultName;

函数描述立即在搜索列表项中存储所提供Key的值(如果传递的值为nil,则删除该值),然后在其他进程可以使用该值的情况下异步持久存储该值。

参数 :

value : 要存储在默认数据库中的对象。

defaultName : 与值关联的键。

- (BOOL)synchronize;

函数描述等待对默认数据库的任何挂起的异步更新并返回(等待数据持久持久存储),此函数不推荐使用。

返回值 : 如果数据成功保存到磁盘,则为YES,否则为NO。

\color{red}{例如存储数据:}

[[NSUserDefaults standardUserDefaults] setObject:@"好高兴啊" forKey:@"又吃成长快乐了"];
[[NSUserDefaults standardUserDefaults] synchronize];
- (nullable id)objectForKey:(NSString *)defaultName;

函数描述返回与指定键关联的对象。此方法按域的列出顺序搜索包含在搜索列表中的域,并返回与指定默认值的第一个匹配项关联的对象。

参数 :

defaultName : 当前用户默认数据库中的Key。

返回值 : 与指定键关联的对象,如果找不到键,则为nil。

\color{red}{例如取出数据:}

NSString *Study = [NSUserDefaults standardUserDefaults] objectForKey:@"又吃成长快乐了"];
- (void)removeObjectForKey:(NSString *)defaultName;

函数描述删除指定默认Key的值。如果在搜索列表中位于标准应用程序域之前的域中存在相同的Key,则删除默认值不会影响objectForKey:方法返回的值。

参数 :

defaultName : 要移除其值的Key。

- (nullable NSString *)stringForKey:(NSString *)defaultName;

函数描述返回与指定Key关联的字符串(NSString)。返回的字符串是不可变的,即使最初设置的值是可变的字符串。

参数 :

defaultName : 当前用户默认数据库中的Key。

返回值 : 对于字符串值,与指定Key关联的字符串;对于数字值,为数字的字符串值。如果默认值不存在或不是字符串或数值,则返回nil。

- (nullable NSArray *)arrayForKey:(NSString *)defaultName;

函数描述返回与指定Key关联的数组(NSArray)。返回的数组及其内容是不可变的,即使最初设置的值是可变的。

参数 :

defaultName : 当前用户默认数据库中的Key。

返回值 : 与指定Key关联的数组,如果Key不存在或其值不是数组,则为nil。

- (nullable NSDictionary<NSString *, id> *)dictionaryForKey:(NSString *)defaultName;

函数描述返回与指定Key关联的字典(NSDictionary)对象。返回的字典及其内容是不可变的,即使最初设置的值是可变的。

参数 :

defaultName : 当前用户默认数据库中的Key。

返回值 : 与指定键关联的NSDictionary对象,如果键不存在或其值不是NSDictionary,则为nil。

- (nullable NSData *)dataForKey:(NSString *)defaultName;

函数描述返回与指定Key关联的数据(NSData)对象。返回的数据对象是不可变的,即使最初设置的值是可变的数据对象。

参数 :

defaultName : 当前用户默认数据库中的Key。

返回值 : 与指定键关联的NSData对象,如果键不存在或其值不是NSData对象,则为nil。

- (nullable NSArray<NSString *> *)stringArrayForKey:(NSString *)defaultName;

函数描述返回与指定Key关联的字符串数组(NSArray<NSString *>)。返回的数组及其内容是不可变的,即使最初设置的值是可变的。

参数 :

defaultName : 当前用户默认数据库中的Key。

返回值 :字符串对象数组,如果指定的默认值不存在、默认值不包含数组或数组不包含字符串,则为nil。

- (void)setInteger:(NSInteger)value forKey:(NSString *)defaultName;

函数描述将指定的默认Key的值设置为指定的整数值(NSInteger)。这是一个调用setObject:forKey:的方便方法。

参数 :

value : 要存储在默认数据库中的整数值。

defaultName : 与值关联的Key。

- (NSInteger)integerForKey:(NSString *)defaultName;

函数描述返回与指定Key关联的整数值(NSInteger)。此方法自动将某些值强制转换为等效的整数值(如果可以确定转换的话)。布尔值YES变为1,NO变为0;浮点数变为小于该数的最大整数(例如,2.67变为2);表示整数的字符串变为等效整数(例如“123”变为123)。

参数 :

defaultName : 当前用户默认数据库中的Key。

返回值 : 与指定Key关联的整数值。如果指定的Key不存在,则此方法返回0。

- (void)setFloat:(float)value forKey:(NSString *)defaultName;

函数描述将指定默认Key的值设置为指定的浮点值(float)。这是一个调用setObject:forKey:的方便方法。

参数 :

value : 要存储在默认数据库中的浮点值。

defaultName : 与值关联的Key。

- (float)floatForKey:(NSString *)defaultName;

函数描述返回与指定Key关联的浮点值(float)。此方法自动将某些值强制转换为等效的浮点值(如果可以确定转换的话)。布尔值YES变为1.0,NO变为0.0;整数变为等效的浮点(例如,2变为2.0);表示浮点数的字符串变为等效浮点数(例如,“123.4”变为123.4)。

参数 :

defaultName : 当前用户默认数据库中的Key。

返回值 : 与指定Key关联的浮点值。如果Key不存在,则此方法返回0。

- (void)setDouble:(double)value forKey:(NSString *)defaultName;

函数描述将指定的默认Key的值设置为双精度浮点值(double)。这是一个调用setObject:forKey:的方便方法。

参数 :

value : 要存储在默认数据库中的双精度浮点值。

defaultName :与值关联的Key。

- (double)doubleForKey:(NSString *)defaultName;

函数描述返回与指定键关联的双精度浮点值(double)。此方法自动将某些值强制转换为等效的双精度值(如果可以确定)。布尔值YES变为1.000000,NO变为0.000000;整数变为等效的双精度数(例如,2变为2.000000);表示浮点数的字符串变为等效的双精度(例如,“123.40000000000001”变为123.40000000000001)。

参数 :

defaultName : 当前用户默认数据库中的Key。

返回值 : 与指定Key关联的双精度浮点值。如果Key不存在,则此方法返回0。

- (void)setBool:(BOOL)value forKey:(NSString *)defaultName;

函数描述将指定的默认Key的值设置为指定的布尔值(BOOL)。这是一个调用setObject:forKey:的方便方法。

参数 :

value : 要存储在默认数据库中的布尔值。

defaultName : 与值关联的Key。

- (BOOL)boolForKey:(NSString *)defaultName;

函数描述返回与指定Key关联的布尔值(BOOL)。此方法自动将某些true值(如字符串“true”、“YES”和“1”、数字1和1.0)强制为布尔值YES。对于某些false值也是如此,例如字符串“false”、“NO”和“0”,以及数字0和0.0,它们自动强制为布尔值NO。

参数 :

defaultName : 当前用户默认数据库中的Key。

返回值 : 与指定Key关联的布尔值。如果指定的Key不存在,则此方法返回“否”。

- (void)setURL:(nullable NSURL *)url forKey:(NSString *)defaultName API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));

函数描述将指定的默认Key的值设置为指定的URL。如果url是文件url,则此方法采用绝对url,确定其路径是否可以相对于用户的主目录创建,如果可以,则使用stringByAbbreviatingWithTildeInPath方法对其进行缩写。如果url不是文件url,则通过调用archivedDataWithRootObject:方法并将url作为根对象传递来创建数据对象。

参数 :

url : 要存储在默认数据库中的URL。

defaultName : 与值关联的Key。

- (void)registerDefaults:(NSDictionary<NSString *, id> *)registrationDictionary;

函数描述将指定字典中的内容添加到注册域。如果没有注册域,则使用指定的词典创建一个注册域,并将NSRegistrationDomain添加到搜索列表的末尾。注册域的内容不会写入磁盘;每次启动应用程序时都需要调用此方法。可以将plist文件放在应用程序的Resources目录中,并使用从该文件读取的内容调用registerDefaults:方法。

参数 :

dictionary : 包含要注册的键和值的字典。

- (void)addSuiteNamed:(NSString *)suiteName;

函数描述将指定域名插入到调用方的搜索列表中。suiteName表示的域类似于bundle标识符字符串,但不一定绑定到特定的应用程序或bundle。一个套件可以用来保存在多个应用程序之间共享的首选项。

suiteName域的附加搜索列表在当前域之后但在全局默认值之前搜索(即,当添加套件时,首选项子系统首先搜索应用程序的用户首选项,然后再次搜索,就好像它是一个bundle标识符等于suiteName的应用程序一样,最后搜索全局首选项)。不支持传递NSGlobalDomain或当前应用程序的bundle标识符。

参数 :

suiteName : 要插入的域名。

- (void)removeSuiteNamed:(NSString *)suiteName;

函数描述 : 从调用方的搜索列表中删除指定的域名。

参数 :

suiteName : 要删除的域名。

- (NSDictionary<NSString *, id> *)dictionaryRepresentation;

函数描述 : 返回一个字典,其中包含搜索列表中域中所有键值对的并集。与objectForKey:一样,搜索列表中较早的域中的键值对具有优先权。合并的结果不会保留每个条目来自哪个域的信息。

返回值 : 包含键值的字典。这些键是默认值的名称,每个键对应的值是一个属性列表对象(NSData、NSString、NSNumber、NSDate、NSArray或NSDictionary)。

@property (readonly, copy) NSArray<NSString *> *volatileDomainNames;

属性描述 : 当前易失性的域名。域名用字符串表示。通过将返回的域名传递给volatileDomainForName:方法,可以获取每个域的内容。

- (NSDictionary<NSString *, id> *)volatileDomainForName:(NSString *)domainName;

函数描述 : 返回指定易失性域的字典。

参数 :

domainName : 需要其Key和值的域。

返回值 : 属于域的Key和值的字典。字典中的键是默认值的名称,每个Key对应的值是一个属性列表对象(NSData、NSString、NSNumber、NSDate、NSArray或NSDictionary)。

- (void)setVolatileDomain:(NSDictionary<NSString *, id> *)domain forName:(NSString *)domainName;

函数描述 : 为指定的易失性域设置字典。如果已存在具有指定名称的易失性域,则此方法引发NSInvalidArgumentException。

参数 :

domain : 要分配给域的Key和值的字典。

domainName : 要设置其字典的域。

- (void)removeVolatileDomainForName:(NSString *)domainName;

函数描述 : 从用户的默认值中删除指定的易失性域。

参数 :

domainName : 要删除的易失性域。

- (NSArray *)persistentDomainNames API_DEPRECATED("Not recommended", macos(10.0,10.9), ios(2.0,7.0), watchos(2.0,2.0), tvos(9.0,9.0));

函数描述 : 返回当前持久域名的数组(不推荐使用)。通过将返回的域名传递给persistentDomainForName:方法,可以获取每个域的Key和值。

返回值 : 包含域名的NSString对象数组。

- (nullable NSDictionary<NSString *, id> *)persistentDomainForName:(NSString *)domainName;

函数描述 : 返回指定域的默认值的字典表示形式。调用此方法相当于使用initWithSuiteName初始化用户默认值对象:传递domainName并对其调用dictionaryRepresentation方法。

参数 :

domainName : 要表示的域的名称。

返回值 : 包含每个默认名称对应默认值的及其Key的字典。

- (void)setPersistentDomain:(NSDictionary<NSString *, id> *)domain forName:(NSString *)domainName;

函数描述 : 为指定的持久域设置字典。调用此方法相当于使用initWithSuiteName初始化用户默认值对象:传递domainName,并对域中每个键值对调用setObject:forKey:的方法。

参数 :

domain : 要分配给域的Key和值的字典。

domainName : 要设置其内容的域的名称。

- (void)removePersistentDomainForName:(NSString *)domainName;

函数描述 : 从用户的默认值中删除指定持久域的内容。调用此方法相当于使用initWithSuiteName:传递domainName初始化用户默认对象,并对其每个键调用removeObjectForKey:方法。更改永久性域时,将发布NSUserDefaultsDidChangeNotification通知。

参数 :

domainName : 要删除其内容的域的名称。

- (BOOL)objectIsForcedForKey:(NSString *)key;

函数描述 : 返回一个布尔值,指示指定的Key是否由管理员管理。此方法假定Key是与当前用户和应用程序关联的首选项。对于托管Key,应用程序应禁用允许用户修改Key的值的任何用户界面。

参数 :

key : 要检查其状态的Key。

返回值 :如果指定密钥的值由管理员管理,则为“YES”,否则为“NO”。

- (BOOL)objectIsForcedForKey:(NSString *)key inDomain:(NSString *)domain;

函数描述 : 返回一个布尔值,指示指定域中的Key是否由管理员管理。此方法假定Key是与当前用户关联的首选项。对于托管Key,应用程序应禁用允许用户修改密Key的值的任何用户界面。

参数 :

key : 要检查其状态的Key。

domain : Key的域。

返回值 : 如果Key由指定域中的管理员管理,则为“YES”,否则为“NO”。

NSUserDefaults的初始化函数
- (nullable instancetype)initWithSuiteName:(nullable NSString *)suitename API_AVAILABLE(macos(10.9), ios(7.0), watchos(2.0), tvos(9.0)) NS_DESIGNATED_INITIALIZER;

函数描述使用指定域标识符的默认值创建初始化的NSUserDefaults对象。在开发应用程序套件时,可以使用此方法在应用程序之间共享首选项或其他数据,或者在开发应用程序扩展时,在扩展程序与其包含的应用程序之间共享首选项或其他数据。参数和注册域在NSUserDefaults的所有实例之间共享。

参数 :

suitename : 搜索列表的域标识符。如果将nil传递给这个参数,系统将使用standardUserDefaults类方法使用的默认搜索列表。因为套件管理指定应用程序组的默认值,所以套件名称必须与应用程序的主bundle identifier不同。NSGlobalDomain也是一个无效的套件名称,因为它不可由应用程序写入。

返回值 : 使用指定域标识符的默认值创建初始化的NSUserDefaults对象。

suiteName参数匹配相应CFPreferences api的域参数(在Foundation和Core Foundation常量之间转换时除外)。例如 :

id userDefaultsValue = [[[NSUserDefaults alloc] initWithSuiteName:@"someDomain"] objectForKey:@"someKey"];
id preferencesValue = CFPreferencesCopyAppValue(@"someKey", @"someDomain");
//userDefaultsValue和preferencesValue相等
- (nullable id)initWithUser:(NSString *)username API_DEPRECATED("Use -init instead", macos(10.0,10.9), ios(2.0,7.0), watchos(2.0,2.0), tvos(9.0,9.0));

函数描述创建用指定用户帐户的默认值初始化的NSUserDefaults对象。此方法不会在搜索列表中放入任何内容。只有在分配了自己的NSUserDefaults实例而不是使用共享实例时才调用它。通常不使用此方法初始化NSUserDefaults的实例。超级用户使用的应用程序可以使用此方法更新许多用户的默认数据库。启动应用程序的用户必须对新用户的默认数据库具有适当的访问权限(读、写或同时访问),否则此方法将返回nil。

参数 :

username : 用户帐户的名称。

返回值 : 已初始化的NSUserDefaults对象,其参数和注册域已设置。如果当前用户无权访问指定的用户帐户,则此方法返回nil。

UserDefaults域的常量
FOUNDATION_EXPORT NSString * const NSGlobalDomain;

常量描述 : 由所有应用程序所看到的默认值组成的域(全局域)。

FOUNDATION_EXPORT NSString * const NSArgumentDomain;

常量描述 : 从应用程序的参数解析默认值组成的域(参数域)。

FOUNDATION_EXPORT NSString * const NSRegistrationDomain;

常量描述 : 由一组临时缺省值组成的域,其值可以由应用程序设置,以确保搜索始终是成功的(注册域)。

NSUserDefaults的通知
FOUNDATION_EXPORT NSNotificationName const NSUserDefaultsSizeLimitExceededNotification API_AVAILABLE(ios(9.3), watchos(2.0), tvos(9.0)) API_UNAVAILABLE(macOS);

通知描述 : 当用户默认数据中存储更多数据时发送。

FOUNDATION_EXPORT NSNotificationName const NSUbiquitousUserDefaultsNoCloudAccountNotification API_AVAILABLE(ios(9.3), watchos(2.0), tvos(9.0)) API_UNAVAILABLE(macOS);

通知描述 : 当设置了云默认时,但没有登录iCloud用户发送。

FOUNDATION_EXPORT NSNotificationName const NSUbiquitousUserDefaultsDidChangeAccountsNotification API_AVAILABLE(ios(9.3), watchos(2.0), tvos(9.0)) API_UNAVAILABLE(macOS);

通知描述 : 当用户更改主iCloud帐户时发送。

FOUNDATION_EXPORT NSNotificationName const NSUbiquitousUserDefaultsCompletedInitialSyncNotification API_AVAILABLE(ios(9.3), watchos(2.0), tvos(9.0)) API_UNAVAILABLE(macOS);

函数描述 : 发布时,默认完成下载数据,无论是第一次设备连接到一个iCloud帐户,或当用户切换他们的主要iCloud帐户发送。

FOUNDATION_EXPORT NSNotificationName const NSUserDefaultsDidChangeNotification;

函数描述 : 在当前进程中更改用户默认值时发送

对象归档

对象归档是一种序列化方式,为了便于数据传输,先将归档对象序列化一个文件,然后再通过反归档将数据恢复到对象中。归档技术可以实现数据的持久化,不过在大量数据和频繁读写的情况下,它就不太合适了。

对一个对象进行完整归档需要满足的条件为该对象的类必须实现NSCoding协议,而且每个成员变量都应该是基本数据类型或都是实现NSCoding协议的某个类的实例

归档类NSKeyedArchiver和反归档类NSKeyedUnArchiver总是与NSData关联在一起。NSData封装了字节数据的缓存类,提供了读取数据文件的方法,具体如下:

+ (nullable instancetype)dataWithContentsOfFile:(NSString *)path;

函数描述 :静态工厂方法,通过在给定路径读取文件中的每个字节来创建数据(NSData)对象。如果无法创建数据对象,则此方法返回nil。这个方法等价于调用dataWithContentsOfFile:options:error:但不传递任何选项。如果需要知道失败的原因,使可以用dataWithContentsOfFile:options:error:。

参数 :

path : 要从中读取数据的文件的绝对路径。

返回值 :通过在给定路径读取文件中的每个字节来创建的数据(NSData)对象。

+ (nullable instancetype)dataWithContentsOfFile:(NSString *)path options:(NSDataReadingOptions)readOptionsMask error:(NSError **)errorPtr;

函数描述 :静态工厂方法,通过在给定路径读取文件中的每个字节来创建数据(NSData)对象。如果无法创建数据对象,则此方法返回nil,此时errorPtr将包含一个指示问题的NSError。

参数 :

path : 要从中读取数据的文件的绝对路径。

readOptionsMask :指定读取数据选项的常量掩码。

errorPtr :如果发生错误,返回时包含描述问题的错误对象。

返回值 :通过在给定路径读取文件中的每个字节来创建的数据(NSData)对象。

- (nullable instancetype)initWithContentsOfFile:(NSString *)path;

函数描述:实例构造函数,使用给定路径上的文件内容初始化数据(NSData)对象。这个方法等价调用initWithContentsOfFile:options:error:函数,但不传入任何选项。

参数 :

path :要从中读取数据的文件的绝对路径。

返回值 :通过从path指定的文件中读入数据来初始化的数据(NSData)对象。

- (nullable instancetype)initWithContentsOfFile:(NSString *)path options:(NSDataReadingOptions)readOptionsMask error:(NSError **)errorPtr;

函数描述:实例构造函数,使用给定路径上的文件内容初始化数据(NSData)对象

参数 :

path :要从中读取数据的文件的绝对路径。

readOptionsMask :指定读取数据选项的常量掩码。

errorPtr :如果发生错误,返回时包含描述问题的NSError对象。

返回值 :通过从path指定的文件中读入数据来初始化的数据(NSData)对象。

- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile;

函数描述将数据(NSData)对象的字节写入给定路径指定的文件。当写入公共可访问的文件时,此方法可能不合适。要安全地将数据写入公共位置,请使用NSFileHandle代替。

参数 :

path :调用方字节要写入的位置。如果path包含波浪号(~)字符,在调用此方法之前,必须使用stringByExpandingTildeInPath展开它。

useAuxiliaryFile :是否写入辅助文件,false时数据直接写入目标文件路径,true数据写入辅助文件,写入成功后将辅助文件路径改为目标文件路径。当目标文件已经存在时,atomically设置为true,可以防止系统崩溃导致旧的文件破坏。

返回值 :写入操作成功则为“YES”,否则为“NO”。

- (BOOL)writeToFile:(NSString *)path options:(NSDataWritingOptions)writeOptionsMask error:(NSError **)errorPtr;

函数描述将数据(NSData)对象的字节写入给定路径指定的文件。当写入公共可访问的文件时,此方法可能不合适。要安全地将数据写入公共位置,请使用NSFileHandle代替。

参数:

path :调用方字节要写入的位置。如果path包含波浪号(~)字符,在调用此方法之前,必须使用stringByExpandingTildeInPath展开它。

writeOptionsMask :指定写入数据选项的常量掩码。

errorPtr :如果写入数据时出现错误,则返回时包含一个描述问题的错误(NSError)对象。

返回值 :写入操作成功则为“YES”,否则为“NO”。

归档过程是使用NSKeyedArchiver对象归档数据,具体过程为先将归档数据写入NSData对象,然后将NAData对象写入归档文件。反归档过程是从归档文件中读取数据到NSData对象,再利用NSKeyedUnArchiver对象从NSData对象中反归档出数据

\color{red}{例如归档一个模型 :}

/*
归档与反归档使用的模型
*/
@interface TestArchiverModel : NSObject<NSCoding, NSSecureCoding>

@property (nonatomic, copy) NSNumber *age;
@property (nonatomic, copy) NSString *name;

@end

@implementation TestArchiverModel

/// 一个布尔值,指示类是否支持安全编码
+ (BOOL)supportsSecureCoding {
    return YES;
}

/// 使用给定的存档对接收器进行编码(归档时调用)。
/// - Parameter coder: 存档对象
- (void)encodeWithCoder:(nonnull NSCoder *)coder {
    [coder encodeObject:self.age ?: NSNull.null forKey:@"age"];
    [coder encodeObject:self.name ?: NSNull.null forKey:@"name"];
}

/// 返回从给定的unarchiver(反归档)中的数据初始化的对象。
/// - Parameter coder: unarchiver(反归档)对象。
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder {
    self = [super init];
    if (self) {
        id age = [coder decodeObjectForKey:@"age"];
        self.age = [age isKindOfClass:NSNumber.class] ? age : nil;
        
        id name = [coder decodeObjectForKey:@"name"];
        self.name = [name isKindOfClass:NSString.class] ? name : nil;
    }
    return self;
}

------------------------------- 分割线 -------------------------------

/// 懒加载归档用模型
- (TestArchiverModel *)user {
    if (_user == nil) {
        _user = [[TestArchiverModel alloc]init];
        _user.age = [NSNumber numberWithInt:20];
        _user.name = @"小林";
    }
    return _user;
}

/// 归档模型
- (BOOL)archiverUser {
    if (self.user == nil) { return NO; }
    //获取用户的主目录,并在主目录中拼接@"User",创建一个新目录
    NSString *userInfoDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"User"];
    //如果创建的新目录不存在
    BOOL isDirectory;
    if ([NSFileManager.defaultManager fileExistsAtPath:userInfoDirectoryPath isDirectory:&isDirectory] == NO) {
        //在指定路径上创建具有给定属性的目录
        [NSFileManager.defaultManager createDirectoryAtPath:userInfoDirectoryPath withIntermediateDirectories:YES attributes:nil error:nil];
    }
    //在创建的新目录中再次拼接@"user_info.test"
    NSString *userInfoFilePath = [userInfoDirectoryPath stringByAppendingPathComponent:@"user_info.test"];
    //指定路径的文件或目录如果存在
    if ([NSFileManager.defaultManager fileExistsAtPath:userInfoFilePath]) {
        //删除指定路径处的文件或目录
        [NSFileManager.defaultManager removeItemAtPath:userInfoFilePath error:nil];
    }
    //将对象转为编码数据
    NSError *error;
    NSData *userInfo = [NSKeyedArchiver archivedDataWithRootObject:self.user requiringSecureCoding:YES error:&error];
    //将数据对象的字节写入给定路径指定的文件
    [userInfo writeToFile:userInfoFilePath atomically:YES];
    //返回指定路径的文件是否存在
    return [NSFileManager.defaultManager fileExistsAtPath:userInfoFilePath];
}

调用归档函数后,会在指定的目录将模型写入文件:

截屏2023-01-30 15.37.29.png

\color{red}{例如反归档一个模型 :}

/// 反归档模型
- (TestArchiverModel *)unarchiverUserInfo {
    //获取要反归档的文件目录
    NSString *userFilePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"User"] stringByAppendingPathComponent:@"user_info.test"];
    //如果文件目录存在,进行反归档
    if ([NSFileManager.defaultManager fileExistsAtPath:userFilePath]) {
        NSData *userInfoData = [NSData dataWithContentsOfFile:userFilePath];
        TestArchiverModel *user = [NSKeyedUnarchiver unarchivedObjectOfClass:TestArchiverModel.class fromData:userInfoData error:nil];
        return user;
    }
    return nil;
}

调用反归档函数后,会在指定的目录的文件还原为模型:

截屏2023-01-30 15.41.12.png

SQLite数据库

2000年,D.里查德.希普开发并发布了嵌入式系统使用的关系数据库SQlite,目前的主流版本是SQLite3。SQLite是开源的,它采用C语言编写,具有可移植性强,可靠性高,小而易用的特点。SQLite运行时与使用它的应用程序之间共用相同的进程空间,而不是单独的两个进程。SQLite提供了对SQL-92标准的支持,支持多表、索引、事务、视图和触发。SQLite是无数据类型的数据库,字段不用指定类型。

虽然SQLite可以忽略数据类型,但从编程规范上讲,我们还是应该在Create Table语句中指定数据类型。因为数据类型可以表明这个字段的含义,便于阅读和理解代码。

SQlite支持的常见数据类型有:

  • INTEGER —— 有符号的整数类型。
  • REAL —— 浮点类型。
  • TEXT —— 字符串类型,采用UTF-8或UTF-16字符编码。
  • BLOB —— 二进制大对象类型,能够存放任何二进制数据。

SQLite中没有BOOLEAN类型,可以采用整数0和1代替。SQLite中也没有时间和日期类型,它们存储在TEXT、REAL和INTEGER类型中。为了兼容SQL-92标准中的其他数据类型,我们可以将它们转换成上述几种类型:

  • 将VARCHAR、CHAR和CLOB转换为TEXT类型。
  • 将FLOAT、DOUBLE转换为REAL类型。
  • 将NUMERIC转换为INTEGER或REAL类型。

Core Data

Core Data是苹果为OX S和IOS系统应用开发提供的数据持久化技术,基于高级数据持久化API,其底层最终是SQLite数据库、二进制文件和内存数据保存,使开发人员不用再关心数据的存储细节问题,不用再使用SQL语句,不用面对SQLite的C语言函数。

Core Data是一种ORM(对象关系映射)技术,ORM是关系数据和对象模型类之间的一个纽带。无论哪一种模型,都是为了描述和构建应用系统。在应用系统中,一个非常基础的概念是“实体”。“实体”是应用系统中的“人”、“事”和“物”,它们能够在关系模型和对象模型中以不同的形态存在。实体在关系模型中代表表的一条数据,该表描述了实体的结构有哪些属性和关系。实体在对象模型中代表类的一个对象,类描述了实体的结构,实体是类的对象。因此表是与类对应的概念,记录是与对象对应的概念。如图 :

1433205-20181011163756707-2119133208.png

关系模型和对象模型是有区别的,对象模型更加先进,能够描述继承、实现、关联、聚合和组成等复杂关系,而关系模型只能描述一对一、一对多和多对多的关系。这两种模型之间的不和谐问题称为“阻抗不匹配”问题,而ORM可以解决这一问题。

使用Core Data
  • 1.建模

在测试的项目中创建一个文件夹,用于存放Core Data生成的相关文件 :

截屏2022-12-12 22.20.21.png

在新创建的文件夹中创建Data Model,New File -> Data Model

截屏2022-12-12 22.20.35.png
截屏2022-12-02 09.02.21.png

创建成功后会得到一个XXX.xcdatamodeld文件夹,图标类似数据库 :

截屏2022-12-13 11.15.28.png

之后我创建一个UserEntity实体,并且添加了两个属性,Core Data支持的属性类型如下:

截屏2022-12-13 13.48.32.png
截屏2022-12-13 13.42.05.png
  • 2.实体

建模完成后,选中UserEntity -> 点击Editor -> 选择Create NSManagedObject Subclass即可创建实体类:

截屏2022-12-14 14.34.17.png
截屏2022-12-14 14.37.31.png
截屏2022-12-14 14.47.16.png

创建实体类后,创建的文件位置并不再选中的文件夹中,并且运行可能报错Multiple commands produce(多个命令生成)

截屏2022-12-14 14.52.28.png

将创建的文件移到想要放置的文件夹中,然后在Build Phases -> Compile Source中删除创建的文件,再次编辑即可通过:

截屏2022-12-14 18.36.17.png

UserEntity+CoreDataClass.h文件

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

NS_ASSUME_NONNULL_BEGIN

@interface UserEntity : NSManagedObject

@end

NS_ASSUME_NONNULL_END

#import "UserEntity+CoreDataProperties.h"

UserEntity+CoreDataClass.m文件

#import "UserEntity+CoreDataClass.h"

@implementation UserEntity

@end

UserEntity+CoreDataProperties.h文件

#import "UserEntity+CoreDataClass.h"

NS_ASSUME_NONNULL_BEGIN

@interface UserEntity (CoreDataProperties)

/// 返回搜索条件描述对象
+ (NSFetchRequest<UserEntity *> *)fetchRequest NS_SWIFT_NAME(fetchRequest());

@property (nonatomic) int64_t userID;
@property (nullable, nonatomic, copy) NSString *userName;

@end

NS_ASSUME_NONNULL_END

UserEntity+CoreDataProperties.m文件

#import "UserEntity+CoreDataProperties.h"

@implementation UserEntity (CoreDataProperties)

/// 返回搜索条件描述对象
+ (NSFetchRequest<UserEntity *> *)fetchRequest {
    return [NSFetchRequest fetchRequestWithEntityName:@"UserEntity"];
}

@dynamic userID;
@dynamic userName;

@end
  • 3.Core Data栈

Core Data栈中有一个或多个被管理对象上下文(NSManagedObjectContext),它连接到一个持久化存储协调器(NSPersistentStoreCoordinator)。一个持久化存储协调器(NSPersistentStoreCoordinator)连接到一个或多个持久化对象存储。持久化对象存储与底层存储文件关联。一个持久化存储协调器(NSPersistentStoreCoordinator)也可以管理多个被管理对象模型(NSManagedObjectModel)。一个持久化存储协调器(NSPersistentStoreCoordinator)就意味着一个Core Data栈。通过Core Data栈我们可以实现数据查询、插入、删除和修改等操作。

20190328151403312.png

PS:持久化对象存储(Persistent Object Store,POS)执行所有底层的从对象到数据的转换,并负责打开和关闭数据文件。它有三种持久化实现方式:SQLite、二进制文件、内存形式。

NSManagedObjectContext - 被管理对象上下文

NSManagedObjectContext,被管理对象上下文(Managed Object Context,Moc)类,在上下文中可以查找、删除、和插入对象,然后通过栈同步到持久化对象存储。

NSManagedObjectContext常用属性
@property (nullable, strong) NSPersistentStoreCoordinator *persistentStoreCoordinator;

属性描述上下文的持久化存储协调器。 协调器提供托管对象模型并处理持久化。多个上下文可以共享一个协调器。将persistentStoreCoordinator设置为nil将引发异常。如果想“断开”上下文与其持久存储协调器的连接,应该简单地将所有对上下文的强引用设置为nil,并允许它正常被释放。

@property (nonatomic, readonly) BOOL hasChanges;

属性描述 :一个布尔值,指示上下文是否有未提交的更改

NSManagedObjectContext常用函数
- (instancetype)initWithConcurrencyType:(NSManagedObjectContextConcurrencyType)ct NS_DESIGNATED_INITIALIZER  API_AVAILABLE(macosx(10.7),ios(5.0));

函数描述 : 使用指定的并发类型创建上下文

参数 :

ct : 上下文的并发类型。

返回值 : 上下文对象。

- (BOOL)save:(NSError **)error;

函数描述尝试将已注册对象还未保存的更改提交到上下文的父存储。如果上下文的父存储是持久存储协调器(NSPersistentStoreCoordinator),则将更改提交到外部存储。如果上下文的父存储是另一个托管对象的上下文,则save:方法仅更新该父存储中的托管对象。要将更改提交到外部存储,必须将更改保存在上下文链条中,直到并包括父级为持久存储协调器的上下文。

如果保存过程中存在多个错误(例如,多个编辑的对象存在验证失败),则返回的NSError说明表明存在多个错,其userInfo字典包含关键字NSDetailedErrors。与NSDetailedErrors键关联的值是包含单个NSError对象的数组。

在调用save:方法之前,应始终验证上下文是否有未提交的更改(使用hasChanges属性)。否则Core Data可能会执行不必要的工作。

参数 :

error : 指向NSError对象的指针。不需要创建NSError对象。如果传递NULL,则save操作在第一次失败后中止。

返回值 : 保存成功则为YES,不成功则为NO。

- (nullable NSArray *)executeFetchRequest:(NSFetchRequest *)request error:(NSError **)error NS_REFINED_FOR_SWIFT;

函数描述返回满足指定搜索请求条件的对象数组。在提取满足指定搜索条件的对象时会考虑以下几点 :

  • 如果提取请求没有谓词,那么将检索指定实体的所有实例,并对下面的其他条件进行模运算。

  • 满足提取请求指定的条件的对象(它是提取请求指定的实体的实例,如果有,它与提取请求的谓词匹配),并且已插入到上下文中但尚未保存到持久存储中,如果在该上下文上执行提取请求,则会检索该对象。

  • 如果上下文中的对象已被修改,则谓词将根据其上下文中的修改状态而不是持久存储中的当前状态进行评估。因此,如果上下文中的对象已被修改,使其符合提取请求的条件,则即使更改尚未保存到存储,并且存储中的值不符合条件,请求也会检索该对象。相反,如果上下文中的对象被修改为与提取请求不匹配,则即使存储中的版本匹配,提取请求也不会检索该对象。

  • 如果对象已从上下文中删除,则即使该删除尚未保存到存储,提取请求也不会检索该对象。

在没有开发人员干预的情况下,获取操作永远不会更改已实现的对象(填充、触发错误、读取等)以及待更新、插入或删除的对象。如果获取一些对象,并使用它们执行包含这些对象超集的新获取,则不会获取现有对象的新实例或更新数据,而是获取具有当前内存状态的现有对象。

参数 :

request :指定提取搜索条件的搜索条件描述对象。

error :如果执行提取时出现问题,则返回时包含一个描述问题的NSError实例。

返回值 :满足从调用该函数的上下文和与调用该函数的上下文的持久存储协调器相关联的持久存储中获取的符合搜索条件描述对象所指定的条件的对象数组。如果发生错误,则返回nil。如果没有对象符合搜索条件描述对象指定的条件,则返回空数组。

NSPersistentStoreCoordinator - 持久化存储协调器

NSPersistentStoreCoordinator,持久化存储协调器(Persistent Store Coordinator,PSC)类,在持久化对象存储之上提供了一个接口,使用模型来帮助上下文和持久存储进行通信,可以把它考虑成数据库的连接。没有协调器的上下文不能完全发挥作用,因为上下文除非通过协调器,否则它不能访问模型。

NSPersistentStoreCoordinator常用函数
- (instancetype)initWithManagedObjectModel:(NSManagedObjectModel *)model NS_DESIGNATED_INITIALIZER;

函数描述 :使用指定的被管理对象模型创建一个持久存储协调器

参数 :

model : 被管理对象模型。

返回值 : 使用被管理对象模型创建的持久存储协调器对象。

- (nullable __kindof NSPersistentStore *)addPersistentStoreWithType:(NSString *)storeType configuration:(nullable NSString *)configuration URL:(nullable NSURL *)storeURL options:(nullable NSDictionary *)options error:(NSError **)error;

函数描述 :在提供的位置添加特定类型的持久存储

参数 :

storeType : 指定存储类型的字符串常量(如NSSQLiteStoreType)。

configuration :持久存储协调器管理对象模型中将被新存储使用的配置的名称。可以为nil。

storeURL : 持久化存储的文件位置。

options :一个包含键值对的字典,该键值对指定存储是否应该是只读的,以及(对于XML存储)在读取XML文件之前是否应该根据DTD进行验证。这个值可以为nil。

error :如果无法创建新存储空间,则在返回值中包含一个描述问题的NSError实例。

返回值 : 新创建的存储空间,如果发生错误,则为nil。

- (void)deleteObject:(NSManagedObject *)object;

函数描述指定在提交更改时应从其持久存储中删除的对象。提交更改后,对象将从uniquing表中删除。如果对象尚未保存到持久存储,则只需将其从调用函数的上下文中删除。

参数 :

object :要删除的托管对象。

NSManagedObjectModel - 被管理对象模型

NSManagedObjectModel,被管理对象模型(Managed Object Model,MOM)类,是系统中的"实体",与数据库中的表等对象对应。

NSManagedObjectModel常用函数
+ (nullable NSManagedObjectModel *)mergedModelFromBundles:(nullable NSArray<NSBundle *> *)bundles; 

函数描述 :返回通过合并给定捆绑包(bundle)中找到的所有模型创建的模型

参数 :

bundles : 要搜索的NSBundle实例的数组。如果指定nil,则搜索主捆绑包(main bundle)。

返回值 :通过合并捆绑包(bundle)中的所有模型创建的模型。

NSEntityDescription - 实体描述

实体(NSEntityDescription)与托管对象(NSManagedObjectModel)的关系就像表与行的关系(使用数据库类比)。实例指定实体的名称、其属性和关系(作为NSAttributeDescription和NSRRelationshipDescription的实例)以及表示该实体的类。该类的实例对应于关联持久存储中的条目。一个实体描述至少需要:

    1. 一个名字。
    1. 对应的托管对象的类名。

如果不指定类名,框架将使用NSManagedObject。

NSEntityDescription常用函数
+ (__kindof NSManagedObject *)insertNewObjectForEntityForName:(NSString *)entityName inManagedObjectContext:(NSManagedObjectContext *)context;

函数描述为具有给定名称的实体创建、配置并返回类的实例。此方法可以轻松地创建给定实体的实例,而不必担心创建托管对象的细节。

参数 :

entityName : 实体的名称。

context : 要使用的托管对象上下文。

返回值 : 名为entityName的实体的类的新的、自动释放的、完全配置的实例。实例具有其实体描述集,并将其插入上下文中。

NSFetchRequest - 搜索条件描述

用于从持久存储中检索数据时搜索条件的描述。搜索条件描述必须包含实体描述(NSEntityDescription的实例)或指定要搜索的实体的实体名称。它通常常包含一个谓(NSPredicte的实例),用于指定作为筛选的属性和对搜索条件的约束,如果不指定谓词,则会选择指定实体的所有实例。搜索条件描述还可以指定应返回的最大对象数、应访问哪些数据存储区、返回托管对象还是仅返回对象ID、对象是否已完全填充其属性、获取哪些指定的属性、开始搜索的偏移量、是否应包括未保存的更改等。当执行NSFetchRequest的实例时,它总是访问底层持久存储以检索最新结果。

NSFetchRequest常用属性
@property (nullable, nonatomic, strong) NSPredicate *predicate;

属性描述描述搜索条件的谓词,谓词实例限制NSFetchRequest实例要搜索的对象的选择。

创建存储管理类TTPersistentStoreManager,实现Core Data栈以现实简单的增、删、改、查

TTPersistentStoreManager.h文件

#import <Foundation/Foundation.h>
#import "UserEntity+CoreDataClass.h"

NS_ASSUME_NONNULL_BEGIN

@interface TTPersistentStoreManager : NSObject

/// 单例
+ (instancetype)sharedManager;

/// 保存上下文,对save:方法的封装,方便获取NSError
- (NSError *)saveContext;

/// 根据用户ID插入用户
/// - Parameter userID: 用户ID
/// - Parameter userName: 用户名
- (UserEntity *)insertUserWithUserID:(int64_t)userID userName:(NSString *)userName;

/// 根据用户ID查询用户
/// - Parameter userID: 用户ID
- (nullable UserEntity *)queryqueryWithUserID:(int64_t)userID;

/// 根据用户ID删除用户
/// - Parameter userID: 用户ID
- (void)deleteUserWithUserID:(int64_t)userID;

@end

NS_ASSUME_NONNULL_END
#import "TTPersistentStoreManager.h"

@interface TTPersistentStoreManager()

//操纵和跟踪托管对象更改的上下文对象
@property (nonatomic, strong) NSManagedObjectContext *context;
//持久化存储协调器
@property (nonatomic, strong) NSPersistentStoreCoordinator *coordinator;
//被管理对象模型
@property (nonatomic, strong) NSManagedObjectModel *managedObjectModel;

@end

@implementation TTPersistentStoreManager

/// 单例
+ (instancetype)sharedManager {
    static TTPersistentStoreManager *_manager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _manager = [[self alloc] init];
    });
    return _manager;
}

/// 保存上下文,对save:方法的封装,方便获取NSError
- (NSError *)saveContext {
    NSError *error = nil;
    //如果上下文有未提交的更改,提交更改
    if (self.context.hasChanges) {
        [self.context save:&error];
    }
    return error;
}

/// 根据用户ID插入用户
/// - Parameter userID: 用户ID
/// - Parameter userName: 用户名
- (UserEntity *)insertUserWithUserID:(int64_t)userID userName:(NSString *)userName;{
    //创建UserEntity实体的实例
    UserEntity *userEntity = (UserEntity *)[NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass(UserEntity.class) inManagedObjectContext:self.context];
    //为实体字段赋值
    userEntity.userID = userID;
    userEntity.userName = userName;
    //保存上下文
    if ([self saveContext]) {
        [NSException raise:@"创建UserEntity失败" format:@"userID = %lld", userID];
        return nil;
    } else {
        return userEntity;
    }
}

/// 根据用户ID查询用户
/// - Parameter userID: 用户ID
- (nullable UserEntity *)queryqueryWithUserID:(int64_t)userID {
    //获取搜索条件描述对象
    NSFetchRequest<UserEntity *> *fetchRequest = UserEntity.fetchRequest;
    //搜索条件谓词
    fetchRequest.predicate = [NSPredicate predicateWithFormat:@"userID = %lld", userID];
    //提取满足指定搜索请求条件的对象数组
    NSError *error = nil;
    NSArray<UserEntity *> *caches = [self.context executeFetchRequest:fetchRequest error:&error];
    //提取发生错误
    if (error) {
        NSException *exception = [[NSException alloc] initWithName:error.localizedDescription reason:error.localizedFailureReason userInfo:error.userInfo];
        [exception raise];
    }
    //提取的对象大于1(业务错误)
    if (caches.count > 1) {
        [NSException raise:@"通过userID获取到的用户数量不为1" format:@"userID = %lld", userID];
    }
    return caches.firstObject;
}

/// 根据用户ID删除用户
/// - Parameter userID: 用户ID
- (void)deleteUserWithUserID:(int64_t)userID {
    //根据用户ID查询用户
    UserEntity *user = [self queryqueryWithUserID:userID];
    //如果查到用户,将改用户删除
    if (user) {
        [self.context deleteObject:user];
        [self saveContext];
    }
}

#pragma mark - 懒加载

/// 懒加载被管理对象上下文
- (NSManagedObjectContext *)context {
    if (_context == nil) {
        //创建主队列并发类型的上下文
        _context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        //设置上下文的持久化存储协调器
        _context.persistentStoreCoordinator = self.coordinator;
    }
    return _context;
}

/// 懒加载持久化存储协调器
- (NSPersistentStoreCoordinator *)coordinator {
    if (_coordinator == nil) {
        //使用指定的被管理对象模型创建一个持久存储协调器
        _coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
        //获取Documents目录位置
        NSString *documentDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
        //生成sqlite文件路径
        NSURL *sqliteURL = [[NSURL fileURLWithPath:documentDirectory] URLByAppendingPathComponent:@"TT.sqlite"];
        //设置自动尝试迁移版本化的存储
        //设置如果没有找到映射模型,协调器将尝试推断映射模型。
        NSDictionary *options = @{
            NSMigratePersistentStoresAutomaticallyOption: @(YES),
            NSInferMappingModelAutomaticallyOption: @(YES)
        };
        //在提供的位置添加特定类型的持久存储
        NSError *error = nil;
        [_coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:sqliteURL options:options error:&error];
        //在提供的位置添加特定类型的持久存储失败,记录失败原因,终止程序
        if (error) {
            NSException *exception = [[NSException alloc] initWithName:error.localizedDescription reason:error.localizedFailureReason userInfo:error.userInfo];
            [exception raise];
        }
    }
    return _coordinator;
}

/// 懒加载被管理对象模型
- (NSManagedObjectModel *)managedObjectModel {
    if (_managedObjectModel == nil) {
        //指定类关联的NSBundle对象
        NSBundle *bundle = [NSBundle bundleForClass:UserEntity.class];
        //创建NSBundle对象中所有模型合并创建的被管理对象模型
        _managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:@[bundle]];
    }
    return _managedObjectModel;
}

@end

测试插入数据

- (void)viewDidLoad {
    [super viewDidLoad];
    UserEntity *user = [TTPersistentStoreManager.sharedManager insertUserWithUserID:1 userName:@"小赵"];
    UserEntity *user2 = [TTPersistentStoreManager.sharedManager insertUserWithUserID:2 userName:@"小钱"];
    NSLog(@"%@",user);
    NSLog(@"%@",user2);
}

控制台打印如下:

2022-12-23 17:27:27.799714+0800 test[10579:306771] <UserEntity: 0x600000f91ae0> (entity: UserEntity; id: 0x8564420472ad35cd <x-coredata://C8D58513-44BD-45F4-BA71-393C7590FBD4/UserEntity/p1>; data: {
    userID = 1;
    userName = "\U5c0f\U8d75";
})
2022-12-23 17:27:29.197637+0800 test[10579:306771] <UserEntity: 0x600000f97b10> (entity: UserEntity; id: 0x8564420472cd35cd <x-coredata://C8D58513-44BD-45F4-BA71-393C7590FBD4/UserEntity/p2>; data: {
    userID = 2;
    userName = "\U5c0f\U94b1";
})

前往应用程序沙盒目录下的Documents文件夹中,可以发现创建的sqlite文件,如下:

截屏2022-12-23 17.28.16.png

使用Navicat Premium工具打开文件,可以看到刚刚插入的数据,说明数据插入成功了:

截屏2022-12-23 17.38.00.png

测试查询与修改数据

- (void)viewDidLoad {
    [super viewDidLoad];
    UserEntity *user =  [TTPersistentStoreManager.sharedManager queryqueryWithUserID:2];
    NSLog(@"%lld - %@",user.userID,user.userName);
    user.userName = @"小李";
    [TTPersistentStoreManager.sharedManager saveContext];
}

通过控制台打印,可以看到成功查询了数据 :

2022-12-23 22:07:52.905481+0800 test[1691:41996] 2 - 小钱

使用Navicat Premium工具打开文件,可以看到小钱已经改为小李,说明修改成功:

截屏2022-12-23 22.09.40.png

测试删除数据

- (void)viewDidLoad {
    [super viewDidLoad];
    [TTPersistentStoreManager.sharedManager deleteUserWithUserID:1];
}

使用Navicat Premium工具打开文件,可以看到UserID为1的小赵被删除了:

截屏2022-12-23 22.28.15.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容