iOS数据持久化技术

iOS数据持久化技术 - By Dorayo

何为数据持久化

能将内存中的数据模型转换为存储模型,并能在将来需要时将存储模型还原为数据模型的机制

说明:通俗将也就是将数据保存在非易失性设备中,并且能在需要的时候恢复。苹果中也就是从内存->闪存的过程

iOS开发中数据持久化的方法

  • Raw File APIs (C语言文件操作、iOS的NSFileManager)
  • NSUserDefaults
  • 属性列表(PList)
  • 对象归档
  • SQlite3
  • Core Data

Raw File APIs

C语言文件操作

  1. 写入文件

    - (void)saveData
    {
        // 文件路径
        const char *filePath = [_path UTF8String];
    
        // 打开文件
        FILE *fp = fopen(filePath, "w+");
        if (NULL == fp) {
            perror("fopen");
            return;
        }
    
        // 将_textFiled的内容写入文件
        const char *content = [_textField.text UTF8String];
        long size = _textField.text.length;
    
        size_t count = fwrite(content, size, 1, fp);
    
        if (count > 0) {
            NSLog(@"Saved data successfully");
        }
    
        fclose(fp);
    }
    
  2. 读取文件

    - (void)loadData
    {
        // 文件路径
        const char *filePath = [_path UTF8String];
        NSLog(@"%s", filePath);
    
        // 打开文件
        FILE *fp = fopen(filePath, "r");
        if (fp == NULL) {
            perror("fopen");
            return;
        }
    
        // 读取文件到内存
        char buf[BUFSIZE] = {0};
        fseek(fp, 0, SEEK_END);
        long size = ftell(fp);
        rewind(fp);
        fread(buf, size, 1, fp);
    
        // 赋值给_textField
        NSString *str = [NSString stringWithUTF8String:buf];
        if (str != NULL && ![str isEqualToString:@""]) {
            _textField.text = str;
        }
    
        fclose(fp);
    }
    

NSFileManager

  1. 创建文件夹和文件

    // Document目录路径
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = paths[0];
    NSLog(@"documentsDirectory%@",documentsDirectory);
    
    // 创建test目录
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *testDirectory = [documentsDirectory stringByAppendingPathComponent:@"test"];
    [fileManager createDirectoryAtPath:testDirectory withIntermediateDirectories:YES attributes:nil error:nil];
    
    // 初始化文件路径
    self.path = [testDirectory stringByAppendingPathComponent:kTmpFileName];
    NSLog(@"path:%@", _path);
    
    // 创建文件
    NSString *content = nil;
    if (![fileManager fileExistsAtPath:_path]) {
        [fileManager createFileAtPath:_path contents:[content dataUsingEncoding:NSUTF8StringEncoding] attributes:nil];
    }
  2. 写入文件

    - (void)saveData
    {
        NSLog(@"content to save:%@", _textField.text);
        NSError *error;
        [_textField.text writeToFile:_path atomically:YES encoding:NSUTF8StringEncoding error:&error];
        if (error) {
            NSLog(@"%@", error);
            return;
        }
        NSLog(@"Save data successfully");
    }
    
  3. 读取文件

    - (void)loadData
    {
        NSError *error;
    
        NSString *content = [[NSString alloc] initWithContentsOfFile:_path encoding:NSUTF8StringEncoding error:&error];
        if (error) {
            NSLog(@"%@", error);
            return;
        }
        NSLog(@"content:%@", content);
    
        _textField.text = content;
    
    }

