最全iOS數據存儲方法介紹:FMDB,SQLite3 ,Core Data,Plist,Preference偏好設置,NSKeyedArchiver歸檔

轉載請註明本文地址:http://www.jianshu.com/p/e88880be794fgit

目的

項目準備運用的Core Data進行本地數據存儲,原本打算只寫一下Core Data的,不過既然說到了數據存儲,乾脆來個數據存儲基礎大總結!本文將對如下幾個模塊進行敘述。github

  1. 沙盒
  2. Plist
  3. Preference偏好設置
  4. NSKeyedArchiver歸檔 / NSKeyedUnarchiver解檔
  5. SQLite3的使用
  6. FMDB
  7. Core Data

Realm的文檔很詳細,這篇文章就不寫了: Realm的使用方法sql

下圖是Core Data堆棧的圖示,在這裏是爲了作文章的封面圖片,後文會介紹Core Data的使用方法。數據庫

Core Data

1、沙盒

iOS本地化存儲的數據保存在沙盒中, 而且每一個應用的沙盒是相對獨立的。每一個應用的沙盒文件結構都是相同的,以下圖所示: 編程

沙盒目錄

Documents:iTunes會備份該目錄。通常用來存儲須要持久化的數據。swift

Library/Caches:緩存,iTunes不會備份該目錄。內存不足時會被清除,應用沒有運行時,可能會被清除,。通常存儲體積大、不須要備份的非重要數據。數組

Library/Preference:iTunes同會備份該目錄,能夠用來存儲一些偏好設置。緩存

tmp: iTunes不會備份這個目錄,用來保存臨時數據,應用退出時會清除該目錄下的數據。安全

如何拿到每一個文件夾的路徑呢?我這裏就只說最好的一種方法。多線程

// 這個方法返回的是一個數組,iOS中這個數組其實只有一個元素,因此咱們能夠用lastObject或lastObject來拿到Documents目錄的路徑
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
// 獲得Document目錄下的test.plist文件的路徑
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];
複製代碼

上面的方法有三個參數,這裏分別說一下。

NSDocumentDirectory: 第一個參數表明要查找哪一個文件,是一個枚舉,點進去看一下發現有不少選擇,爲了直接找到沙盒中的Documents目錄,咱們通常用NSDocumentDirectory。

NSUserDomainMask: 也是一個枚舉,表示搜索的範圍限制於當前應用的沙盒目錄。咱們通常就選擇NSUserDomainMask。

YES (expandTilde): 第三個參數是一個BOOL值。iOS中主目錄的全寫形式是/User/userName,這個參數填YES就表示全寫,填NO就是寫成‘‘~’’,咱們通常填YES。

根據上面的文字,你應用能夠知道如何拿到Library/Caches目錄下的文件路徑了吧?沒錯,就是這樣:

//獲取Library/Caches目錄路徑
NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject; 
//獲取Library/Caches目錄下的test.data文件路徑
NSString *filePath = [path stringByAppendingPathComponent:@"test.data"];
複製代碼
//獲取temp路徑
NSString *tmp= NSTemporaryDirectory();
//獲取temp下test.data文件的路徑
NSString *filePath = [tmp stringByAppendingPathComponent:@"test.data"];
複製代碼

因此,若是程序中有須要長時間持久化的數據,就選擇Documents,若是有體積大可是並不重要的數據,就能夠選擇交給Library,而臨時沒用的數據固然是放到temp。至於Preference則能夠用來保存一些設置類信息,後面會講到偏好設置的使用方法。

推薦一個好用的分類工具集合**WHKit**,能夠直接使用分類方法拿到文件路徑。

//一行代碼搞定
NSString *filePath = [@"test.plist" appendDocumentPath];

//使用WHKit,上面這一行代碼等同於下面這兩行。

NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];
複製代碼

2、Plist存儲

Plist文件的Type能夠是字典NSDictionary或數組NSArray,也就是說能夠把字典或數組直接寫入到文件中。 NSString、NSData、NSNumber等類型,也可使用writeToFile:atomically:方法直接將對象寫入文件中,只是Type爲空。

下面就舉個例子來看一下如何使用Plist來存儲數據。

//準備要保存的數據
NSDictionary *dict = [NSDictionary dictionaryWithObject:@"first" forKey:@"1"];

//獲取路徑,你也能夠利用上面說WHKit更優雅的拿到路徑
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];

//寫入數據
[dict writeToFile:filePath atomically:YES];
複製代碼

上面的代碼運行以後,在應用沙盒的Documents中就建立了一個plist文件,而且已經寫入數據保存。

寫入plist

數據存儲了,那麼如何讀取呢?

