iOS小技能:常用的数据存储方式

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: iOS小技能:常用的数据存储方式

前言

iOS应用数据存储的常用方式

  1. preference偏好设置
  2. XML属性列表归档(plist)
  3. 使用Keychain 存储,例如存储UUID来解决设备唯一标识符获取方案
  4. NSKeyedArchiver归档(NSCoding)
  5. SQLite3  https://github.com/ccgus/fmdb
  6. Core Data  http://m.blog.csdn.net/article/details?id=8563438
  7. 第三方库:BGFMDB的使用

I 应用沙盒

每个iOS应用都有自己的应用沙盒,来与其他文件系统隔离。

1.1 目录结构分析

1、应用程序包

(上图中的Layer)包含了所有的资源文件和可执行文件

2、Documents

保存应用运行时生成的需要持久化的数据,iTunes同步设备时会备份该目录。例如,游戏应用可将游戏存档保存在该目录

3、tmp:

保存应用运行时所需的临时数据,使用完毕后再将相应的文件从该目录删除。应用没有运行时,系统也可能会清除该目录下的文件。iTunes同步设备时不会备份该目录

4、Library/Caches:

保存应用运行时生成的需要持久化的数据,iTunes同步设备时不会备份该目录。一般存储体积大、不需要备份的非重要数据

5 Library/Preference:

保存应用的所有偏好设置,iOS的Settings(设置)应用会在该目录中查找应用的设置信息。iTunes同步设备时会备份该目录

1.2 应用沙盒目录的获取方式

tmp:

FOUNDATION_EXPORT NSString *NSTemporaryDirectory(void);

沙盒根目录:

