12 | 数据存储基础

数据存储分为:运行时存储(程序运行时存储数据,程序结束内存回收,数据消失)和持久性存储。

iPhone应用程序采用的是沙盒机制,应用程序只能在自己的文件系统中读取文件。

iOS数据存储的五种方式:

NSUserDefaults:常用的一种持久性存储方式

属性列表(Plist):把NSDictionary和NSArray实例写入属性列表并保存

序列化:存储方式类似属性列表,但是可以存储自定义类

数据库:利用嵌入式数据库(SQLite3)存储数据

Coredata:对SQLite3的封装

------------------------------------------------

沙盒的目录:

Documents:存储应用程序的数据,不过NSUserDefaults的首选项设置除外

Library:基于NSUserDefaults的首选项设置存储在Library/Preference文件夹中

tmp:存储应用程序的临时文件,当需求空间的时候,这里面的文件会被删除


获取到上面三个文件夹的路径:

Documents

/**
     *  获取到Documents的路径
     *
     *  @param NSDocumentDirectory 告诉函数要查找的是什么目录,这里要找Document的目录
     *  @param NSUserDomainMask    查找的范围限制,这里是在沙河之类
     */
    NSString *str = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
    /**
     *  获取到了Documents的路径就可以调用下面的方法来拼接地址获取到想要的资源的路径再进行操作,不适合网页url
     */
    NSString *filePath = [str stringByAppendingPathComponent:@"autoModel.png"];


tmp:

NSString *str2 = NSTemporaryDirectory();

Library:

Library里面包含了三个文件夹:Preferences(应用程序的设置文件,通过NSUserDefaults进行访问)、Caches(缓存)、Application Support(不了解)

NSString *str = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES).firstObject;

------------------------------------------------------

本地数据存储建议:

程序数据:用户生成的文件、其他文件或者程序不能再重新创建的文件保存在Documents里面

可再生文件:Caches

临时文件:tmp

数据存储目前有两种策略:单文件存储和多文件存储(优)。

单文件存储:把所有的数据存在一个地方,每次要对数据进行操作的时候,全部读取出来,然后进行操作。eg:我现在有n条数据,要删除一条,就得取出所有的数据,然后删除。

好处:简单,在小数据量的时候优势很大。

坏处:在数据量大的时候,一个简单的操作就有可能造成线程堵塞。

代表:归档

多文件存储:把数据分类存储,每次操作的时候有针对性的去取数据。

优劣:和单文件相反。

代表:SQLite3

---------------------------------------------------------

NSUserDefaults:

/**
     *  它是一个单例
     */
    NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];
    /**
     *  给key@“苍井空”存值
     */
    [userDefault setObject:@"Aoi Sara" forKey:@"苍井空"];
    /**
     *  通过key取出里面存储的数据
     */
    NSString * str3 = [userDefault objectForKey:@"苍井空"];

NSUserDefaults存储一些小数据很流行,比如用户的密码啥的,因为是用的单例模式(单例链接),所以用起来超级方便。用的多的也就上面两个方法,可以看出它的数据存储方式是键值对。