//拿到plist文件路徑,你也能夠利用上面說WHKit更優雅的拿到路徑
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];

//解析數據,log出的結果爲first,讀取成功
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSString *result = dict[@"1"];
NSLog(@"%@",result);
複製代碼

上面這段代碼就讀出了plist種的數據。

3、Preference偏好設置

偏好設置的使用很是方便快捷,咱們通常使用它來進行一些設置的記錄,好比用戶名,開關是否打開等設置。 Preference是經過NSUserDefaults來使用的,是經過鍵值對的方式記錄設置。下面舉個例子。

利用NSUserDefaults判斷APP是否是首次啓動。

// 啓動的時候判斷key有沒有value,若是有,說明已經啓動過了,若是沒有,說明是第一次啓動
if (![[NSUserDefaults standardUserDefaults] valueForKey:@"first"]) {
        
    //若是是第一次啓動,就運用偏好設置給key設置一個value
    [[NSUserDefaults standardUserDefaults] setValue:@"start" forKey:@"first"];
    NSLog(@"是第一次啓動");
        
} else {
    NSLog(@"不是第一次啓動");
}
複製代碼

經過鍵值對的方式很是easy的保存了數據。

**注意:**NSUserDefaults能夠存儲的數據類型包括:NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary。若是要存儲其餘類型,則須要轉換爲前面的類型,才能用NSUserDefaults存儲。

下面的例子是用NSUserDefaults存儲圖片,須要先把圖片轉換成NSData類型。

UIImage *image=[UIImage imageNamed:@"photo"];
//UIImage對象轉換成NSData
NSData *imageData = UIImageJPEGRepresentation(image, 100);

//偏好設置能夠保存NSData,可是不能保存UIimage
[[NSUserDefaults standardUserDefaults] setObject:imageData forKey:@"image"];
    
// 讀出data
NSData *getImageData = [[NSUserDefaults standardUserDefaults] dataForKey:@"image"];
//NSData轉換爲UIImage
UIImage *Image = [UIImage imageWithData:imageData];
複製代碼

NSUserDefaults是否是很好用!不過有沒有以爲每次都寫[NSUserDefaults standardUserDefaults]有點煩,那麼又到了推薦環節,好用的分類工具**WHKit**,這個工具中有許多好用的宏定義,其中一個就是KUSERDEFAULT,等同於[NSUserDefaults standardUserDefaults]。 **WHKit**中還有更多好用的宏定義等着你!

4、NSKeyedArchiver歸檔 / NSKeyedUnarchiver解檔

歸檔和解檔會在寫入、讀出數據以前進行序列化、反序列化,數據的安全性相對高一些。 NSKeyedArchiver能夠有三個使用情景。

1.對單個簡單對象進行歸檔/解檔

與plist差很少,對於簡單的數據進行歸檔,直接寫入文件路徑。

//獲取歸檔文件路徑
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test"];

//對字符串@」test」進行歸檔,寫入到filePath中
[NSKeyedArchiver archiveRootObject:@"test" toFile:filePath];

//根據保存數據的路徑filePath解檔數據
NSString *result = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
//log結構爲@」test」,就是上面歸檔的數據
NSLog(@"%@",result);
複製代碼

固然,也能夠存儲NSArray,NSDictionary等對象。

2.對多個對象進行歸檔/解檔

這種狀況能夠一次保存多種不一樣類型的數據,最終使用的是與plist相同的writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile來寫入數據。

//獲取歸檔路徑
    NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
    NSString *filePath = [documentPath stringByAppendingPathComponent:@"test"];
    
    //用來承載數據的NSMutableData
    NSMutableData *data = [[NSMutableData alloc] init];
    //歸檔對象
    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
    
    //將要被保存的三個數據
    NSString *name = @"jack";
    int age = 17;
    double height = 1.78;
    
    //運用encodeObject:方法歸檔數據
    [archiver encodeObject:name forKey:@"name"];
    [archiver encodeInt:age forKey:@"age"];
    [archiver encodeDouble:height forKey:@"height"];
    //結束歸檔
    [archiver finishEncoding];
    //寫入數據(存儲數據)
    [data writeToFile:filePath atomically:YES];
    
    
    //NSMutableData用來承載解檔出來的數據
    NSMutableData *resultData = [[NSMutableData alloc] initWithContentsOfFile:filePath];
    //解檔對象
    NSKeyedUnarchiver *unArchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:resultData];
    
    //分別解檔出三個數據
    NSString *resultName = [unArchiver decodeObjectForKey:@"name"];
    int resultAge = [unArchiver decodeIntForKey:@"age"];
    double resultHeight = [unArchiver decodeDoubleForKey:@"height"];
    //結束解檔
    [unArchiver finishDecoding];
    //成功打印出結果,說明成功歸檔解檔
    NSLog(@"name = %@, age = %d, height = %.2f",resultName,resultAge,resultHeight);
