美文网首页
iOS底层系列28 -- 数据存储

iOS底层系列28 -- 数据存储

作者: YanZi_33 | 来源:发表于2021-11-09 09:03 被阅读0次

应用沙盒

  • 每一个 IOS 应用都是单独运行在一个沙盒内部的,以保证不会篡改或者读取其他 APP 的信息;
  • 常见的沙盒文件夹有如下:
    • Documents:应用程序在运行时生成的一些需要长久保存的重要数据放在此文件中;
    • Library:内部有CachesPreferences子文件夹;
      • Caches:存放缓存文件,比如从网络上下载的数据,一般用来保存应用需要长期使用的,数据量大,不需要备份的非重要数据;
      • Preferences:保存应用的所有偏好设置,比如账号,设置等,由系统自动管理;
    • tmp:临时文件,系统会自动清理,重新启动或系统磁盘空间不足时,会自动清理;

应用沙盒的访问

  • 案例代码如下:
- (void)viewDidLoad {
    [super viewDidLoad];
    
    //沙盒根目录
    NSString *home = NSHomeDirectory();
    
    //Documents路径
    NSString *documents1 = [home stringByAppendingPathComponent:@"Documents"];
    NSString *documents2 = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    
    //Library路径
    NSString *library1 = [home stringByAppendingPathComponent:@"Library"];
    NSString *library2 = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) firstObject];
    
    //Caches路径
    NSString *caches1 = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
    NSString *caches2 = [library2 stringByAppendingPathComponent:@"Caches"];
    //Preferences路径
    NSString *preferences = [library2 stringByAppendingPathComponent:@"Preferences"];
    
    //tmp路径
    NSString *tmp1 = NSTemporaryDirectory();
    NSString *tmp2 = [home stringByAppendingPathComponent:@"tmp"];
    
    NSLog(@"home = %@",home);
    NSLog(@"documents1 = %@",documents1);
    NSLog(@"documents2 = %@",documents2);
    NSLog(@"library1 = %@",library1);
    NSLog(@"library2 = %@",library2);
    NSLog(@"caches1 = %@",caches1);
    NSLog(@"caches2 = %@",caches2);
    NSLog(@"preferences = %@",preferences);
    NSLog(@"tmp1 = %@",tmp1);
    NSLog(@"tmp2 = %@",tmp2);
}
  • 沙盒路径的获取可采用字符串拼接或者调用NSSearchPathForDirectoriesInDomains方法获取,建议使用NSSearchPathForDirectoriesInDomains
  • iOS中数据持久化存储的方式有:Plist属性列表NSUserDefaults偏好设置NSKeyedArchiver/NSKeyedUnarchiver(归档/解裆)SQLite3FMDBCore Data
Plist属性列表
  • Plist:属性列表是一种XML格式的文件,拓展名为plist,按照key:value键值对的形式进行数据存储;
- (void)viewDidLoad {
    [super viewDidLoad];
    //数据 --> 写入plist文件
    NSString *documents = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    NSString *targetPath = [documents stringByAppendingPathComponent:@"student.plist"];
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    [dict setObject:@"张三" forKey:@"name"];
    [dict setObject:@"155xxxxxxx" forKey:@"phone"];
    [dict setObject:@"27" forKey:@"age"];
    [dict setObject:@"175.2" forKey:@"height"];
    //将字典持久化到Documents/student.plist文件中
    [dict writeToFile:targetPath atomically:YES];
    
    //plist文件 --> 读取数据
    NSDictionary *userDic = [NSDictionary dictionaryWithContentsOfFile:targetPath];
    NSLog(@"name= %@",userDic[@"name"]);
    NSLog(@"phone= %@",userDic[@"phone"]);
    NSLog(@"age= %@",userDic[@"age"]);
    NSLog(@"height= %@",userDic[@"height"]);
   
    //从mainBundle中读取plist文件
    NSString *userPath = [NSBundle.mainBundle pathForResource:@"UserInfo" ofType:@"plist"];
    NSDictionary *userData = [NSDictionary dictionaryWithContentsOfFile:userPath];
    NSLog(@"name = %@",userData[@"name"]);
    NSLog(@"password = %@",userData[@"password"]);
    NSLog(@"age = %@",userData[@"age"]);
    NSLog(@"height = %@",userData[@"height"]);
}
image.png image.png
  • plist 文件读取需要使用一个 NSDictionary 进行接收了;
NSUserDefaults偏好设置
  • 其主要用来存储轻量型数据,比如用户信息账号密码,程序配置等等,以便下次启动程序后能恢复上次的设置;
  • NSUserDefaults设置数据时,不是立即写入,而是根据时间戳定时地把缓存中的数据写入本地磁盘,所以调用了set方法之后数据有可能还没有写入磁盘应用程序就终止了,出现以上问题,可以通过调用synchornize方法[defaults synchornize],强制立即写入;
  • 案例代码一:
