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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章