複製代碼

3.歸檔保存自定義對象

我認爲這是歸檔最常使用的情景。 定義一個Person類,若是想對person進行歸檔解檔,首先要讓Person遵照協議。

/********Person.h*********/

#import <Foundation/Foundation.h>

//遵照NSCoding協議
@interface Person : NSObject<NSCoding>

@property (nonatomic, copy) NSString *name;

@property (nonatomic, assign) NSInteger age;

//自定義的歸檔保存數據的方法
+(void)savePerson:(Person *)person;

//自定義的讀取沙盒中解檔出的數據
+(Person *)getPerson;

@end
複製代碼

NSCoding協議有2個方法:

  • (void)encodeWithCoder:(NSCoder *)aCoder 歸檔時調用這個方法,在方法中使用encodeObject:forKey:歸檔變量。
  • (instancetype)initWithCoder:(NSCoder *)aDecoder 解檔時調用這個方法,在方法中石油decodeObject:forKey讀出變量。
/******Person.m*******/

#import "Person.h"

@implementation Person

//歸檔,Key建議使用宏代替,這裏就不使用了
- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:self.name forKey:@"name"];
    [aCoder encodeInteger:self.age forKey:@"age"];
}

//解檔
-(instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self=[super init]) {
        self.name = [aDecoder decodeObjectForKey:@"name"];
        self.age = [aDecoder decodeIntegerForKey:@"age"];
    }
    return self;
}

//類方法,運用NSKeyedArchiver歸檔數據
+(void)savePerson:(Person *)person {
    NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
    NSString *path=[docPath stringByAppendingPathComponent:@"Person.plist"];
    [NSKeyedArchiver archiveRootObject:person toFile:path];
}

//類方法,使用NSKeyedUnarchiver解檔數據
+(Person *)getPerson {
    NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
    NSString *path=[docPath stringByAppendingPathComponent:@"Person.plist"];
    Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
    return person;
}

@end
複製代碼

下面就能夠在須要的地方歸檔或解檔Person對象。

/*******ViewController.m*******/

    //建立Person對象
    Person *person = [Person new];
    person.name = @"jack";
    person.age = 17;
    //歸檔保存數據
    [Person savePerson:person];
    //解檔拿到數據
    Person *resultPerson = [Person getPerson];
    //打印出結果,證實歸檔解檔成功
    NSLog(@"name = %@, age = %ld",resultPerson.name,resultPerson.age);
複製代碼

5、SQLite3的使用

一、首先須要添加庫文件libsqlite3.0.tbd 二、導入頭文件#import <sqlite3.h> 三、打開數據庫 四、建立表 五、對數據表進行增刪改查操做 六、關閉數據庫

上代碼以前,有些問題你須要瞭解。

  • SQLite 不區分大小寫,但也有須要注意的地方,例如GLOB 和 glob 具備不一樣做用。

  • SQLite3有5種基本數據類型 text、integer、float、boolean、blob

  • SQLite3是無類型的,在建立的時候你能夠不聲明字段的類型,不過仍是建議加上數據類型

create table t_student(name, age);
複製代碼
create table t_student(name text, age integer);
複製代碼

下面的代碼就是SQLite3的基本使用方法,帶有詳細註釋。 代碼中用到了一個Student類,這個類有兩個屬性name和age。

/****************sqliteTest.m****************/

#import "sqliteTest.h"
//1.首先導入頭文件
#import <sqlite3.h>

//2.數據庫
static sqlite3 *db;

@implementation sqliteTest

/** 3.打開數據庫 */
+ (void)openSqlite {
    
    //數據庫已經打開
    if (db != nil) {
        NSLog(@"數據庫已經打開");
        return;
    }
    
    //建立數據文件路徑
    NSString *string = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
    NSString *path = [string stringByAppendingPathComponent:@"Student.sqlite"];
    NSLog(@"%@",path);
    
    //打開數據庫
    int result = sqlite3_open(path.UTF8String, &db);
    
    if (result == SQLITE_OK) {
        NSLog(@"數據庫打開成功");
    } else {
        NSLog(@"數據庫打開失敗");
    }
}

/** 4.建立表 */
+ (void)createTable {
    
    //建立表的SQLite語句,其中id是主鍵,not null 表示在表中建立紀錄時這些字段不能爲NULL
    NSString *sqlite = [NSString stringWithFormat:@"create table if not exists t_student (id integer primary key autoincrement, name text not null, age integer)"];
    
    //用來記錄錯誤信息
    char *error = NULL;
    
    //執行SQLite語句
    int result = sqlite3_exec(db, sqlite.UTF8String, nil, nil, &error);
    
    if (result == SQLITE_OK) {
        NSLog(@"建立表成功");
    }else {
        NSLog(@"建立表失敗");
    }
}