NSUserDefaults

  • 直接使用原始的文件操作API,不管是C语言的还是OC的都不太方便

  • Cocoa会为每个app自动创建一个数据库,用来存储App本身的偏好设置,如:开关值,音量值之类的少量信息

  • NSUserDefaults使用时用 [NSUserDefaults standardUserDefaults] 接口获取单例对象

  • NSUserDefaults本质上是以Key-Value形式存成plist文件,放在App的Library/Preferences目录下

  • 这个文件是不安全的,所以千万不要用NSUserDefaults来存储密码之类的敏感信息,用户名密码应该使用KeyChains来存储


  1. 保存数据

    - (IBAction)saveConfig:(id)sender {
    
        NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    
        [userDefaults setBool:self.toggle.on forKey:@"toggle"];
    
        float progress = [self.progressTextField.text floatValue];
        [userDefaults setFloat:progress forKey:@"progress"];
        [userDefaults setObject:self.inputTextField.text forKey:@"input"];
    
        // keeps the in-memory cache in sync with a user’s defaults database
        [userDefaults synchronize];
    }
  2. 读取数据

    - (void)loadConfig
    {
        NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    
        self.toggle.on = [userDefaults boolForKey:@"toggle"];
        self.progressView.progress = [userDefaults floatForKey:@"progress"];
        self.progressTextField.text = [NSString stringWithFormat:@"%.2f", self.progressView.progress];
        self.inputTextField.text = [userDefaults stringForKey:@"input"];
    }
    