关于NSUserDefault详细的用法点这里:(NSUserDefault详细用法链接

-------------------------

属性列表Plist:

我们用一个例子来说明:

//
//  VC.m
//  ModelView
//
//  Created by Wu on 16/3/7.
//  Copyright © 2016年 Wu. All rights reserved.
//

#import "VC.h"
//键宏
#define kName @"name"
#define kAge @"age"
#define kNum @"num"

@interface VC ()
@property(nonatomic , strong)NSString *name;
@property(nonatomic , assign)NSInteger age;
@property(nonatomic , assign)NSInteger num;
@end

@implementation VC

- (void)viewDidLoad {
    [super viewDidLoad];
    _name = @"wu";
    _age = 23;
    _num = 20110513;
    [self saveDataToPlist];
    [self getDataFromPlist];
}
//将数据写入plist文件
- (void)saveDataToPlist {
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    [dict setObject:_name forKey:kName];
    [dict setObject:[NSNumber numberWithInteger:_age] forKey:kAge];
    [dict setObject:[NSNumber numberWithInteger:_num] forKey:kNum];
    //将字典写入文件,序列化
    [dict writeToFile:[self filePath] atomically:YES];
}
//获取数据,反序列化
- (void)getDataFromPlist {
    NSDictionary *getData = [NSDictionary dictionaryWithContentsOfFile:[self filePath]];
    NSLog(@"name:%@ | age:%@ | num:%@",[getData objectForKey:kName],[getData objectForKey:kAge],[getData objectForKey:kNum]);
    
    //之前,我们一般这样读取工程里面现成的plist文件
    //前提:工程里面有一个叫student的plist、plist最外层数据格式是NSArray
//    NSArray *data = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"student" ofType:@"plist"]];
}
//在Documents文件夹下面建一个plist文件,并返回路径
- (NSString *)filePath {
    NSArray *arr = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documnetsPath = [arr firstObject];
    return  [documnetsPath stringByAppendingPathComponent:@"student.plist"];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
@end

-------------------------

序列化 --> 归档:

plist就是一种序列化表现,那为啥还要把归档单独出来,因为plist无法保存自定义类。

OC规定使用序列化需要对象实现NSCoding协议(NSDictionary实现了)。对于用来保存数据的对象只要符合了NSCoding协议,或者类中的变量为普通标量(int、float...),那么程序就可以利用序列化把数据写成文件,

访问的时候就是反序列化。

归档:就是对自定义的对象进行序列化(NSKeyedArchiver类:用来归档的 | NSKeyedUnarchiver类:用来反归档的)

@protocol NSCoding
//序列化
- (void)encodeWithCoder:(NSCoder *)aCoder;
//反序列化
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder; // NS_DESIGNATED_INITIALIZER
@end
@protocol NSCopying
- (id)copyWithZone:(nullable NSZone *)zone;
@end
例子:

自定义School类,就是用来归档的自定义对象

//
//  School.h
//  ModelView
//
//  Created by Wu on 16/3/7.
//  Copyright © 2016年 Wu. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface School : NSObject
@property(nonatomic , strong)NSString *schoolName;
@property(nonatomic , strong)NSString *schoolGrade;
@property(nonatomic , assign)NSInteger classNum;
@end

//
//  School.m
//  ModelView
//
//  Created by Wu on 16/3/7.
//  Copyright © 2016年 Wu. All rights reserved.
//

#import "School.h"

#define kSchoolName @"schoolName"
#define kSchoolGrade @"schoolGrade"
#define kClassNum @"ClassNum"

@interface School()<NSCoding,NSCopying>

@end

@implementation School
#pragma mark- NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:_schoolName forKey:kSchoolName];
    [aCoder encodeObject:_schoolGrade forKey:kSchoolGrade];
    [aCoder encodeInteger:_classNum forKey:kClassNum];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super init];
    if (self) {
        self.schoolName = [aDecoder decodeObjectForKey:kSchoolName];
        self.schoolGrade = [aDecoder decodeObjectForKey:kSchoolGrade];
        self.classNum = [aDecoder decodeIntegerForKey:kClassNum];
    }
    return self;
}
#pragma mark- NSCopying
- (id)copyWithZone:(NSZone *)zone {
    School *new = [[School alloc]init];
    new.schoolName = [self.schoolName copyWithZone:zone];
    new.schoolGrade = [self.schoolGrade copyWithZone:zone];
    new.classNum = self.classNum;
    return new;
}
@end

归档:

//
//  VC.m
//  ModelView
//
//  Created by Wu on 16/3/7.
//  Copyright © 2016年 Wu. All rights reserved.
//

#import "VC.h"
#import "School.h"
//键宏
#define kName @"name"
#define kAge @"age"
#define kNum @"num"
#define kSchool @"school"

@interface VC ()
//@property(nonatomic , strong)NSString *name;
//@property(nonatomic , assign)NSInteger age;
//@property(nonatomic , assign)NSInteger num;

@property(nonatomic , strong)School *school;
@end

@implementation VC

- (void)viewDidLoad {
    [super viewDidLoad];
//    _name = @"wu";
//    _age = 23;
//    _num = 20110513;
//    [self saveDataToPlist];
//    [self getDataFromPlist];
    
    
    
    _school = [[School alloc]init];
    _school.schoolName = @"Wuhan university of technology";
    _school.schoolGrade = @"five";
    _school.classNum = 5;
    [self archiver];
    School *new = [self unarchiver];
    
    NSLog(@"写入之前的数据:schoolName:%@ | schoolGrade:%@ | classNum:%ld",_school.schoolName,_school.schoolGrade,_school.classNum);
    
    NSLog(@"读取出来的数据:schoolName:%@ | schoolGrade:%@ | classNum:%ld",new.schoolName,new.schoolGrade,new.classNum);
    
}

- (void)archiver {
    NSMutableData *data = [[NSMutableData alloc]init];
    
    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc]initForWritingWithMutableData:data];
    [archiver encodeObject:_school forKey:kSchool];
    [archiver finishEncoding];
    [data writeToFile:[self archiverFilePath] atomically:YES];
}

- (id)unarchiver {
    NSData *data = [NSData dataWithContentsOfFile:[self archiverFilePath]];
    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc]initForReadingWithData:data];
    School *school = [unarchiver decodeObjectForKey:kSchool];
    return school;
}

- (NSString *)archiverFilePath {
    NSString *str = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
    return [str stringByAppendingPathComponent:@"archiver"];
}