/** 5.添加數據 */
+ (void)addStudent:(Student *)stu {
    
    //增添數據的SQLite語句
    NSString *sqlite = [NSString stringWithFormat:@"insert into t_student (name,age) values ('%@','%ld')",stu.name,stu.age];
    char *error = NULL;
    
    int result = sqlite3_exec(db, [sqlite UTF8String], nil, nil, &error);
    
    if (result == SQLITE_OK) {
        NSLog(@"添加數據成功");
    } else {
        NSLog(@"添加數據失敗");
    }
}

/** 6.刪除數據 */
+ (void)deleteStuWithName:(NSString *)name {
    
    //刪除特定數據的SQLite語句
    NSString *sqlite = [NSString stringWithFormat:@"delete from t_student where name = '%@'",name];
    char *error = NULL;
    
    int result = sqlite3_exec(db, sqlite.UTF8String, nil, nil, &error);
    
    if (result == SQLITE_OK) {
        NSLog(@"刪除數據成功");
    } else {
        NSLog(@"刪除數據失敗");
    }
}

/** 7.更改數據 */
+ (void)upDateWithStudent:(Student *)stu WhereName:(NSString *)name {
    
    //更新特定字段的SQLite語句
    NSString *sqlite = [NSString stringWithFormat:@"update t_student set name = '%@', age = '%ld' where name = '%@'",stu.name,stu.age,name];
    char *error = NULL;
    
    int result = sqlite3_exec(db, sqlite.UTF8String, nil, nil, &error);
    
    if (result == SQLITE_OK) {
        NSLog(@"修改數據成功");
    } else {
        NSLog(@"修改數據失敗");
    }
}

/** 8.根據條件查詢 */
+ (NSMutableArray *)selectWithAge:(NSInteger)age {
    
    //可變數組,用來保存查詢到的數據
    NSMutableArray *array = [NSMutableArray array];
    
    //查詢全部數據的SQLite語句
    NSString *sqlite = [NSString stringWithFormat:@"select * from t_student where age = '%ld'",age];
    
    //定義一個stmt存放結果集
    sqlite3_stmt *stmt = NULL;
    
    //執行
    int result = sqlite3_prepare(db, sqlite.UTF8String, -1, &stmt, NULL);
    
    if (result == SQLITE_OK) {
        NSLog(@"查詢成功");
        
        //遍歷查詢到的全部數據,並添加到上面的數組中
        while (sqlite3_step(stmt) == SQLITE_ROW) {
            Student *stu = [[Student alloc] init];
            //得到第1列的姓名,第0列是id
            stu.name = [NSString stringWithUTF8String:(const char *)sqlite3_column_text(stmt, 1)];
            //得到第2列的年齡
            stu.age = sqlite3_column_int(stmt, 2);
            [array addObject:stu];
        }
    } else {
        NSLog(@"查詢失敗");
    }
    
    //銷燬stmt,防止內存泄漏
    sqlite3_finalize(stmt);
    return array;
}

/** 9.查詢全部數據 */
+ (NSMutableArray *)selectStudent {
    
    //可變數組,用來保存查詢到的數據
    NSMutableArray *array = [NSMutableArray array];
    
    //查詢全部數據的SQLite語句
    NSString *sqlite = [NSString stringWithFormat:@"select * from t_student"];
    
    //定義一個stmt存放結果集
    sqlite3_stmt *stmt = NULL;
    
    //執行
    int result = sqlite3_prepare(db, sqlite.UTF8String, -1, &stmt, NULL);
    
    if (result == SQLITE_OK) {
        NSLog(@"查詢成功");
        
        //遍歷查詢到的全部數據,並添加到上面的數組中
        while (sqlite3_step(stmt) == SQLITE_ROW) {
            Student *stu = [[Student alloc] init];
            //得到第1列的姓名,第0列是id
            stu.name = [NSString stringWithUTF8String:(const char *)sqlite3_column_text(stmt, 1)];
            //得到第2列的年齡
            stu.age = sqlite3_column_int(stmt, 2);
            [array addObject:stu];
        }
    } else {
        NSLog(@"查詢失敗");
    }
    
    //銷燬stmt,防止內存泄漏
    sqlite3_finalize(stmt);
    return array;
}

/** 10.刪除表中的全部數據 */
+ (void)deleteAllData {
    NSString *sqlite = [NSString stringWithFormat:@"delete from t_student"];
    char *error = NULL;
    int result = sqlite3_exec(db, sqlite.UTF8String, nil, nil, &error);
    if (result == SQLITE_OK) {
        NSLog(@"清除數據庫成功");
    } else {
        NSLog(@"清除數據庫失敗");
    }
}