FOUNDATION_EXPORT NSString *NSHomeDirectory(void);
NSLog(@"%@",NSHomeDirectory());//: /Users/devzkn/Library/Developer/CoreSimulator/Devices/5A6E02FF-A156-455B-AE43-C207F4E7FBC4/data/Containers/Data/Application/E3F77B8B-C88C-4577-A943-187151AB19CC
Documents:(2种方式)
方式1:
```objectivec
NSString *home = NSHomeDirectory();
NSString *documents = [home stringByAppendingPathComponent:@"Documents"];
// 不建议采用,因为新版本的操作系统可能会修改目录名

方式2:

//FOUNDATION_EXPORT NSArray<NSString *> *NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask, BOOL expandTilde);
// NSUserDomainMask 代表从用户文件夹下找
// YES 代表展开路径中的波浪字符“~”
NSArray *array = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, NO);~/Documents/data.plist,yes的话,就展开全路径
// 在iOS中,只有一个目录跟传入的参数匹配,所以这个集合里面只有一个元素
NSString *documents = [array objectAtIndex:0];

Library/Caches (类似Documents)

Library/Preference

通过NSUserDefaults类存取该目录下的设置信息

1.3 偏好设置(standardUserDefaults 的使用)

针对应用的字体大小、是否保存用户名等偏好设置,IOS的每个应用都有个NSUserDefaults实例,通过它来存取偏好设置。NSUserDefaults设置数据的时候,不是立即写入,而是根据时间戳定时地把缓存中的数据写入本地磁盘。所以调用了set方法之后数据有可能还没有写入磁盘应用程序就终止了。

出现以上问题,可以通过调用synchornize方法强制写入

- (BOOL)synchronize;

偏好设置的好处

1、不用关心文件名 2、快速的进行键值对存储

  • 偏好设置的工具方法

+ (void)setObject:(id)value forKey:(NSString *)key{
    [[NSUserDefaults standardUserDefaults] setObject:value forKey:key];
    [[NSUserDefaults standardUserDefaults] synchronize];
}
+ (id)objectForKey:(NSString *)key{
    return [[NSUserDefaults standardUserDefaults] objectForKey:key];
}
  • 例子:是否展示过版本新特性,也即是引导页

-(void)setBShowIntroduce:(BOOL)bShowIntroduce
{
    //    [SessionMgr Instance].strOrderAmount = bShowIntroduce;
    [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:bShowIntroduce] forKey:@"bShowIntroduce"];
    [[NSUserDefaults standardUserDefaults] synchronize];
}
/**
 此版本是否提示过文案
 */
-(BOOL)bShowIntroduce
{
    //    return [SessionMgr Instance].strOrderAmount;
    NSNumber *numShowIntroduce = [[NSUserDefaults standardUserDefaults] objectForKey: @"bShowIntroduce"];
    return [numShowIntroduce boolValue];
}

1.4 存储自定义类型对象到NSUserDefaults

自定义的类型需要转成NSData再存

setObject

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:model];
        [[NSUserDefaults standardUserDefaults] setObject:data forKey:ModelKey];

unarchiveObjectWithData

NSData *data = [[NSUserDefaults standardUserDefaults] forKey:ModelKey];
    [Session shareSession].model= [NSKeyedUnarchiver unarchiveObjectWithData:data];

———————————————— 版权声明:本文为CSDN博主「#公众号:iOS逆向」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/z929118967/article/details/77387782

II 使用Keychain 存储

2.1  什么是Keychain?

Keychain是OS X和iOS都提供的一种安全存储敏感信息工具。

比如,我们可以在Keychain中存储用户名、密码等信息。

Keychain的安全机制从系统层面保证了存储的敏感信息不会被非法读取或者窃取。

Keychain的特点如下:

1、保存在Keychain中的数据,即使应用程序被卸载,数据仍然存在;重新安装应用程序,我们也可以从Keychain中读取这些数据。2、Keychain中的数据可以通过Group的方式实现应用程序之间共享,只要应用程序具有相同的TeamID即可。3、保存在Keychain中的数据都是经过加密的,因此非常安全。

———————————————— 版权声明:本文为CSDN博主「#公众号:iOS逆向」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/z929118967/article/details/115860229

2.2 案例:存储UUID来解决设备唯一标识符获取方案

- (NSString *)strUUID{
    if (_strUUID == nil || [_strUUID isEqualToString:@""]) {
        CMPayKeychainItemWrapper *wrapper = [[CMPayKeychainItemWrapper alloc] initWithIdentifier:@"https://kunnan.blog.csdn.net/"accessGroup:nil];
        // 读测试
        NSString *strMD5 = [wrapper  objectForKey:(__bridge id)kSecAttrAccount];
        NSLog(@"读出md5:%@",strMD5);
        if (strMD5 == nil || [strMD5 isEqualToString:@""])
        {
            strMD5 = [MD5Generator MD5];
            // 如果是模拟器
            if (TARGET_IPHONE_SIMULATOR){
            }else{
                [wrapper setObject:strMD5 forKey:(__bridge id)kSecAttrAccount];
            }
            NSLog(@"写入MD5:%@",strMD5);
        }
        _strUUID = strMD5;
        NSLog(@"strUUID:%@", strMD5);
    }
    return _strUUID;
}
- (NSString *)openUDID{
    if (_openUDID == nil  || [_openUDID isEqualToString:@""]) {
        CMPayKeychainItemWrapper *wrapper = [[CMPayKeychainItemWrapper alloc] initWithIdentifier:@"weiliu.openUdid"accessGroup:nil];
        // 读测试
        NSString *openUDID = [wrapper  objectForKey:(__bridge id)kSecValueData];
        NSLog(@"读出_openUDID:%@",openUDID);
        if (openUDID == nil || [openUDID isEqualToString:@""])
        {
          openUDID = [OpenUDID value];
            // 如果是模拟器
            if (TARGET_IPHONE_SIMULATOR){
            }else{
                [wrapper setObject:openUDID forKey:(__bridge id)kSecValueData];
            }
            NSLog(@"写入_openUDID:%@",openUDID);
        }
        _openUDID = openUDID;
        NSLog(@"_openUDID:%@", openUDID);
    }
    return _openUDID;
}

iOS安全之【设备唯一标识符获取方案】设备ID除了使用_idfa、_idfv 还可使用Keychain 存储UUID及通过Safari与mobileconfig获取 | 蓄力计划

———————————————— 版权声明:本文为CSDN博主「#公众号:iOS逆向」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/z929118967/article/details/115860229

2.3 iOS安全之敏感逻辑的保护方案

【把函数名隐藏在结构体里,以函数指针成员的形式存储】案例:js根据key从本地方法获取设备及签名信息 (完整demo)

1、文章:https://kunnan.blog.csdn.net/article/details/115857706 2、原理:为了提高代码的安全性,可以采用把把函数名隐藏在结构体里,以函数指针成员的形式存储。编译后,只留了下地址,去掉了名字和参数表,提高了逆向成本和攻击门槛. 3、应用场景:签名函数 4、从CSDN下载Demo:https://download.csdn.net/download/u011018979/16751837

III 属性列表(.plist)

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

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

只有具备writeToFile:方法的对象才可以向plist文件存储数据

- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile;
+ (nullable NSDictionary<KeyType, ObjectType> *)dictionaryWithContentsOfFile:(NSString *)path;

image.png

IV NSKeyedArchiver

只有遵守了NSCoding协议的对象才可以使用NSKeyedArchiver进行归档。

protocol NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder;//每次归档对象的时候都会调用这个方法,来指定归档对象的每个实例变量- (void)encodeObject:(nullable id)objv forKey:(NSString *)key;
/**
每次从文件中恢复对象时,都会调用这个方法来指定如何解码文件中的数据为对象的实例变量--- (nullable id)decodeObjectForKey:(NSString *)key;
*/
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder; // NS_DESIGNATED_INITIALIZER
@end

NS_DESIGNATED_INITIALIZER 宏的使用

如果存在继承关系,记得调用父类的NSCoding协议方法。

4.1 例子:存储自定义类型

@interface HSPersonModel () <NSCoding>
@end
@implementation HSPersonModel
/** 对象规定的时候调用*/
#if 1
- (void)encodeWithCoder:(NSCoder *)aCoder{
    NSLog(@"%s",__func__);
    [aCoder encodeInt:self.age forKey:@"age"];
    [aCoder encodeObject:self.name forKey:@"name"];
}
#endif
/**  对象解挡的时候调用 */
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
    NSLog(@"%s",__func__);
    self = [super init];//继承父类的属性
    if (self) {
        //定义如何解挡
        self.age = [aDecoder decodeIntForKey:@"age"];
        self.name = [aDecoder decodeObjectForKey:@"name"];
    }
    return self;
}

存取自定义对象

- (IBAction)savePerson:(id)sender {
    //存储自定义对象
    HSPersonModel *model = [HSPersonModel new];
    model.age= 122;
    [model setName:@"Lydia"];
    NSString *cachPath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
    NSString *filePath = [cachPath stringByAppendingPathComponent:@"person.arc"];
    BOOL isSuccess=  [NSKeyedArchiver archiveRootObject:model toFile:filePath];
    if (isSuccess) {
        NSLog(@"%@",filePath);
    }
}
- (IBAction)readPerson:(id)sender {
    NSString *cachPath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
    NSString *filePath = [cachPath stringByAppendingPathComponent:@"person.arc"];
    NSLog(@"%@",filePath);
    HSPersonModel *model = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
    NSLog(@"%d,%@",model.age,model.name);
}

4.2 NS_DESIGNATED_INITIALIZER 宏的使用

如果父类也遵守了NSCoding协议,记得调用父类的NSCoding协议方法

UIView 就是个典型的例子,UIView的子类必学实现initWithCoder:decoder,否则无法继承父类属性。initWithCoder:decoder的优先级比- (void)awakeFromNib还高。

应该在encodeWithCoder:方法中加上一句 [super encodeWithCode:encode];确保继承的实例变量也能被编码,即也能被归档

同样地,应该在initWithCoder:方法中加上一句 self = [super initWithCoder:decoder];

--确保继承的实例变量也能被解码,即也能被恢复*/

4.3 归档NSArray

image.png

+ (BOOL)archiveRootObject:(id)rootObject toFile:(NSString *)path;//归档
+ (nullable id)unarchiveObjectWithFile:(NSString *)path;//恢复解码

V NSData

可以将多个对象写入到同一个文件中,那么就要使用NSData来进行归档对象。NSData可以为一些数据提供临时存储空间,以便随后写入文件,或者存放从磁盘读取的文件内容。可以使用[NSMutableData data]创建可变数据空间。

5.1 FMDB例子

使用NSData 来存储多个对象到数据库中

+(void)savestatusesWithStatuses:(NSArray *)statuses{
    if (statuses.count <= 0) {
        return;
    }
//    [_db open];
    // 插入数据
    for (NSDictionary *obj in statuses) {
        //先查询
        BOOL isExistsStatus = [self isExistsStatus:obj[@"idstr"]];
        NSError *error;
#warning 只有将自定义对象实现nscoding 协议,才可以使用archivedDataWithRootObject 进行nsdata 的转换
        NSData *data = [NSKeyedArchiver  archivedDataWithRootObject:obj];//以2进制
        if (isExistsStatus) {
            //更新
             [_db executeUpdateWithFormat:@"update   t_status set status= %@ where idStr=%@ and access_token =%@;",data,obj[@"idstr"],[HWAccountTool account].access_token];//不能自己使用//        [NSString stringWithFormat:(nonnull NSString *), ...] 拼写sql
        }else{
            //插入
            [_db executeUpdateWithFormat:@"INSERT  into   t_status (status , idStr,access_token) VALUES (%@,%@,%@);",data,obj[@"idstr"],[HWAccountTool account].access_token];//不能自己使用//        [NSString stringWithFormat:(nonnull NSString *), ...] 拼写sql
        }
        if (error) {
            NSLog(@"%@",error);
        }
    }
    [_db commit];
//    [_db close];
}

5.2 例子2:利用NSData归档2个Person对象到同一文件中

//1.(编码)
// 新建一块可变数据区
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];
//2.恢复(解码)
// 从文件中读取数据
NSData *data = [NSData dataWithContentsOfFile:path];
// 根据数据,解析成一个NSKeyedUnarchiver对象
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
Person *person1 = [unarchiver decodeObjectForKey:@"person1"];
Person *person2 = [unarchiver decodeObjectForKey:@"person2"];
// 恢复完毕
[unarchiver finishDecoding];

image.png

5.3 例子3:利用NSData实现深copy

// 临时存储person1的数据
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:person1];
// 解析data,生成一个新的Person对象
Student *person2 = [NSKeyedUnarchiver unarchiveObjectWithData:data];
// 分别打印内存地址
NSLog(@"person1:0x%x",person1);// person1:0x7177a60
NSLog(@"person2:0x%x",person2);// person2:0x7177cf0

VI SQLite3(开源的嵌入式关系型数据库)

6.1 基本用法

可移植性好、易使用、开销小--无类型的(可以保存任意类型的数据到任意表的任意字段) 1、SQLite3常用的5种数据类型:text、integer、float、boolean、blob 2、在iOS中使用SQLite3,首先要添加库文件libsqlite3.dylib和导入主头文件sqlite3 3、创建、打开、关闭数据库

SQLITE_API int SQLITE_STDCALL sqlite3_open(
  const char *filename,   /* Database filename (UTF-8) */
  sqlite3 **ppDb          /* OUT: SQLite db handle */
);
SQLITE_API int SQLITE_STDCALL sqlite3_open16(
  const void *filename,   /* Database filename (UTF-16) */
  sqlite3 **ppDb          /* OUT: SQLite db handle */
);
SQLITE_API int SQLITE_STDCALL sqlite3_open_v2(
  const char *filename,   /* Database filename (UTF-8) */
  sqlite3 **ppDb,         /* OUT: SQLite db handle */
  int flags,              /* Flags */
  const char *zVfs        /* Name of VFS module to use */
);
//创建或打开数据库
// path为:~/Documents/person.db
sqlite3 *db;
int result = sqlite3_open([path UTF8String], &db);
/**代码解析:
usqlite3_open()将根据文件路径打开数据库,如果不存在,则会创建一个新的数据库。如果result等于常量SQLITE_OK,则表示成功打开数据库
usqlite3 *db:一个打开的数据库实例
数据库文件的路径必须以字符串(而非NSString)传入*/
//关闭数据库:
sqlite3_close(db);

4、执行不返回数据的sql语句

SQLITE_API int SQLITE_STDCALL sqlite3_exec(
  sqlite3*,                                  /* An open database */
  const char *sql,                           /* SQL to be evaluated */
  int (*callback)(void*,int,char**,char**),  /* Callback function */
  void *,                                    /* 1st argument to callback */
  char **errmsg                              /* Error msg written here */
);
char *errorMsg;  // 用来存储错误信息
char *sql = "create table if not exists t_person(id integer primary key autoincrement, name text, age integer);";
int result = sqlite3_exec(db, sql, NULL, NULL, &errorMsg);
/**可以执行任何SQL语句,比如创表、更新、插入和删除操作。但是一般不用它执行查询语句,因为它不会返回查询到的数据
usqlite3_exec()还可以执行的语句:
①开启事务:begin transaction;
②回滚事务:rollback;
③提交事务:commit;*/

5、带占位符进行数据插入

SQLITE_API int SQLITE_STDCALL sqlite3_prepare_v2(
  sqlite3 *db,            /* Database handle */
  const char *zSql,       /* SQL statement, UTF-8 encoded */
  int nByte,              /* Maximum length of zSql in bytes. */
  sqlite3_stmt **ppStmt,  /* OUT: Statement handle */
  const char **pzTail     /* OUT: Pointer to unused portion of zSql */
);
char *sql = "insert into t_person(name, age) values(?, ?);";
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {   //usqlite3_prepare_v2()返回值等于SQLITE_OK,说明SQL语句已经准备成功,没有语法问题
    sqlite3_bind_text(stmt, 1, "母鸡", -1, NULL);  
    sqlite3_bind_int(stmt, 2, 27);
}
if (sqlite3_step(stmt) != SQLITE_DONE) {   //执行SQL语句返回SQLITE_DONE代表成功执行完毕
    NSLog(@"插入数据错误");
}
sqlite3_finalize(stmt);//销毁sqlite3_stmt *对象
/**  
usqlite3_bind_text():大部分绑定函数都只有3个参数
①第1个参数是sqlite3_stmt *类型
②第2个参数指占位符的位置,第一个占位符的索引从开始1,不是0
③第3个参数指占位符要绑定的值
④第4个参数指在第3个参数中所传递数据的长度,对于C字符串,可以传递-1代替字符串的长度
⑤第5个参数是一个可选的函数回调,一般用于在语句执行后完成内存清理工作
*/

6、查询数据 查询数据

char *sql = "select id,name,age from t_person;";
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {  
    while (sqlite3_step(stmt) == SQLITE_ROW) {//代表遍历到一条新记录
        int _id = sqlite3_column_int(stmt, 0);
        char *_name = (char *)sqlite3_column_text(stmt, 1);
        NSString *name = [NSString stringWithUTF8String:_name];
        int _age = sqlite3_column_int(stmt, 2);
        NSLog(@"id=%i, name=%@,age=%i", _id, name, _age);  
    }
}
sqlite3_finalize(stmt);//销毁stmt对象
/**代码解析
usqlite3_step()返回SQLITE_ROW代表遍历到一条新记录
usqlite3_column_*()用于获取每个字段对应的值,第2个参数是字段的索引,从0开始*/

6.2 数据库缓存性能进行优化

在数据采集的过程中,插入事件和查询事件都是比较频繁的操作,如果每次都做“预解析SQL语句”的操作,将会造成资源的大量浪费。

对于插入事件-insertEvent:方法来说,每次操作的SQL语句都是相同的,因此“预解析SQL语句”只需执行一次即可。

由于每次需要绑定不同的数据,我们只需要重置一下之前的sqlite3_stmt,然后绑定新的数据即可。

sqlite3_stmt 优化

/**
 // 最后一次查询的事件数量
 引入一个静态变量lastSelectEventCount来记录上次查询事件的条数,然后判断SQL语句是否有改变。
 */
static NSUInteger lastSelectEventCount = 50;
static sqlite3_stmt *selectStmt = NULL;
- (NSArray<NSString *> *)selectEventsForCount:(NSUInteger)count {
    // 初始化数组,用于存储查询到的事件数据
    NSMutableArray<NSString *> *events = [NSMutableArray arrayWithCapacity:count];
#pragma mark - ******** 这里需要使用dispatch_sync函数,不能使用dispatch_async函数,否则会导致返回的事件不完整。
    dispatch_sync(self.queue, ^{
        // 当本地事件数据为 0 时,直接返回
        if (self.eventCount == 0) {
            return ;
        }
        if (count != lastSelectEventCount) {
            lastSelectEventCount = count;
            selectStmt = NULL;//由于每次需要绑定不同的数据,我们只需要重置一下之前的sqlite3_stmt,然后绑定新的数据即可。
        }
        if (selectStmt) {
            // 重置查询语句,重制之后可重新查询数据
            sqlite3_reset(selectStmt);
        } else {
            // 查询语句:从events表中查询前50条(默认条数)数据,并按id升序排列。
            NSString *sql = [NSString stringWithFormat:@"SELECT id, event FROM events ORDER BY id ASC LIMIT %lu", (unsigned long)count];
            // 准备执行 SQL 语句,获取 sqlite3_stmt
            if (sqlite3_prepare_v2(self.database, sql.UTF8String, -1, &selectStmt, NULL) != SQLITE_OK) {
                // 准备执行 SQL 语句失败,打印 log 返回失败(NO)
                return NSLog(@"SQLite stmt prepare error: %s", sqlite3_errmsg(self.database));
            }
        }
        // 执行 SQL 语句
        while (sqlite3_step(selectStmt) == SQLITE_ROW) {
            // 将当前查询的这条数据转换成 NSData 对象
            NSData *data = [[NSData alloc] initWithBytes:sqlite3_column_blob(selectStmt, 1) length:sqlite3_column_bytes(selectStmt, 1)];
            // 将查询到的事件数据转换成 json 字符串
            NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
#ifdef DEBUG
            NSLog(@"%@", jsonString);
#endif
            // 将 json 字符串添加到数组中
            [events addObject:jsonString];
        }
    });
    return events;
}

———————————————— 版权声明:本文为CSDN博主「#公众号:iOS逆向」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/z929118967/article/details/77387782

VII coreDate (对象-关系映射ORM)

能够将OC对象转化成数据,保存在SQLite3数据库文件中,也能够将保存在数据库中的数据还原成OC对象。在此数据操作期间,不需要编写任何SQL语句(只须操作对象)。使用此功能,要添加CoreData.framework和导入主头文件

<CoreData/CoreData.h>

image.png

7.1 模型文件

在Core Data,需要进行映射的对象称为实体(entity),而且需要使用Core Data的模型文件来描述应用的所有实体和实体属性. 1>这里以Person和Card(身份证)2个实体为例子,先看看实体属性和之间的关联关系

image.png

*添加Person实体的基本属性

image.png

*在Person中添加card属性

image.png

*在Card中添加person属性

image.png

7.2 NSManagedObject

通过Core Data从数据库取出的对象,默认情况下都是NSManagedObject对象; NSManagedObject的工作模式有点类似于NSDictionary对象,通过键-值对来存取所有的实体属性:setValue:forKey: 存储属性值(属性名为key);valueForKey: 获取属性值(属性名为key)

image.png

7.3 coredata 主要对象

image.png

7.4 搭建coredate上下文

/1.从应用程序包中加载模型文件
NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];
//2.传入模型,初始化NSPersistentStoreCoordinator
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
//构建SQLite文件路径
NSString *docs =[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES) lastObject];
NSURL *url = [NSURL fileURLWithPath:[docs stringByAppendingPathComponent:@"person.data"]];
//3.添加持久化存储库,这里使用SQLite作为存储库
NSError *error = nil;
NSPersistentStore *store = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error];
if (store == nil) { // 直接抛异常  
[NSException raise:@"添加数据库错误" format:@"%@",[error localizedDescription]];
}
//4.初始化上下文,设置persistentStoreCoordinator属性
NSManagedObjectContext *context =[[NSManagedObjectContext alloc] init];
context.persistentStoreCoordinator = psc;

7.5 添加数据

//1.传入上下文,创建一个Person实体对象
NSManagedObject *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:context];
//设置简单属性
[person setValue:@"MJ" forKey:@"name"];
[person setValue:[NSNumber numberWithInt:27] forKey:@"age"];
//传入上下文,创建一个Card实体对象
NSManagedObject *card = [NSEntityDescription insertNewObjectForEntityForName:@"Card" inManagedObjectContext:context];
[card setValue:@"4414241933432" forKey:@"no"];
//2.设置Person和Card之间的关联关系
[person setValue:card forKey:@"card"];
//3.利用上下文对象,将数据同步到持久化存储库
NSError *error = nil;
BOOL success = [context save:&error];
if (!success) {
    [NSException raise:@"访问数据库错误" format:@"%@",[error localizedDescription]];
}
// 如果是想做更新操作:只要在更改了实体对象的属性后调用[context save:&error],就能将更改的数据同步到数据库

7.6 查询数据

//1.初始化一个查询请求
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
//设置要查询的实体
NSEntityDescription *desc = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:context];
//1>设置排序(按照age降序)
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:NO];
request.sortDescriptors = [NSArray arrayWithObject:sort];
//2>设置条件过滤(name like '%Itcast-1%')
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name like %@", @"*Itcast-1*"];
request.predicate = predicate;
//2.执行请求
NSError *error = nil;
NSArray *objs = [context executeFetchRequest:request error:&error];
if (error) {  
    [NSException raise:@"查询错误" format:@"%@",
    [error localizedDescription]];
}
//3.遍历数据
for (NSManagedObject *obj in objs) {  
    NSLog(@"name=%@", [obj valueForKey:@"name"]
}

7.7 删除数据

//传入需要删除的实体对象
[context deleteObject:managedObject];
//将结果同步到数据库
NSError *error = nil;
[context save:&error];
if (error) {  
    [NSException raise:@"删除错误" format:@"%@",
    [error localizedDescription]];
}

7.8 打开coredata的sql日志输出 开关

image.png

7.9、coredata 的延迟加载

不会根据实体中的关联关系立即获取相应的关联对象

比如通过Core Data取出Person实体时,并不会立即查询相关联的Card实体;当应用真的需要使用Card时,才会查询数据库,加载Card实体的信

7.10、NSManagedObject子类

默认情况下,利用Core Data取出的实体都是NSManagedObject类型的,能够利用键-值对来存取数据 使用场景:存取数据的基础上,添加一些业务方式完成一些其他任务

例子:保存未读消息

#pragma mark - 保存未读消息
/** 先查询,在修改 */
+ (void)saveMessageWithMessage:(IPMessageDetailData*)data{
    if (data.messageId == nil || data == nil){
        return;
    }
    NSManagedObjectContext *context = [IPMessageDataTool shareContext];
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    request.entity = [NSEntityDescription entityForName:@"Message" inManagedObjectContext:context];
    // 设置排序(按照messageId降序)
    NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"messageId" ascending:NO];
    request.sortDescriptors = [NSArray arrayWithObject:sort];
    // 设置条件过滤(搜索name中包含字符串"Itcast-1"的记录,注意:设置条件过滤时,数据库SQL语句中的%要用*来代替,所以%Itcast-1%应该写成*Itcast-1*)
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"messageId = %@  AND operateID = %@",data.messageId,[SessionMgr Instance].operateID];
    request.predicate = predicate;
    // 执行请求
    //    request.fetchLimit = 50;
    NSError *error = nil;
    NSArray *objs = [context executeFetchRequest:request error:&error];
    if (error) {
        [NSException raise:@"查询错误" format:@"%@", [error localizedDescription]];
    }
    Message *tmpMessage;
    if (objs.count>0) {
        tmpMessage = objs[0];
        return;
    }else{
        tmpMessage = [NSEntityDescription insertNewObjectForEntityForName:@"Message" inManagedObjectContext:context];
    }
    tmpMessage.messageExpiredTime = data.messageExpiredTime;
    tmpMessage.messageIssueTime = data.messageIssueTime;
    tmpMessage.messageId = data.messageId;
    tmpMessage.messageTitle = data.messageTitle;
    tmpMessage.operateID = [SessionMgr Instance].operateID;
    tmpMessage.isRead = NO;
    BOOL success = [context save:&error];
    if (!success) {
        [NSException raise:@"访问数据库错误" format:@"%@", [error localizedDescription]];
    }
}

VIII   BGFMDB

iOS 使用BGFMDB存储信息到本地数据库教程【应用场景:商户首次登陆同意协议流程】

https://blog.csdn.net/z929118967/article/details/112533687

IX  NSPredicate 的使用举例

谓词技术的使用  NSPredicate   (以一定的条件(特定日期)过滤maTemp数组,即进行大数据搜索。)

谓词的使用例子

http://blog.csdn.net/z929118967/article/details/74066170

image.png

see also


目录
相关文章
|
7月前
|
JSON JavaScript 安全
iOS应用程序数据保护:如何保护iOS应用程序中的图片、资源和敏感数据
iOS应用程序数据保护:如何保护iOS应用程序中的图片、资源和敏感数据
64 1
|
7月前
|
存储 数据建模 数据库
IOS开发数据存储:什么是 UserDefaults?有哪些替代方案?
IOS开发数据存储:什么是 UserDefaults?有哪些替代方案?
112 0
|
7月前
|
存储 iOS开发 开发者
使用克魔助手进行iOS数据抓包和HTTP抓包的方法详解
使用克魔助手进行iOS数据抓包和HTTP抓包的方法详解
105 0
|
4月前
|
iOS开发 开发者
iOS平台RTMP|RTSP播放器如何实时回调YUV数据
我们在做RTMP、RTSP播放器的时候,有开发者需要自己处理拉取到的YUV数据,做二次分析之用,为此,我们做了以下的设计:InitPlayer之后,再调用SmartPlayerStart()接口之前,设置yuv数据回调即可。
|
7月前
|
存储 数据库 对象存储
IOS的四种数据存储方式及优劣
IOS的四种数据存储方式及优劣
205 1
|
7月前
|
Java iOS开发
iOS的数据序列化(又称持久化)的两类使用方式
iOS的数据序列化(又称持久化)的两类使用方式
70 0
|
7月前
|
移动开发 小程序 API
uniapp通过蓝牙传输数据 (ios)
uniapp通过蓝牙传输数据 (ios)
343 1
|
7月前
|
存储 安全 数据安全/隐私保护
IOS开发数据存储:解释一下 iOS 中的 Keychain,它的作用是什么?
IOS开发数据存储:解释一下 iOS 中的 Keychain,它的作用是什么?
335 4