- (void)viewDidLoad {
    [super viewDidLoad];
    //数据存储 --> NSUserDefaults
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setObject:@"张三" forKey:@"username"];
    [defaults setFloat:18.0f forKey:@"text_size"];
    [defaults setBool:YES forKey:@"auto_login"];
    [defaults synchronize];
    
    //NSUserDefaults --> 数据读取
    NSString *username = [defaults stringForKey:@"username"];
    float textSize = [defaults floatForKey:@"text_size"];
    BOOL autoLogin = [defaults boolForKey:@"auto_login"];

    NSLog(@"username = %@",username);
    NSLog(@"text_size = %f",textSize);
  • NSUserDefaults支持存储的数据类型有:NSNumber(NSInteger、float、double),NSString,NSDate,NSArray,NSDictionary,BOOL;
  • 案例代码二:
- (void)viewDidLoad {
    [super viewDidLoad];
    
    //存储单个对象
    YYPerson *person = [[YYPerson alloc]init];
    person.name = @"yanzi";
    person.age = 10;
    person.height = 128.5;
    person.sex = false;
    
    NSData *data_origin = [NSKeyedArchiver archivedDataWithRootObject:person];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setObject:data_origin forKey:@"person"];
    
    //读取单个对象
    NSData *data_read = [defaults objectForKey:@"person"];
    YYPerson *p = [NSKeyedUnarchiver unarchiveObjectWithData:data_read];
    NSLog(@"name = %@",p.name);
    NSLog(@"age = %ld",p.age);
    NSLog(@"height = %f",p.height);
    NSLog(@"sex = %d",p.sex);
    
    //存储对象数组
    NSMutableArray *students = [[NSMutableArray alloc]init];
    for (int i = 0; i < 10; i++) {
        YYPerson *p = [[YYPerson alloc]init];
        p.name = [NSString stringWithFormat:@"name_%d",i];
        NSData *data = [NSKeyedArchiver archivedDataWithRootObject:p];
        //数组中存储的是data
        [students addObject:data];
    }
    
    //转成不可变数组
    NSArray *arr_origin = [NSArray arrayWithArray:students];
    [defaults setObject:arr_origin forKey:@"AllStudents"];
    
    //读取对象数组
    NSArray *arr_read = [defaults objectForKey:@"AllStudents"];
    for (NSData *data in arr_read) {
        YYPerson *p = [NSKeyedUnarchiver unarchiveObjectWithData:data];
        NSLog(@"name = %@",p.name);
    }
}
  • 自定义对象若使用NSUserDefaults进行存储,必须遵循NSCoding协议,转成NSData对象,然后才可以存储,其实本质还是存储NSData;
NSKeyedArchiver/NSKeyedUnarchiver(归档/解裆)
  • NSKeyedArchiver/NSKeyedUnarchiver支持NSArray,NSString,NSInteger,NSDictionary 等 iOS 自带的数据类型的存储与读取;
  • NSKeyedArchiver/NSKeyedUnarchiver也支持自定义对象的存储与读取,但自定义对象必须遵循NSCoding协议,并实现encodeWithCoderinitWithCoder方法;
    • encodeWithCoder: 每次归档对象时,都会调用这个方法,一般在这个方法里面指定如何归档对象中的每个实例变量;
    • initWithCoder:每次从文件中恢复(解码)对象时,都会调用这个方法,一般在这个方法里面指定如何解码文件中的数据为对象的实例变量;
  • 案例代码一:
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSString *str_origin = @"asd123";
    //归档NSString
    NSData *data_origin = [NSKeyedArchiver archivedDataWithRootObject:str_origin];
    //解裆NSString
    NSString *str_read = [NSKeyedUnarchiver unarchiveObjectWithData:data_origin];
    
    NSLog(@"str_read = %@",str_read);
}
  • 案例代码二:
#import <Foundation/Foundation.h>

@interface YYPerson : NSObject<NSCoding>

@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)NSInteger age;
@property(nonatomic,assign)float height;
@property(nonatomic,assign)BOOL sex;

@end
#import "YYPerson.h"

@implementation YYPerson

- (void)encodeWithCoder:(NSCoder *)coder{
    [coder encodeObject:self.name forKey:@"name"];
    [coder encodeFloat:self.height forKey:@"height"];
    [coder encodeInteger:self.age forKey:@"age"];
    [coder encodeBool:self.sex forKey:@"sex"];
}

- (instancetype)initWithCoder:(NSCoder *)coder {
    self = [super init];
    if (self) {
        self.name = [coder decodeObjectForKey:@"name"];
        self.height = [coder decodeFloatForKey:@"height"];
        self.age = [coder decodeIntegerForKey:@"age"];
        self.sex = [coder decodeBoolForKey:@"sex"];
    }
    return self;
}