/** 11.刪除表 */
+ (void)dropTable {
    NSString *sqlite = [NSString stringWithFormat:@"drop table if exists t_student"];
    char *error = NULL;
    int result = sqlite3_exec(db, sqlite.UTF8String, nil, nil, &error);
    if (result == SQLITE_OK) {
        NSLog(@"刪除表成功");
    } else {
        NSLog(@"刪除表失敗");
    }
}

/** 12.關閉數據庫 */
+ (void)closeSqlite {
    int result = sqlite3_close(db);
    if (result == SQLITE_OK) {
        NSLog(@"數據庫關閉成功");
    } else {
        NSLog(@"數據庫關閉失敗");
    }
}
複製代碼

附上SQLite的基本語句

  • 建立表: create table if not exists 表名 (字段名1, 字段名2...);

create table if not exists t_student (id integer primary key autoincrement, name text not null, age integer)

  • 增長數據: insert into 表名 (字段名1, 字段名2, ...) values(字段1的值, 字段2的值, ...);

insert into t_student (name,age) values (@"Jack",@17);

  • 根據條件刪除數據: delete from 表名 where 條件;

delete from t_student where name = @"Jack";

  • 刪除表中全部的數據: delete from 表名

delete from t_student

  • 根據條件更改某個數據: update 表名 set 字段1 = '值1', 字段2 = '值2' where 字段1 = '字段1的當前值'

update t_student set name = 'lily', age = '16' where name = 'Jack'

  • 根據條件查找: select * from 表名 where 字段1 = '字段1的值'

select * from t_student where age = '16'

  • 查找全部數據: select * from 表名

select * from t_student

  • 刪除表: drop table 表名

drop table t_student

  • 排序查找: select * from 表名 order by 字段

select * from t_student order by age asc (升序,默認) select * from t_student order by age desc (降序)

  • 限制: select * from 表名 limit 值1, 值2

select * from t_student limit 5, 10 (跳過5個,一共取10個數據)

SQLite3還有事務方面的使用,這裏就不作說明了,下面的FMDB中會有事務的使用。

6、FMDB

FMDB封裝了SQLite的C語言API,更加面向對象。 首先須要明確的是FMDB中的三個類。

FMDatabase:能夠理解成一個數據庫。

FMResultSet:查詢的結果集合。

FMDatabaseQueue:運用多線程,可執行多個查詢、更新。線程安全。

FMDB基本語法

查詢:executeQuery: SQLite語句命令。

[db executeQuery:@"select id, name, age from t_person"]
複製代碼

其他的操做都是「更新」:executeUpdate: SQLite語句命令。

// CREATE, UPDATE, INSERT, DELETE, DROP,都使用executeUpdte
[db executeUpdate:@"create table if not exists t_person (id integer primary key autoincrement, name text, age integer)"]
複製代碼

FMDB的基本使用

在項目中導入FMDB框架和sqlite3.0.tbd,導入頭文件。

1. 打開數據庫,並建立表

初始化FMDatabase:FMDatabase *db = [FMDatabase databaseWithPath:filePath]; 其中的filePath是提早準備好要存放數據的路徑。

打開數據庫:[db open]

建立數據表:[db executeUpdate:@"create table if not exists t_person (id integer primary key autoincrement, name text, age integer)"];

#import "ViewController.h"
#import <FMDB.h>

@interface ViewController ()

@end

@implementation ViewController{
    FMDatabase *db;
}

- (void)openCreateDB {

    //存放數據的路徑
    NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
    NSString *filePath = [path stringByAppendingPathComponent:@"person.sqlite"];
    
    //初始化FMDatabase
    db = [FMDatabase databaseWithPath:filePath];
    
    //打開數據庫並建立person表,person中有主鍵id,姓名name,年齡age
    if ([db open]) {
        BOOL success = [db executeUpdate:@"create table if not exists t_person (id integer primary key autoincrement, name text, age integer)"];
        if (success) {
            NSLog(@"創表成功");
        }else {
            NSLog(@"建立表失敗");
        }
    } else {
        NSLog(@"打開失敗");
    }
}
複製代碼

2. 插入數據

運用executeUpdate方法執行插入數據命令: [db executeUpdate:@"insert into t_person(name,age) values(?,?)",@"jack",@17]

-(void)insertData {
    BOOL success = [db executeUpdate:@"insert into t_person(name,age) values(?,?)",@"jack",@17];
    if (success) {
        NSLog(@"添加數據成功");
    } else {
        NSLog(@"添加數據失敗");
    }
}
複製代碼