////将数据写入plist文件
//- (void)saveDataToPlist {
//    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
//    [dict setObject:_name forKey:kName];
//    [dict setObject:[NSNumber numberWithInteger:_age] forKey:kAge];
//    [dict setObject:[NSNumber numberWithInteger:_num] forKey:kNum];
//    //将字典写入文件
//    [dict writeToFile:[self filePath] atomically:YES];
//}
////获取数据
//- (void)getDataFromPlist {
//    NSDictionary *getData = [NSDictionary dictionaryWithContentsOfFile:[self filePath]];
//    NSLog(@"name:%@ | age:%@ | num:%@",[getData objectForKey:kName],[getData objectForKey:kAge],[getData objectForKey:kNum]);
//    
//    //之前,我们一般这样读取工程里面现成的plist文件
//    //前提:工程里面有一个叫student的plist、plist最外层数据格式是NSArray
////    NSArray *data = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"student" ofType:@"plist"]];
//}
////在Documents文件夹下面建一个plist文件,并返回路径
//- (NSString *)filePath {
//    NSArray *arr = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
//    NSString *documnetsPath = [arr firstObject];
//    return  [documnetsPath stringByAppendingPathComponent:@"student.plist"];
//}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
@end

个人总结:其实plist和归档是一档子事情,只是归档是对plist无法序列化自定义类的一种完善而已,用法和思路基本一样。

如果属性超多,那么这时候就可以使用runtime来简化归档:链接

-------------------------