说明:对NSUserDefaults单例对象的操作,实质上还是对PList文件(Library/Preferences/\

关于Setting Bundle

  • Setting Bundle的概念更多地应该是在App的配置选择上
  • Setting Bundle可以给用户提供一个从《设置》应用里去配置应用程序的方式
  • 从开发者的角度来看,一般需要频繁修改的配置选项,如游戏的音量和控制选项等最好放到app内部的设置页里,而类似于邮箱应用中的邮件地址和服务器的设置等不需要频繁更改的配置项可以放到Setting Bundle里
  • 从《设置》应用中进行设置,实际上是操作iOS配置系统中的应用程序域(Application Domain),是持久的
  • iOS的配置系统中存在如下一些域,将来查询时严格按照如下列出域的顺序进行查找

    DomainState
    NSArgumentDomainvolatile
    Application (Identified by the app’s identifier)persistent
    NSGlobalDomainpersistent
    Languages (Identified by the language names)volatile
    NSRegistrationDomainvolatile
  • registerDefaults:方法是在NSRegistrationDomain域上进行配置的,所以仅仅是存在于内存中的,易失的

属性列表(PList)

  • NSUserDefaults只能读写Library/Preferences/\

对象归档(NSCoding+NSKeyedArchiver)

  • NSUserDefaults和Plist文件支持常用数据类型,但是不支持自定义的数据对象
  • Cocoa提供了NSCoding和NSKeyArchiver两个工具类,可以把我们自定义的对象编码成二进制数据流,然后存进文件里面

  1. NSCoding协议

    /**
     *  解档
     *
     *  @param aDecoder 解码器
     *
     *  @return 解档之后会生成一个该类的对象
     */
    - (id)initWithCoder:(NSCoder *)aDecoder
    {
        self = [super init];
        if (self) {
            self.name = [aDecoder decodeObjectForKey:kNameKey];
            self.age = [aDecoder decodeIntForKey:kAgeKey];
            self.studyID = [aDecoder decodeObjectForKey:kStudyIDKey];
        }
    
        return self;
    }
    
    /**
     *  归档
     *
     *  @param aCoder 编码器
     */
    - (void)encodeWithCoder:(NSCoder *)aCoder
    {
        [aCoder encodeObject:_name forKey:kNameKey];
        [aCoder encodeInt:_age forKey:kAgeKey];
        [aCoder encodeObject:_studyID forKey:kStudyIDKey];
    }
    
  2. 保存数据

    - (IBAction)saveData:(id)sender {
    
        _student = [[YMStudent alloc] init];
    
        _student.name = _name.text;
        _student.age = [_age.text intValue];
        _student.studyID = _studyID.text;
    
        NSLog(@"%@", _student);
    
        if ([NSKeyedArchiver archiveRootObject:_student toFile:_path]) {
            NSLog(@"Archive successfully!");
        }
    }
  3. 读取数据

    - (void)loadData
    {
        NSLog(@"%@", _path);
        NSFileManager *fileManager = [NSFileManager defaultManager];
        if (![fileManager fileExistsAtPath:_path]) {
            NSLog(@"Student.data does not exsist!");
            return;
        }
        _student = [NSKeyedUnarchiver unarchiveObjectWithFile:_path];
    
        if (_student == nil) {
            NSLog(@"No data!");
            return;
        }
    
        NSLog(@"Unarchive successfully!");
        _name.text = _student.name;
        _age.text = [@(_student.age) stringValue];
        _studyID.text = _student.studyID;
    }

Sqlite3

  1. 创建数据库引擎单例

    + (YMSqlDBManager *)shareInstance
    {
        static dispatch_once_t once;
        dispatch_once(&once, ^{
            _dbManager = [[self alloc] init];
        });
        return _dbManager;
    }
  2. 打开数据库

    - (BOOL)openDB
    {
        if (_dbSqlite) {
            return YES;
        }
    
        // 数据库路径
        NSString *dbPath = [[self documentPath] stringByAppendingPathComponent:kDBFileName];
    
    
        int result = sqlite3_open([dbPath UTF8String], &_dbSqlite);
        if (result != SQLITE_OK) {
            NSLog(@"%s", sqlite3_errmsg(_dbSqlite));
            return NO;
        }
    
        NSLog(@"Open database ok");
        return YES;
    }
    
  3. 关闭数据库

    - (BOOL)closeDB
    {
        if (sqlite3_close(_dbSqlite) != SQLITE_OK) {
            NSLog(@"Close database failed!");
            return NO;
        }
        _dbSqlite = nil;
        return YES;
    }
  4. 创建学生记录表

    - (BOOL)createTable 
    {
        // 1. 打开数据库
        [self openDB];
    
        // 2. 编写SQL
        NSString *sql = @"CREATE TABLE IF NOT EXSIST student(number INTEGER PRIMARY KEY, name TEXT NOT NULL, age INTEGER, sex TEXT, icon BLOB)";
    
        // 3. 执行SQL语句
        char *errmsg;
        int result = sqlite3_exec(_dbSqlite, [sql UTF8String], NULL, NULL, &errmsg);
        if (result != SQLITE_OK) {
            NSLog(@"%s", errmsg);
            return NO;
        }
    
        NSLog(@"Create student table ok");
    
        // 4. 关闭数据库
        [self closeDB];
        return YES;
    }
  5. 插入学生记录

    - (BOOL)insertStudent:(YMStudentModel *)student
    {
        // 1. 打开数据库
        [self openDB];
    
        // 2. 编写SQL
        NSString *sql = @"INSERT INTO student(number, name, age, sex, icon) VALUES (?, ?, ?, ?, ?)";
    
        //3. 编译sql语句
        sqlite3_stmt *stmt;
        int result = sqlite3_prepare_v2(_dbSqlite, [sql UTF8String], -1, &stmt, NULL);
        if (result != SQLITE_OK) {
            NSLog(@"Error: sqlite3_prepare_v2");
            return NO;
        }
    
        // 4. 绑定sql语句中的参数 即:替换掉问号
        sqlite3_bind_int(stmt, 1, student.number);
        sqlite3_bind_text(stmt, 2, [student.name UTF8String], -1, NULL);
        sqlite3_bind_int(stmt, 3, student.age);
        sqlite3_bind_text(stmt, 4, [student.sex UTF8String], -1, NULL);
        sqlite3_bind_blob(stmt, 5, [UIImagePNGRepresentation(student.icon) bytes], (int)UIImagePNGRepresentation(student.icon).length, NULL);
    
    
        // 5. 执行
        sqlite3_step(stmt);
    
        // 6. 释放预编译语句对象
        sqlite3_finalize(stmt);
    
        // 7. 关闭数据库
        [self closeDB];
    
        NSLog(@"Insert ok");
        return YES;
    }
    
  6. 删除学生记录

    - (BOOL)deleteStudentByNumber:(int)number
    {
        // 1. 打开数据库
        [self openDB];
    
        // 2. 编写SQL
        NSString *sql = @"DELETE FROM student WHERE number = ?";
    
        //3. 编译sql语句
        sqlite3_stmt *stmt;
        int result = sqlite3_prepare_v2(_dbSqlite, [sql UTF8String], -1, &stmt, NULL);
        if (result != SQLITE_OK) {
            NSLog(@"Error: sqlite3_prepare_v2");
            return NO;
        }
    
        // 4. 绑定sql语句中的参数 即:替换掉问好
        sqlite3_bind_int(stmt, 1, number);
    
        // 5. 执行
        sqlite3_step(stmt);
    
        // 6. 释放预编译语句对象
        sqlite3_finalize(stmt);
    
        // 7. 关闭数据库
        [self closeDB];
    
        NSLog(@"Delete ok");
        return YES;
    }
    
  7. 更新学生记录

    - (BOOL)updateStudent:(YMStudentModel *)student ByNumber:(int)number
    {
        // 1. 打开数据库
        [self openDB];
    
        // 2. 编写SQL
        NSString *sql = @"UPDATE student SET name = ?, age = ?, icon = ? WHERE number = ?";
    
        //3. 编译sql语句
        sqlite3_stmt *stmt;
        int result = sqlite3_prepare_v2(_dbSqlite, [sql UTF8String], -1, &stmt, NULL);
        if (result != SQLITE_OK) {
            NSLog(@"Error: sqlite3_prepare_v2");
            return NO;
        }
    
        // 4. 绑定sql语句中的参数 即:替换掉问好
        sqlite3_bind_text(stmt, 1, [student.name UTF8String], -1, NULL);
        sqlite3_bind_int(stmt, 2, student.age);
        sqlite3_bind_blob(stmt, 3, [UIImagePNGRepresentation(student.icon) bytes], (int)UIImagePNGRepresentation(student.icon).length, NULL);
        sqlite3_bind_int(stmt, 4, student.number);
    
    
        // 5. 执行
        sqlite3_step(stmt);
    
        // 6. 释放预编译语句对象
        sqlite3_finalize(stmt);
    
        // 7. 关闭数据库
        [self closeDB];
    
        NSLog(@"Update ok");
        return YES;
    }
    
  8. 查询所有数据

    从上一步的step的结果中提取出模型对象

    - (YMStudentModel *)extractModelFrom:(sqlite3_stmt *)stmt
    {
        // 1 提取学号
        int number = sqlite3_column_int(stmt, 0);
    
        // 2 提取姓名
        const unsigned char *name = sqlite3_column_text(stmt, 1);
    
        NSString *nameStr;
        if (name != NULL) {
            nameStr = [NSString stringWithCString:(const char *)name encoding:NSUTF8StringEncoding];
        }
    
        // 3 提取年龄
        int age = sqlite3_column_int(stmt, 2);
    
        // 4 提取性别
        const unsigned char *sex = sqlite3_column_text(stmt, 3);
    
        NSString *sexStr;
        if (sex != NULL) {
            sexStr = [NSString stringWithCString:(const char *)sex encoding:NSUTF8StringEncoding];
        }
    
        // 5 提取头像
        const void *iconData = sqlite3_column_blob(stmt, 4);
        int iconLength = sqlite3_column_bytes(stmt, 4);
        UIImage *icon = [UIImage imageWithData:[NSData dataWithBytes:iconData length:iconLength]];
    
        YMStudentModel *model = [[YMStudentModel alloc] init];
        model.number = number;
        model.name = nameStr;
        model.age = age;
        model.sex = sexStr;
        model.icon = icon;
    
        return model;
    }
    
    
    - (NSArray *)selectAllData
    {
        // 1. 打开数据库
        [self openDB];
    
        // 2. 编写SQL
        NSString *sql = @"SELECT * from student";
    
        // 3. 编译SQL语句
        sqlite3_stmt *stmt;
        int result = sqlite3_prepare_v2(_dbSqlite, [sql UTF8String], -1, &stmt, NULL);
        if (result != SQLITE_OK) {
            NSLog(@"Error: sqlite3_prepare_v2");
            return nil;
        }
    
        // 4. 执行SQL语句
        NSMutableArray *array = [NSMutableArray array];
        while (sqlite3_step(stmt) == SQLITE_ROW) {
            YMStudentModel *model;
            // 5. 按列提取执行结果
            model = [self extractModelFrom:stmt];
            if (model) {
                [array addObject:model];
            }
        }
    
        // 6. 释放预编译语句对象
        sqlite3_finalize(stmt);
    
        // 7. 关闭数据库
        [self closeDB];
    
        // 8. 返回结果
        return array;
    }
  9. 根据学号查询指定记录

    - (YMStudentModel *)selectStudentByNumber:(int)number
    {
        // 1. 打开数据库
        [self openDB];
    
        // 2. 编写SQL
        NSString *sql = @"SELECT * FROM student WHERE number = ?";
    
        // 3. 编译SQL语句
        sqlite3_stmt *stmt;
        int result = sqlite3_prepare_v2(_dbSqlite, [sql UTF8String], -1, &stmt, NULL);
        if (result != SQLITE_OK) {
            NSLog(@"Error: sqlite3_prepare_v2");
            return nil;
        }
    
        // 4. 执行SQL语句
        sqlite3_bind_int(stmt, 1, number);
        YMStudentModel *model;
        if (sqlite3_step(stmt) == SQLITE_ROW) {
            // 5. 按列提取执行结果
            model = [self extractModelFrom:stmt];
        }
    
        // 6. 释放预编译语句对象
        sqlite3_finalize(stmt);
    
        // 7. 关闭数据库
        [self closeDB];
    
        // 8. 返回结果
        return model;
    }
    
  10. 根据学生姓名查询指定记录

    - (NSArray *)selectStudentsByName:(NSString *)name
    {
        // 1. 打开数据库
        [self openDB];
    
        // 2. 编写SQL
        NSString *sql = @"SELECT name FROM student WHERE name = ?";
    
        // 3. 编译SQL语句
        sqlite3_stmt *stmt;
        int result = sqlite3_prepare_v2(_dbSqlite, [sql UTF8String], -1, &stmt, NULL);
        if (result != SQLITE_OK) {
            NSLog(@"Error: sqlite3_prepare_v2");
            return nil;
        }
    
        // 4. 执行SQL语句
        sqlite3_bind_text(stmt, 1, [name UTF8String], -1, NULL);
    
        YMStudentModel *model;
        NSMutableArray *array = [NSMutableArray array];
    
        while (sqlite3_step(stmt) == SQLITE_ROW) {
            // 5. 按列提取执行结果
            model = [self extractModelFrom:stmt];
            if (model) {
                [array addObject:model];
            }
        }
    
        // 6. 释放预编译语句对象
        sqlite3_finalize(stmt);
    
        // 7. 关闭数据库
        [self closeDB];
    
        // 8. 返回结果
        return array;
    
    }
    
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
GeoPandas是一个开源的Python库,旨在简化地理空间数据的处理和分析。它结合了Pandas和Shapely的能力,为Python用户提供了一个强大而灵活的工具来处理地理空间数据。以下是关于GeoPandas的详细介绍: 一、GeoPandas的基本概念 1. 定义 GeoPandas是建立在Pandas和Shapely之上的一个Python库,用于处理和分析地理空间数据。 它扩展了Pandas的DataFrame和Series数据结构,允许在其中存储和操作地理空间几何图形。 2. 核心数据结构 GeoDataFrame:GeoPandas的核心数据结构,是Pandas DataFrame的扩展。它包含一个或多个列,其中至少一列是几何列(geometry column),用于存储地理空间几何图形(如点、线、多边形等)。 GeoSeries:GeoPandas中的另一个重要数据结构,类似于Pandas的Series,但用于存储几何图形序列。 二、GeoPandas的功能特性 1. 读取和写入多种地理空间数据格式 GeoPandas支持读取和写入多种常见的地理空间数据格式,包括Shapefile、GeoJSON、PostGIS、KML等。这使得用户可以轻松地从各种数据源中加载地理空间数据,并将处理后的数据保存为所需的格式。 2. 地理空间几何图形的创建、编辑和分析 GeoPandas允许用户创建、编辑和分析地理空间几何图形,包括点、线、多边形等。它提供了丰富的空间操作函数,如缓冲区分析、交集、并集、差集等,使得用户可以方便地进行地理空间数据分析。 3. 数据可视化 GeoPandas内置了数据可视化功能,可以绘制地理空间数据的地图。用户可以使用matplotlib等库来进一步定制地图的样式和布局。 4. 空间连接和空间索引 GeoPandas支持空间连接操作,可以将两个GeoDataFrame按照空间关系(如相交、包含等)进行连接。此外,它还支持空间索引,可以提高地理空间数据查询的效率。
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同时保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作时执行额外的逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值