3. 刪除數據

刪除姓名爲lily的數據:[db executeUpdate:@"delete from t_person where name = 'lily'"]

-(void)deleteData {
    BOOL success = [db executeUpdate:@"delete from t_person where name = 'lily'"];
    if (success) {
        NSLog(@"刪除數據成功");
    } else {
        NSLog(@"刪除數據失敗");
    }
}
複製代碼

4. 修改數據

把年齡爲17歲的數據,姓名改成lily:[db executeUpdate:@"update t_person set name = 'lily' where age = 17"]

-(void)updateData {
    BOOL success = [db executeUpdate:@"update t_person set name = 'lily' where age = 17"];
    if (success) {
        NSLog(@"更新數據成功");
    } else {
        NSLog(@"更新數據失敗");
    }
}
複製代碼

5. 查詢數據

執行查詢語句,用FMResultSet接收查詢結果:FMResultSet *set = [db executeQuery:@"select id, name, age from t_person"]

遍歷查詢結果:[set next]

拿到每條數的姓名:NSString *name = [set stringForColumnIndex:1];

也能夠這樣拿到每條數據的姓名:NSString *name = [result stringForColumn:@"name"];

FMResultSet *set = [db executeQuery:@"select id, name, age from t_person"];
    while ([set next]) {
        int ID = [set intForColumnIndex:0];
        NSString *name = [set stringForColumnIndex:1];
        int age = [set intForColumnIndex:2];
        NSLog(@"%d,%@,%d",ID,name,age);
    }
}
複製代碼

6. 刪除表

刪除指定表:[db executeUpdate:@"drop table if exists t_person"]

-(void)dropTable {
    BOOL success = [db executeUpdate:@"drop table if exists t_person"];
    if (success) {
        NSLog(@"刪除表成功");
    } else {
        NSLog(@"刪除表失敗");
    }
}
複製代碼

FMDatabaseQueue基本使用

FMDatabase是線程不安全的,當FMDB數據存儲想要使用多線程的時候,FMDatabaseQueue就派上用場了。

初始化FMDatabaseQueue的方法與FMDatabase相似

//數據文件路徑
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [path stringByAppendingPathComponent:@"student.sqlite"];
    
//初始化FMDatabaseQueue
FMDatabaseQueue *dbQueue = [FMDatabaseQueue databaseQueueWithPath:filePath];
複製代碼

在FMDatabaseQueue中執行命令的時候也是很是方便,直接在一個block中進行操做

-(void)FMDdatabaseQueueFunction {
    
    NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
    //數據文件路徑
    NSString *filePath = [path stringByAppendingPathComponent:@"student.sqlite"];
    //初始化FMDatabaseQueue
    FMDatabaseQueue *dbQueue = [FMDatabaseQueue databaseQueueWithPath:filePath];
    
    //在block中執行SQLite語句命令
    [dbQueue inDatabase:^(FMDatabase * _Nonnull db) {
        //建立表
        [db executeUpdate:@"create table if not exists t_student (id integer primary key autoincrement, name text, age integer)"];
        //添加數據
        [db executeUpdate:@"insert into t_student(name,age) values(?,?)",@"jack",@17];
        [db executeUpdate:@"insert into t_student(name,age) values(?,?)",@"lily",@16];
        //查詢數據
        FMResultSet *set = [db executeQuery:@"select id, name, age from t_student"];
        //遍歷查詢到的數據
        while ([set next]) {
            int ID = [set intForColumn:@"id"];
            NSString *name = [set stringForColumn:@"name"];
            int age = [set intForColumn:@"age"];
            NSLog(@"%d,%@,%d",ID,name,age);
        }
        
    }];
}
複製代碼

FMDB中的事務

什麼是事務?

事務(Transaction)是不可分割的一個總體操做,要麼都執行,要麼都不執行。 舉個例子,幼兒園有20位小朋友由老師組織出去春遊,返校的時候,全部人依次登上校車,這時候若是有一位小朋友沒有上車,車也是不能出發的。因此哪怕19人都上了車,也等於0人上車。20人是一個總體。 固然這個例子可能不是很精準。

FMDB中有事務的回滾操做,也就是說,當一個總體事務在執行的時候出了一點小問題,則執行回滾,以後這套事務中的全部操做將總體無效。

下面代碼中,利用事務循環向數據庫中添加2000條數據,假如在添加的過程當中出現了一些問題,因爲執行了*rollback = YES的回滾操做,數據庫中一個數據都不會出現。 若是第2000條數據的添加出了問題,哪怕以前已經添加了1999條數據,因爲執行了回滾,數據庫中依然一個數據都沒有。