@end
- (void)viewDidLoad {
    [super viewDidLoad];
    
    YYPerson *person = [[YYPerson alloc]init];
    person.name = @"yanzi";
    person.age = 10;
    person.height = 128.5;
    person.sex = false;
    
    //归档
    NSString *documents = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    NSString *path = [documents stringByAppendingPathComponent:@"person.archiver"];
    [NSKeyedArchiver archiveRootObject:person toFile:path];
    
    //解裆
    YYPerson *object = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
    NSLog(@"解归档成功 name = %@",object.name);
}
FMDB
  • FMDB 是 iOS 平台的 SQLite 数据库框架;
  • FMDB 以 OC 的方式封装了 SQLite 的 C 语言 API;
  • 使用起来更加面向对象,省去了很多麻烦、冗余的 C 语言代码;
  • 提供了多线程安全的数据库操作方法,有效地防止数据混乱;
  • 案例代码:
@interface YYBookDB : NSObject

/**
 构造方法
 @return 阅读器数据库单例对象
 */
+ (YYBookDB *)shareInstance;

/**
 初始化数据库
 */
- (void)initDataBase;

/**
 书本的插入
 @param bookId 书本id
 @param bookName 书名
 */
- (void)FMInsertBookWithBookId:(NSString*)bookId bookName:(NSString*)bookName bookPic:(NSString*)bookPic;

/**
 根据bookId去本地数据库表中查找目标书本
 */
- (YYBookModel*)selectBookWithBookId:(NSString*)bookId;

//更新
- (void)updateBook:(NSString *)bookId IsDisplayReadSign:(NSString *)displayReadSign;

//批量删除
- (void)bookcase_deleteAllBook;

//批量插入
- (void)bookcase_batchInsertBookArr:(NSArray *)bookArr;
#import "YYBookDB.h"

@interface YYBookDB()

/**
 FMDB数据库
 */
@property(nonatomic,strong)FMDatabase *dataBase;

@end

@implementation YYBookDB

static YYBookDB *bookDB;

+ (YYBookDB *)shareInstance{
    if (bookDB == nil) {
        bookDB = [[self alloc]init];
    }
    return bookDB;
}

+ (id)allocWithZone:(struct _NSZone *)zone{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        bookDB = [super allocWithZone:zone];
    });
    return bookDB;
}

/**
 初始化数据库并创建相应的表
 */
- (void)initDataBase{
    NSString *path = [DocumentPath stringByAppendingPathComponent:@"book.sqlite"];
    self.dataBase = [FMDatabase databaseWithPath:path];
    if ([self.dataBase open]) {
        NSLog(@"数据库打开成功");
        //书本模型表
        NSString *createBookTable = @"create table if not exists t_book(id integer primary key autoincrement,bookId text,bookName text,bookPic text,currentChapterName text,isDisplayReadSign text)";
       //数据库表字段 新增
       if (![self.dataBase columnExists:@"bookType" inTableWithName:@"t_book"]) {
            NSString *alter_bookType = [NSString stringWithFormat:@"alter table %@ add %@",@"t_book",@"bookType"];
            chapterResult = [self.dataBase executeUpdate: alter_bookType];
        }
        [self.dataBase close];
    }else{
         NSLog(@"数据库打开失败");
    }
}

/**
 单个书本的插入
 */
- (void)FMInsertBookWithBookId:(NSString*)bookId bookName:(NSString*)bookName bookPic:(NSString*)bookPic{
    if (bookId) {
        [self.dataBase open];
        if (![self isExistWithBookId:bookId]) {
            //插入书本数据
            NSString *insertBook = [NSString stringWithFormat:@"insert into t_book(bookId,bookName,bookPic)values('%@','%@','%@');",bookId,bookName,bookPic];
            BOOL insertBookResult = [self.dataBase executeUpdate:insertBook];
            if (insertBookResult) {
                NSLog(@"书本 -- %@ 缓存成功",bookName);
            }else{
                NSLog(@"书本 -- %@ 缓存失败",bookName);
            }
        }
        [self.dataBase close];
    }else{
        NSLog(@"缺少bookId");
    }
}

/**
 根据bookId去本地数据库表中查找目标书本
 */
- (YYBookModel*)selectBookWithBookId:(NSString*)bookId{
    [self.dataBase open];
    //查找目标书本
    NSString *bookSelect = [NSString stringWithFormat:@"select * from t_book where bookId = %@",bookId];
    FMResultSet *bookResultSet = [self.dataBase executeQuery:bookSelect];
    
    NSMutableArray *bookArr = [[NSMutableArray alloc]init];
    while ([bookResultSet next]) {
        //数据转模型
        YYBookModel *bModel = [[YYBookModel alloc]init];
        bModel.bookId = [bookResultSet objectForColumn:@"bookId"];
        bModel.bookName = [bookResultSet objectForColumn:@"bookName"];
        [bookArr addObject:bModel];
    }
    //关闭数据库
    [self.dataBase close];
    return targetBookModel;
}