SQLite3:[详细的SQLite3使用链接

想要使用SQLite3,程序需要添加libSQLite3.dylib包,它包含了使用SQLite 3需要的C函数接口。

SQLite3是嵌入在iOS中的关系型数据库。对于存储大规模的数据很有效。SQLite3使得不必将每个对象都加载到内存中。

sqlite3     *db, 数据库句柄,跟文件句柄FILE很类似

sqlite3_stmt      *stmt, 这个相当于ODBC的Command对象,用于保存编译好的SQL语句

sqlite3_open(),   打开数据库,没有数据库时创建。

sqlite3_exec(),   执行非查询的sql语句

Sqlite3_step(), 在调用sqlite3_prepare后,使用这个函数在记录集中移动。

Sqlite3_close(), 关闭数据库文件

还有一系列的函数,用于从记录集字段中获取数据,如

sqlite3_column_text(), 取text类型的数据。

sqlite3_column_blob(),取blob类型的数据

sqlite3_column_int(), 取int类型的数据

//
//  ViewController.m
//  ModelView
//
//  Created by Wu on 16/3/5.
//  Copyright © 2016年 Wu. All rights reserved.
//

#import "ViewController.h"
#import "Model.h"
#import <sqlite3.h>

@interface ViewController ()<ModelDelegate>
{
    sqlite3 *database;
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    /**
     *  获取到Documents的路径
     *
     *  @param NSDocumentDirectory 告诉函数要查找的是什么目录,这里要找Document的目录
     *  @param NSUserDomainMask    查找的范围限制,这里是在沙盒之类
     */
//    NSString *str = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES).firstObject;
    /**
     *  获取到了Documents的路径就可以调用下面的方法来拼接地址获取到想要的资源的路径再进行操作,不适合网页url
     */
//    NSString *filePath = [str stringByAppendingPathComponent:@"autoModel.png"];
//    
//    NSString *str2 = NSTemporaryDirectory();
    /**
     *  它是一个单例
     */
    NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];
    /**
     *  给key@“苍井空”存值
     */
    [userDefault setObject:@"Aoi Sara" forKey:@"苍井空"];
    /**
     *  通过key取出里面存储的数据
     */
    NSString * str3 = [userDefault objectForKey:@"苍井空"];
    NSLog(@"%@",str3);
    
//打开数据库
    //下面为何要利用UTF8String转换,因为sqlite3是基于C语言的,所以要将NSString转换为C里面的字符串char
    char *path;
    path = (char *)[[self dataFilePath] UTF8String];//如果path不存在,则open等于创建一个数据库,path存在就是打开
    if (sqlite3_open(path, &database) != SQLITE_OK) {
        sqlite3_close(database);//关闭数据库
        NSLog(@"数据库创建失败!");
    }
//创建一张表
    //将SQL语言写成字符串
    NSString *ceateSQL = @"CREATE TABLE IF NOT EXISTS PERSONINFO(NAME TEXT, AGE INTEGER, SEX TEXT, WEIGHT INTEGER, ADDRESS TEXT)";
    char *ERROR;
    //下面是sqlite3比较重要的一个执行方法(sqlite3_exec()),如果创建表失败,错误信息会存储到ERROR里
    if (sqlite3_exec(database, [ceateSQL UTF8String], NULL, NULL, &ERROR)!=SQLITE_OK){
        sqlite3_close(database);
        NSAssert(0, @"ceate table faild!");
        NSLog(@"表创建失败");
    }
//插入
    char *error = nil;
//    char *update = "INSERT OR REPLACE INTO PERSONINFO('NAME','AGE','SEX','WEIGHT','ADDRESS')VALUES('zhi',23,'man',68,'China.Wuhan')";
    char *update = "INSERT OR REPLACE INTO PERSONINFO(NAME,AGE,SEX,WEIGHT,ADDRESS) ""VALUES('wu222222',23,'man',68,'China.Wuhan')";
    if (sqlite3_exec(database, update, NULL, NULL,&error) != SQLITE_OK) {
        NSLog(@"插入失败");
    }
//查询
    NSString *quary = @"SELECT * FROM PERSONINFO";//SELECT ROW,FIELD_DATA FROM FIELDS ORDER BY ROW
    sqlite3_stmt *stmt;//申明结构体指针
    //sqlite3_prepare_v2(database, [quary UTF8String], -1, &stmt, nil)函数将SQL语句编译为SQLite3内部一个结构体SQLite3_stmt,后面查询就可以通过返回的结构体来进行查询
    if (sqlite3_prepare_v2(database, [quary UTF8String], -1, &stmt, nil) == SQLITE_OK) {
        
        while(sqlite3_step(stmt) == SQLITE_ROW) {//按行查找
            
            char *name = (char *)sqlite3_column_text(stmt, 0);//第一列
            NSString *nameString = [[NSString alloc] initWithUTF8String:name];
            NSLog(@"name:%@",nameString);
            
            int age = sqlite3_column_int(stmt, 1);//第二列
            NSLog(@"age:%d",age);
            
            char *sex = (char *)sqlite3_column_text(stmt, 2);
            NSString *sexString = [[NSString alloc] initWithUTF8String:sex];
            NSLog(@"sex:%@",sexString);
            
            int weight = sqlite3_column_int(stmt, 3);
            NSLog(@"weight:%d",weight);
            
            char *address = (char *)sqlite3_column_text(stmt, 4);
            NSString *addressString = [[NSString alloc] initWithUTF8String:address];
            NSLog(@"address:%@",addressString);
        }
        sqlite3_finalize(stmt);//结束
    }  
    //用完了一定记得关闭,释放内存  
    sqlite3_close(database);
}
- (void)inputDataAndSaveToSQLite3 {
    char *update = "INSERT OR REPLACE INTO PERSIONINFO(NAME,AGE,SEX,WEIGHT,ADDRESS)""VALUES(?,?,?,?,?);";
    //上边的update也可以这样写:
    //NSString *insert = [NSString stringWithFormat:@"INSERT OR REPLACE INTO PERSIONINFO('%@','%@','%@','%@','%@')VALUES(?,?,?,?,?)",NAME,AGE,SEX,WEIGHT,ADDRESS];
    char *errorMsg = NULL;
    sqlite3_stmt *stmt;
    if (sqlite3_prepare_v2(database, update, -1, &stmt, nil) == SQLITE_OK) {
        //【插入数据】在这里我们使用绑定数据的方法,参数一:sqlite3_stmt,参数二:插入列号,参数三:插入的数据,参数四:数据长度(-1代表全部),参数五:是否需要回调
        //自己在UI中设置几个textFiled,然后输入完成后调用这个方法把数据存储到sqlite3里面去
//        sqlite3_bind_text(stmt, 1, [self.nameTextField.text UTF8String], -1, NULL);
//        sqlite3_bind_int(stmt, 2, [self.ageTextField.text intValue]);
//        sqlite3_bind_text(stmt, 3, [self.sexTextField.text UTF8String], -1, NULL);
//        sqlite3_bind_int(stmt, 4, [self.weightTextField.text integerValue]);
//        sqlite3_bind_text(stmt, 5, [self.addressTextField.text UTF8String], -1, NULL);
    }
    if (sqlite3_step(stmt) != SQLITE_DONE)
        NSLog(@"数据更新失败");
    NSAssert(0, @"error updating :%s",errorMsg);
    sqlite3_finalize(stmt);
    sqlite3_close(database);
}

-(NSString *) dataFilePath{
    //获取沙盒中Document文件夹的路径
    NSArray *path =  NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *document = [path objectAtIndex:0];
    //在Document文件夹下面添加一个名为person的数据库
    NSLog(@"%@",[document stringByAppendingPathComponent:@"person.sqlite"]);
    //返回数据库的路径
    return [document stringByAppendingPathComponent:@"person.sqlite"];//'person.sqlite'
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end















发布了34 篇原创文章 · 获赞 0 · 访问量 2万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章