//數據庫路徑
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [path stringByAppendingPathComponent:@"student.sqlite"];

//初始化FMDatabaseQueue
FMDatabaseQueue *dbQueue = [FMDatabaseQueue databaseQueueWithPath:filePath];

//FMDatabaseQueue的事務inTransaction
[dbQueue inTransaction:^(FMDatabase * _Nonnull db, BOOL * _Nonnull rollback) {
        //建立表
        [db executeUpdate:@"create table if not exists t_student (id integer primary key autoincrement, name text, age integer)"];
        //循環添加2000條數據
        for (int i = 0; i < 2000; i++) {
            BOOL success = [db executeUpdate:@"insert into t_student(name,age) values(?,?)",@"jack",@(i)];
            //若是添加數據出現問題,則回滾
            if (!success) {
                //數據回滾
                *rollback = YES;
                return;
            }
        }
    }];
複製代碼

7、Core Data

Core Data有着圖形化的操做界面,而且是操做模型數據的,更加面向對象。當你瞭解並熟悉它的時候,相信你會喜歡上這個數據庫存儲框架。

利用Core Data快速實現數據存儲

1. 圖形化建立模型

建立項目的時候,勾選下圖中的Use Core Data選項,工程中會自動建立一個數據模型文件。固然,你也能夠在開發中本身手動建立。

自動建立模型文件

下圖就是自動建立出來的文件

建立出來的文件

若是沒有勾選,也能夠在這裏手動建立。

手動建立

點擊Add Entity以後,至關一張數據表。表的名稱本身在上方定義,注意首字母要大寫。 在界面中還能夠爲數據實體添加屬性和關聯屬性。

建立一個數據表

Core Data屬性支持的數據類型以下

數據類型

編譯以後,Xcode會自動生成Person的實體代碼文件,而且文件不會顯示在工程中,若是下圖中右側Codegen選擇Manual/None,則Xcode就不會自動生成代碼,咱們能夠本身手動生成。

6.png

手動生成實體類代碼,選中CoreDataTest.xcdatamodeld文件,而後在Mac菜單欄中選擇Editor,以下圖所示。一路Next就能夠了。 若是沒有選擇Manual/None,依然進行手動建立的話,則會與系統自動建立的文件發生衝突,這點須要注意。 你也能夠不要選擇Manual/None,直接使用系統建立好的NSManagedObject,一樣會有4個文件,只是在工程中是看不到的,使用的時候直接導入#import "Person+CoreDataClass.h"頭文件就能夠了。

手動建立NSManagedObject

手動建立出來的是這樣4個文件

10.png

還要注意編程語言的選擇,Swift或OC

編程語言

2.Core Data堆棧的介紹與使用

下面要作的就是對Core Data進行初始化,實現本地數據的保存。 須要用到的類有三個:

NSManagedObjectModel 數據模型的結構信息 NSPersistentStoreCoordinator 數據持久層和對象模型協調器 NSManagedObjectContext 模型managedObject對象的上下文

以下圖所示,一個context內能夠有多個模型對象,不過在大多數的操做中只存在一個context,而且全部的對象存在於那個context中。 對象和他們的context是相關聯的,每一個被管理的對象都知道本身屬於哪一個context,每一個context都知道本身管理着哪些對象。

Core Data從系統讀或寫的時候,有一個持久化存儲協調器(persistent store coordinator),而且這個協調器在文件系統中與SQLite數據庫交互,也鏈接着存放模型的上下文Context。

Core Data

下圖是比較經常使用的方式:

Core Data堆棧

下面咱們來一步步實現Core Data堆棧的建立。

  • 首先在AppDelegate中定義一個NSManagedObjectModel屬性。而後利用懶加載來建立NSManagedObjectModel對象。而且要注意建立時候的後綴用momd,代碼以下:
//建立屬性
@property (nonatomic, readwrite, strong) NSManagedObjectModel *managedObjectModel;

//懶加載
- (NSManagedObjectModel *)managedObjectModel {
    if (!_managedObjectModel) {
        //注意擴展名爲 momd
        NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"CoreDataTest" withExtension:@"momd"];
        _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    }
    return _managedObjectModel;
}
複製代碼
  • 建立協調器NSPersistentStoreCoordinator,一樣的先在AppDelegate中來一個屬性,而後懶加載。
//屬性
@property (nonatomic, readwrite, strong) NSPersistentStoreCoordinator *persistentStoreCoordinator;

