本地化存储

SQL基本操作

1.create 和 drop
  • create table 表名 (字段名1 字段类型1, 字段名2 字段类型2, …) ;

  • create table t_student (id integer, name text, age integer, score real) ;

  • create table if not exists 表名 (字段名1 字段类型1, 字段名2 字段类型2, …) ;

2.增、改、删
insert
  • insert into 表名 (字段1, 字段2, …) values (字段1的值, 字段2的值, …) ;

  • insert into t_student (name, age) values (‘mj’, 10) ;

update
  • update 表名 set 字段1 = 字段1的值, 字段2 = 字段2的值, … ;

  • update t_student set name = ‘jack’, age = 20 ;

delete
  • delete from 表名 ;

  • delete from t_student ;

数据类型

SQLite将数据划分为以下几种存储类型:

  • integer : 整型值

  • real : 浮点值

  • text : 文本字符串

  • blob : 二进制数据(比如文件)

  • SQLite是无类型的,就算声明为integer类型,还是能存储字符串文本(主键除外)。
    建表时声明啥类型或者不声明类型都可以,也就意味着创表语句可以这么写:

    create table t_student(name, age);

  • 数据库中的字符串内容应该用单引号 ’ 括住

条件语句

where
  • where 字段 = 某个值 ; // 不能用两个 =

  • where 字段 is 某个值 ; // is 相当于 =

  • where 字段 != 某个值 ;

  • where 字段 is not 某个值 ; // is not 相当于 !=

  • where 字段 > 某个值 ;

  • where 字段1 = 某个值 and 字段2 > 某个值 ; // and相当于C语言中的 &&

  • where 字段1 = 某个值 or 字段2 = 某个值 ; // or 相当于C语言中的 ||

例子:

(1)将t_student表中年龄大于10 并且 姓名不等于jack的记录,年龄都改为 5

update t_student set age = 5 where age > 10 and name != ‘jack’ ;

(2)删除t_student表中年龄小于等于10 或者 年龄大于30的记录

delete from t_student where age <= 10 or age > 30 ;

(3)将t_student表中名字等于jack的记录,score字段的值 都改为 age字段的

update t_student set score = age where name = ‘jack’ ;
select
  • select 字段1, 字段2, … from 表名 ;

      select name, age from t_student ;
    
      select * from t_student where age > 10 ;  //  条件查询
    
  • select 字段1 别名 , 字段2 别名 , … from 表名 别名 ;

      select name myname, age myage from t_student s;
    
      select s.myname, s.myage from t_student s ;
    
  • 计算记录的数量

    select count (字段) from 表名 ;

      select count (age) from t_student ;
    

    select count ( * ) from 表名 ;

      select count ( * ) from t_student where score >= 60;
    
  • 排序

    select * from t_student order by 字段 ;

      select * from t_student order by age ;
    
      select * from t_student order by age desc ;  //降序
      
      select * from t_student order by age asc ;   // 升序(默认)
    

    先按照年龄排序(升序),年龄相等就按照身高排序(降序)

      select * from t_student order by age asc, height desc ;
    
limit(精准查询)
  • select * from 表名 limit 数值1, 数值2 ;

     select * from t_student limit 4, 8 ;//跳过最前面4条语句,然后取8条记录
     
     select * from t_student limit 7 ;//这条语句的作用相当于select * from t_student limit 0, 7 ;表示取最前面的7条记录
    
  • limit常用来做分页查询,比如每页固定显示5条数据,那么应该这样取数据

    第1页:limit 0, 5

    第2页:limit 5, 5

    第3页:limit 10, 5

    第n页:limit 5*(n-1), 5

约束

简单约束
  • not null :规定字段的值不能为null

  • unique :规定字段的值必须唯一

  • default :指定字段的默认值

      create table t_student (id integer, name text not null unique, age integer not null default 1) ;//name字段不能为null,并且唯一;age字段不能为null,并且默认为1
    
主键约束
  • 主键:每张表里都需要有一个字段,来保证数据的唯一性;

  • 主键可以是一个字段或多个字段;

  • 主键应当是对用户没有意义的

  • 永远也不要更新主键

  • 主键不应包含动态变化的数据

  • 主键应当由计算机自动生成

  • 声明主键

      create table t_student (id integer primary key autoincrement, name text, age integer) ;//primary key,就说明是一个主键字段;如果想要让主键自动增长(必须是integer类型),应该增加autoincrement
    