//更新
- (void)updateBook:(NSString *)bookId IsDisplayReadSign:(NSString *)displayReadSign{
    [self.dataBase open];
    NSString *updateBookDisplayReadSign = [NSString stringWithFormat:@"update t_book set isDisplayReadSign = '%@' where bookId = '%@'",[NSString stringWithFormat:@"%@",displayReadSign],[NSString stringWithFormat:@"%@",bookId]];
    BOOL updateResult = [self.dataBase executeUpdate:updateBookDisplayReadSign];
    if (updateResult) {
        NSLog(@"显示阅读足迹设置成功");
    }else{
        NSLog(@"显示阅读足迹设置失败");
    }
    [self.dataBase close];
}

//批量删除
- (void)bookcase_deleteAllBook{
    [self.dataBase open];
    // 清除地址表中的所有数据
    NSString *delete = [NSString stringWithFormat:@"delete from t_book"];
    // 将表中的自增字段归零 归零非常重要 表格名称必须要加单引号
    NSString *delete1 = @"delete from sqlite_sequence where name = 't_book'";
    
    BOOL deleteResult = [_dataBase executeUpdate:delete];
    if (deleteResult) {
        NSLog(@"所有书本清除成功");
    }else{
        NSLog(@"所有书本清除失败");
    }
    
    BOOL delete1Result = [_dataBase executeUpdate:delete1];
    if (delete1Result) {
        NSLog(@"书本表自增列清零成功");
    }else{
        NSLog(@"书本表自增列清零失败");
    }
    [self.dataBase close];
}

//批量插入
- (void)bookcase_batchInsertBookArr:(NSArray *)bookArr{
    //批量插入目录数据
    [self.dataBase open];
    //使用事务批量插入数据 提高效率
    [self.dataBase beginTransaction];
    BOOL isRollBack = NO;
    
    @try {
        for (int i = 0; i < bookArr.count; i++) {
            BookModel *book = bookArr[i];
            //bookId,bookName,bookPic,readStatus,whetherUpdate,addCaseTime,freeStatus
            NSInteger time = [[NSDate getNowTimeTimestamp3] integerValue];
            NSString *insertCaseBook = [NSString stringWithFormat:@"insert into t_book(bookId,bookName,bookPic,readStatus,whetherUpdate,addCaseTime,freeStatus) values ('%@','%@','%@','%@','%@','%ld','%@');",[NSString stringWithFormat:@"%@",book.bookId],book.bookName,book.bookPic,book.readStatus,[NSString stringWithFormat:@"%ld",book.whetherUpdate],time,[NSString stringWithFormat:@"%ld",book.freeStatus]];
            BOOL insertCaseBookResult = [self.dataBase executeUpdate:insertCaseBook];
            if (insertCaseBookResult) {
                NSLog(@"书本 -- %@ 缓存成功",book.bookName);
            }else{
                NSLog(@"书本 -- %@ 缓存失败",book.bookName);
            }
        }
    }
    @catch (NSException *exception) {
        isRollBack = YES;
        [self.dataBase rollback];
    }
    @finally {
        if (!isRollBack) {
            [self.dataBase commit];
        }
    }
    [self.dataBase close];
}

- (BOOL)isExistWithBookId:(NSString *)bookId{
    NSString *select_book = [NSString stringWithFormat:@"select * from t_book where bookId = '%@'",[NSString stringWithFormat:@"%@",bookId]];
    FMResultSet *select_result = [self.dataBase executeQuery:select_book];
    
    while ([select_result next]) {
        NSString *select_bookid = [select_result objectForColumn:@"bookid"];
        if ([select_bookid isEqualToString:bookId]) {
            return YES;
        }
    }
    return NO;
}
  • 创建一个存储书本的单例了YYBookDB,内部使用FMDB进行数据的存储;
  • 创表:create table if not exists t_book(id integer primary key autoincrement,bookId text,bookName text,bookPic text,currentChapterName text,isDisplayReadSign text)
  • 插入:insert into t_book(bookId,bookName,bookPic)values('%@','%@','%@')
  • 删除:delete from sqlite_sequence where name = 't_book' 同时将自增列表清零delete from sqlite_sequence where name = 't_book'
  • 更新:update t_book set isDisplayReadSign = '%@' where bookId = '%@'
  • 查找:select * from t_book where bookId = %@
  • 注意⚠️:单引号的使用
  • 对于批量操作,可使用事务,提高执行效率;

相关文章

网友评论

      本文标题:iOS底层系列28 -- 数据存储

      本文链接:https://www.haomeiwen.com/subject/dmluzltx.html