//懶加載
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    
    if (!_persistentStoreCoordinator) {
        
        //傳入以前建立好了的Model
        _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
        
        //指定sqlite數據庫文件
        NSURL *sqliteURL = [[self documentDirectoryURL] URLByAppendingPathComponent:@"CoreDataTest.sqlite"];
        
        //這個options是爲了進行數據遷移用的,有興趣的能夠去研究一下。
        NSDictionary *options=@{NSMigratePersistentStoresAutomaticallyOption:@(YES),NSInferMappingModelAutomaticallyOption:@(YES)};
        NSError *error;
        
        [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                  configuration:nil
                                                            URL:sqliteURL
                                                        options:options
                                                          error:&error];
        if (error) {
            NSLog(@"建立協調器失敗: %@", error.localizedDescription);
        }
    }
    return _persistentStoreCoordinator;
}


//獲取document目錄
- (nullable NSURL *)documentDirectoryURL {
    return [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject;
}
複製代碼
  • 建立NSManagedObjectContext,一樣的是屬性+懶加載。
//屬性
@property (nonatomic, readwrite, strong) NSManagedObjectContext *context;

//懶加載
- (NSManagedObjectContext *)context {
    if (!_context) {
        
        _context = [[NSManagedObjectContext alloc ] initWithConcurrencyType:NSMainQueueConcurrencyType];
        
        // 指定協調器
        _context.persistentStoreCoordinator = self.persistentStoreCoordinator;
    }
    return _context;
}
複製代碼

3.運用Core Data對數據進行增刪改查

  • 添加數據 使用NSEntityDesctiption類的一個方法建立NSManagedObject對象。參數一是實體類的名字,參數二是以前建立的Context。 爲對象賦值,而後存儲。
@implementation ViewController {
    NSManagedObjectContext *context;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //拿到managedObjectContext
    context = [AppDelegate new].context;
    
    //運用NSEntityDescription建立NSManagedObject對象
    Person *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:context];
    //爲對象賦值
    person.name = @"Jack";
    person.age = 17;
    NSError *error;
    //保存到數據庫
    [context save:&error];
}
複製代碼
  • 查詢數據 Core Data從數據庫中查詢數據,會用到三個類:

**NSFetchRequest:**一條查詢請求,至關於 SQL 中的select語句 **NSPredicate:**謂詞,指定一些查詢條件,至關於 SQL 中的where **NSSortDescriptor:**指定排序規則,至關於 SQL 中的 order by

NSFetchRequest中有兩個屬性:

**predicate:**是NSPredicate對象 **sortDescriptors:**它是一個NSSortDescriptor數組,數組中前面的優先級比後面高。能夠有多個排列規則。

//更多NSFetchRequest屬性
    fetchLimit:結果集最大數,至關於 SQL 中的limit
    fetchOffset:查詢的偏移量,默認爲0
    fetchBatchSize:分批處理查詢的大小,查詢分批返回結果集
    entityName/entity:數據表名,至關於 SQL中的from
    propertiesToGroupBy:分組規則,至關於 SQL 中的group by
    propertiesToFetch:定義要查詢的字段,默認查詢所有字段
複製代碼

設置好NSFetchRequest以後,調用NSManagedObjectContext的executeFetchRequest方法,就會返回結果集了。

//Xcode自動建立的NSManagedObject會生成fetchRequest方法,能夠直接獲得NSFetchRequest
    NSFetchRequest *fetchRequest = [Person fetchRequest];
    //也能夠這樣得到:[NSFetchRequest fetchRequestWithEntityName:@"Student"];
    
    //謂詞
    fetchRequest.predicate = [NSPredicate predicateWithFormat:@"age == %@", @(16)];
    
    //排序
    NSArray<NSSortDescriptor *> *sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"age" ascending:YES]];
    
    fetchRequest.sortDescriptors = sortDescriptors;

    //運用executeFetchRequest方法獲得結果集
    NSArray<Person *> *personResult = [context executeFetchRequest:fetchRequest error:nil];
複製代碼

Xcode本身有一個NSFetchRequest的code snippet,「fetch」,出現的結果以下圖。

snippet

NSFetchRequest的code snippet: 「fetch」

  • 更新數據 更新數據比較簡單,查詢出來須要修改的數據以後,直接修改值,而後用context save就能夠了。
for (Person *person in personResult) {
        //直接修改
        person.age = 26;
    }

//別忘了save一下
[context save:&error];
複製代碼
  • 刪除數據 查詢出來須要刪除的數據以後,調用 NSManagedObjectContext 的deleteObject方法就能夠了。
for (Person *person in personResult) {
        //刪除數據
        [context deleteObject:person];
    }
//別忘了save
[context save:&error];
複製代碼

至此Core Data的基礎內容就講完了。


後記:

iOS中有多種數據持久化的方法,要根據具體情景選用最合適的技術。

推薦簡單又好用的分類集合:WHKit github地址:github.com/remember17

相關文章
相關標籤/搜索