外键约束
  • 外键:一张表的某个字段,引用着另一张表的主键字段,用来建立表与表之间的联系。

      create table t_student (id integer primary key autoincrement, name text, age integer, class_id integer, constraint fk_student_class foreign key (class_id) references t_class (id));//t_student表中有一个叫做fk_t_student_class的外键; 这个外键的作用是用t_student表中的class_id字段引用t_class表的id字段
    
表连接查询
  • 表连接:需要联合多张表才能查询到数据。

  • 内连接:inner join 或者 join (显示的是左右表都有完整字段值的记录)

  • 左外连接:left outer join (保证左表数据的完整性)

      select s.name,s.age from t_student s, t_class c where s.class_id = c.id and c.name = ‘0316iOS’;//查询0316iOS班的所有学生的姓名和年龄
    

FMDB

主要的类
  • FMDatabase – 表示一个单独的SQLite数据库。 用来执行SQLite的命令;

  • FMResultSet – 表示FMDatabase执行查询后结果集;

  • FMDatabaseQueue – 如果你想在多线程中执行多个查询或更新,你应该使用该类。这是线程安全的;

创建数据库
FMDatabase *db = [FMDatabase databaseWithPath:@"/tmp/tmp.db"];
  • 该文件路径无需真实存,如果不存在会自动创建;

  • 空字符串(@”")。表示会在临时目录创建一个空的数据库,当FMDatabase 链接关闭时,文件也被删除;

  • NULL. 将创建一个内在数据库。同样的,当FMDatabase连接关闭时,数据会被销毁;

打开数据库

与数据库交互前,必须先打开数据库,如果资源或权限不足,将会打开失败。

if (![db open]) {    
        [db release];   
        return;    
    }  
执行更新

一切不是select的命令都是更新命令。执行更新会返回一个BOOL值。YES表示执行成功,否则有错误。相关方法:

  • -lastErrorMessage

  • -lastErrorCode

使用executeUpdate:执行更新

-(BOOL)executeUpdate:(NSString*)sql, ...

-(BOOL)executeUpdateWithFormat:(NSString*)format, ...

-(BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments

示例:

[db executeUpdate:@"UPDATE t_student SET age = ? WHERE name = ?;", @20, @"Jack"]

[db executeUpdateWithFormat:@"INSERT INTO t_shop(name, price) VALUES (%@, %f);", shop.name, shop.price];

注意:参数必须是对象类型。比如要把int型变成NSNumber对象;

SQL中使用%要用%%;

执行查询

select命令,执行方法是以-excuteQuery开头的。

  • 成功返回FMResultSet对象, 错误返回nil;

  • 支持使用NSError**参数

  • -lastErrorMessage

  • -lastErrorCode

使用executeQuery:执行更新

-(FMResultSet *)executeQuery:(NSString*)sql, ...

-(FMResultSet *)executeQueryWithFormat:(NSString*)format, ...

-(FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments

示例:

// 查询数据
FMResultSet *rs = [db executeQuery:@"SELECT * FROM t_student"];

// 遍历结果集
while ([rs next]) {
    NSString *name = [rs stringForColumn:@"name"];
    int age = [rs intForColumn:@"age"];
    double score = [rs doubleForColumn:@"score"];
}
类型转换

参数是查询结果集的列的索引位置

  • intForColumn:
  • longForColumn:
  • longLongIntForColumn:
  • boolForColumn:
  • doubleForColumn:
  • stringForColumn:
  • dataForColumn:
  • dataNoCopyForColumn:
  • UTF8StringForColumnIndex:
  • objectForColumn:

你无需调用 [FMResultSet close]来关闭结果集, 当新的结果集产生,或者其数据库关闭时,会自动关闭。

关闭数据库

使用完数据库后,关闭数据库连接来释放SQLite资源。

[db close]
事务和多线程

事务:是执行多个SQL语句时,如果有一个失败了,则回滚到原始状态。主要用来确保所有的SQL逻辑必须完成。

多线程:如果多个线程同时访问FMDatabase实例,会造成混乱,所以要用FMDatabaseQueue:

  • FMDatabaseQueue的创建
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:path];
  • 简单使用
[queue inDatabase:^(FMDatabase *db) {
    [db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Jack"];
    [db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Rose"];
    [db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Jim"];
    
    FMResultSet *rs = [db executeQuery:@"select * from t_student"];
    while ([rs next]) {
        // …
    }
}];
  • 轻松地把简单任务包装到事务里
[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
    [db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Jack"];
    [db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Rose"];
    [db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Jim"];
    
    FMResultSet *rs = [db executeQuery:@"select * from t_student"];
    while ([rs next]) {
        // …
    }
}];

事务回滚
*rollback = YES;

NSUserDefault

NSUserDefault:是一个单例,在整个程序中,只有一个实例对象,可以用于数据的永久保存,且简单实用,这是他可以让数据自由传递的一个前提(可以存储用户信息,夜间模式,字体大小)

其实存储后也是一个plist文件。

存储基本数据类型
  • NSNumber(NSInteger,float,double)

  • NSString,NSArray,NSDictionary,BOOL

示例:

//使用NSUserDefault进行存储
//1:创建要存储的数组
NSArray *array = @[@"11", @"22",@"33"];
//创建NSUserDefault对象
NSUserDefaults *user = [NSUserDefaults standardUserDefaults];
[user setObject:array forKey:@"userArray"];
NSLog(@"%@",user);

//读取
NSArray *newarr = [user objectForKey:@"userArray"];
NSLog(@"%@",newarr);

存储的对象必须是不可变,如果要存储可变的数组和字典,应该先转换成不可变的之后再存储。

存储自定义对象
  • 对象要遵守NSCoding协议、copy协议,进行归档、解档
//归档
-(void)encodeWithCoder:(NSCoder *)aCoder{
    unsigned int count;
    objc_property_t *properties = class_copyPropertyList([self class], &count);
    for (int i = 0; i < count; i++) {
        const char * propertyCsString = property_getName(properties[i]);
        NSString   * propertyName  =  [NSString stringWithCString:propertyCsString encoding:NSUTF8StringEncoding];
        id           propertyValue =  [self valueForKey:propertyName];
        [aCoder encodeObject:propertyValue forKey:propertyName];
    }
    free(properties);
}
//解档
-(instancetype)initWithCoder:(NSCoder *)aDecoder{
    self = [super init];
    if (self) {
        unsigned int count;
        objc_property_t *properties = class_copyPropertyList([self class], &count);
        for (int i = 0; i < count; i++) {
            const char *property_csNam = property_getName(properties[i]);
            NSString   *p_key = [NSString stringWithCString:property_csNam encoding:NSUTF8StringEncoding];
            id value = [aDecoder decodeObjectForKey:p_key];   
            [self setValue:value forKey:p_key];
        }   
        free(properties);
    } 
return self
} 


 ///copy 协议
-(id)copyWithZone:(NSZone *)zone{
    StudentModel *s_model = [[self copyWithZone:zone] init];
    unsigned int count;
    objc_property_t *properties = class_copyPropertyList([self class], &count);
    for (int i = 0; i < count; i++) {
        const char *propertyCS = property_getName(properties[i]);
        NSString *p_key = [NSString stringWithCString:propertyCS encoding:NSUTF8StringEncoding];
        id p_value = [self valueForKey:p_key];
        [s_model setValue:p_value forKey:p_key];
    }
    free(properties);
    return s_model;
}  
  • 转换成NSData再进行存储
    StudentModel *s_model = [[StudentModel alloc] init];
    s_model.s_name = @"xiaoqiang";
    s_model.s_id = @"22";
    s_model.s_password = @"123456";
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:s_model];
    [u_default setObject:data forKey:@"s_student"];
    [u_default synchronize];
    NSData *rachiverData = [u_default objectForKey:@"s_student"];
    StudentModel *model = [NSKeyedUnarchiver unarchiveObjectWithData:rachiverData];
    NSLog(@"\n name is %@ \n id is %@ \n password is %@",model.s_name,model.s_id,model.s_password);

调用 synchronize 立刻同步数据到文件内,不然会有延迟。

[mySettingData synchronize];
删除对应的key
- (void)removeObjectForKey:(NSString *)defaultName;
//或者 
[[NSUserDefaults ] setObject:nil]

Plist

主要用来存储大文件。

缺点:需要将所有的数据取出再全部保存。

存储基本数据类型(array,dictionary,string,bool,date,data)
  • 存入字典和数组
NSMutableDictionary *dict = [NSMutableDictionary dictionary];

[dict writeToFile:@"filePath" atomically:YES];

NSMutableArray *array = [NSMutableArray array];

[array writeToFile:@"filePath" atomically:YES];
  • 读取数组和字典
//方法1:
NSString *filePath=[[NSBundle mainBundle] pathForResource:@"myDict" ofType:@"plist"];  
NSDictionary *myDic = [[NSDictionary alloc]initWithContentsOfFile:filePath];

NSString *filePath=[[NSBundle mainBundle] pathForResource:@"myArr" ofType:@"plist"];
NSArray *myArr2 = [[NSArray alloc] initWithContentsOfFile:filePath];

//方法2:
 NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
 NSString *filePath = [docPath stringByAppendingPathComponent: @"myData.plist"];
       
 NSDictionary *infoDic = [NSDictionary dictionaryWithContentsOfFile: filePath];
存储自定义数据类型
  • 也要实现归档协议然后转换成NSData类型储存
NSString *path = [self getPlistLibraryWithSuffix:@"test.plist"];

    NSMutableArray *stu_array = [NSMutableArray new];

    for (int i = 0; i < 10; i ++) {

        StudentModel *s_model = [StudentModel new];

        s_model.s_name = [NSString stringWithFormat:@"name%d",i];
        s_model.s_id = [NSString stringWithFormat:@"%d",100 + i];
        s_model.s_password = @"123456";
        [stu_array addObject:s_model];
    }
///写入
    NSData *archiverData = [NSKeyedArchiver archivedDataWithRootObject:stu_array];
    BOOL isSuc = [archiverData writeToFile:path atomically:YES];
//  BOOL isSuc = [NSKeyedArchiver archiveRootObject:stu_array toFile:path];
    NSLog(@"%d",isSuc);

  ///读取
    NSData *unarchiverData = [NSData dataWithContentsOfFile:path];
    NSArray *unArchiverArr = [NSKeyedUnarchiver unarchiveObjectWithData:unarchiverData];
//  NSArray *unArchiverArr = [NSKeyedUnarchiver unarchiveObjectWithFile:path];    
    NSLog(@"%@==",unArchiverArr);
删除plist文件
NSFileManager *fileManager = [NSFileManager defaultManager];

if ([fileManager isExecutableFileAtPath:@"filePath"]) {

    NSError *error;

    [fileManager removeItemAtPath:@"filePath" error:&error];
}

沙盒

沙盒的结构

总结:

  • Document 保存用户生成的数据(会备份)

  • Library/Cache 保存服务器数据或日志(不会备份)

  • temp 保存临时数据、经常要删除的

相关操作
  • 获取路径
//Document
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
//library
NSString *libraryPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES)[0];
//cache
NSString *cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
//temp
NSString *tempPath =  NSTemporaryDirectory();
  • 创建目录、创建文件
//document path
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
//test path
NSString *testPath= [documentsPath stringByAppendingPathComponent:@"test"];
//file path 
NSString *filePath = [testPath stringByAppendingString:@"file.plist"];

NSFileManager *fileManager = [NSFileManager defaultManager];
// 创建目录
[fileManager createDirectoryAtPath:testPath withIntermediateDirectories:YES attributes:nil error:nil];
// 创建文件
[fileManager createFileAtPath:filePath contents:nil attributes:nil];
  • 获取目录下的所有文件名
NSArray *fileNameArrray = [fileManager subpathsOfDirectoryAtPath:documentsPath error:nil];

//可能目录比较深,耗性能
NSArray *fileNameArrray =  [fileManager subpathsAtPath:documentsPath];
  • 更改到当前目录操作
//之后都在document文件夹中操作
[fileManager changeCurrentDirectoryPath:documentsPath];
  • 删除目录、删除文件(将其移动到temp目录)
//删除目录
[fileManager removeItemAtPath:documentsPath error:nil];
//删除文件
[fileManager removeItemAtPath:filePath error:nil];
  • 写入数据
NSString *temp = @"nihao 世界";
int dataInt = 1234;
float dataFloat = 3.14f;
//创建数据缓冲
NSMutableData *writer = [[NSMutableData alloc] init];
//将字符串添加到缓冲中
[writer appendData:[temp dataUsingEncoding:NSUTF8StringEncoding]];   
//将其他数据添加到缓冲中
[writer appendBytes:&dataInt length:sizeof(dataInt)];
[writer appendBytes:&dataFloat length:sizeof(dataFloat)];  
//将缓冲的数据写入到文件中
[writer writeToFile:path atomically:YES]; 
  • 读取数据
int intData;
float floatData = 0.0;
NSString *stringData;
    
NSData *reader = [NSData dataWithContentsOfFile:path];
stringData = [[NSString alloc] initWithData:[reader subdataWithRange:NSMakeRange(0, [temp length])]
                               encoding:NSUTF8StringEncoding];
[reader getBytes:&intData range:NSMakeRange([temp length], sizeof(intData))];
[reader getBytes:&floatData range:NSMakeRange([temp length] + sizeof(intData), sizeof(floatData))];
NSLog(@"stringData:%@ intData:%d floatData:%f", stringData, intData, floatData);

引用文章

参考NSUserDefault、Plist的解档和归档
FMDB的使用
本地数据持久化简述

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

推荐阅读更多精彩内容