iOS 數據存儲(持久化存儲、緩存)

存儲的基礎知識

應用沙盒

應用沙盒文件夾
  • Application(應用程序包):包含了所有的資源文件和和可執行文件,上架前經過數字簽名,上架後不可修改。
  • Documents:文檔目錄,要保存程序生成的數據,會自動被分到iCloud中。保存應用運行時生成的需要持久化的數據,iTunes同步設備時會備份該目錄。例如,遊戲應用可將遊戲存檔保存在該目錄。(注意點:不要保存從網絡上下載的文件,否則會無法上架!)
  • Library:
    1. 用戶偏好,使用 NSUserDefault 直接讀寫!
    2. 如果要想數據及時寫入磁盤,還需要調用一個同步方法
    3. 保存臨時文件,"後續需要使用",例如:緩存圖片,離線數據(地圖數據
    4. 系統不會清理 cache 目錄中的文件,就要求程序開發時,"必須提供 cache 目錄的清理解決方案"
    5. Caches:存放體積大又不需要備份的數據
    6. Preference:保存應用的所有偏好設置,iCloud會備份設置信息
  • Tmp:臨時文件,系統會自動清理。重新啓動就會清理。保存應用運行時所需的臨時數據,使用完畢後再將相應的文件從該目錄刪除。應用沒有運行時,系統也可能會清除該目錄下的文件。iTunes同步設備時不會備份該目錄。
    1. 存放臨時文件,不會被備份,而且這個文件下的數據有可能隨時被清除的可能
    2. 保存臨時文件,"後續不需要使用"
    3. tmp 目錄中的文件,系統會自動清理
    4. 重新啓動手機,tmp 目錄會被清空
    5. 系統磁盤空間不足時,系統也會自動清理

作爲一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流羣:834688868,不管你是大牛還是小白都歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 大家一起交流學習成長!

  • 文末有驚喜,請一定看到最後!
應用沙盒目錄的常見獲取方式
  • 沙盒根目錄:NSString *home = NSHomeDirectory();

  • Documents:(2種方式)

    1. 利用沙盒根目錄拼接”Documents”字符串(不建議採用,因爲新版本的操作系統可能會修改目錄名)

      NSString *home = NSHomeDirectory();
      NSString *documents = [home stringByAppendingPathComponent:@"Documents"];
      複製代碼
      
    2. 利用 NSSearchPathForDirectoriesInDomains 函數

      // NSUserDomainMask 代表從用戶文件夾下找
      // YES 代表展開路徑中的波浪字符“~”
      NSArray *array =  NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, NO);
      // 在iOS中,只有一個目錄跟傳入的參數匹配,所以這個集合裏面只有一個元素
      NSString *documents = [array objectAtIndex:0];
      複製代碼
      
  • tmp:NSString *tmp = NSTemporaryDirectory();

  • Library/Caches:(跟Documents類似的2種方法)

    1. 利用沙盒根目錄拼接”Caches”字符串
    2. 利用 NSSearchPathForDirectoriesInDomains 函數 (將函數的第2個參數改爲:NSCachesDirectory即可)
  • Library/Preference:通過NSUserDefaults類存取該目錄下的設置信息。

內存緩存和磁盤緩存的區別

緩存分爲內存緩存和磁盤緩存兩種。
其中內存是指當前程序的運行空間,緩存速度快容量小,是臨時存儲文件用的,供CPU直接讀取,比如說打開一個程序,他是在內存中存儲,關閉程序後內存就又回到原來的空閒空間;
磁盤是程序的存儲空間,緩存容量大、速度慢、可持久化。與內存不同的是磁盤是永久存儲東西的,只要裏面存放東西,不管運行不運行 ,他都佔用空間!磁盤緩存是存在Library/Caches

內存分區

iOS內存分爲5個區:棧區,堆區,全局區,常量區,代碼區(從左到右由高地址像低地址)。

  • 棧區stack:這一塊區域系統會自己管理,我們不用幹預,主要存一些局部變量,以及函數跳轉時的現場保護。因此大量的局部變量,深遞歸,函數循環調用都可能導致內存耗盡而運行崩潰。
  • 堆區heap:與棧區相對,這一塊一般由我們自己管理,比如alloc,free的操作,存儲一些自己創建的對象。
  • 全局區(靜態區static):全局變量和靜態變量都存儲在這裏,已經初始化的和沒有初始化的會分開存儲在相鄰的區域,程序結束後系統會釋放。
  • 常量區:存儲常量字符串和const常量
  • 代碼區:存儲代碼

NSCache

NSCache是蘋果提供的一套緩存機制,用法和NSMutableDictionary類似,在AFNetworking,SDWebImage,Kingfisher中都有用到。

當內存不足時NSCache會自動釋放內存。 NSCache設置緩存對象數量和佔用的內存大小,當緩存超出了設置會自動釋放內存。
NSCache是Key-Value數據結構,其中key是強引用,不實現NSCoping協議,作爲key的對象不會被拷貝。

NSCache的屬性

countLimit: 能夠緩存對象的最大數量,默認值是0,沒有限制。
totalCostLimit: 設置緩存佔用的內存大小 evictsObjectsWithDiscardedContent: 是否回收廢棄內容,默認YES

NSCache的方法

objectForKey: 通過key獲得緩存對象。
setObject: forKey: 緩存對象。
setObject: forKey: cost: 緩存對象,並指定key值對應的成本,用於計算緩存中所有對象的總成本。
removeObjectForKey: 刪除指定對象。
removeAllObjects: 刪除所有緩存對象。

NSCacheDelegate代理

willEvictObject: 緩存對象即將被清理時調用,一般開發者用來調試,不能在此方法中修改緩存。

在下列場景中會被調用:

  1. removeObjectForKey
  2. 緩存對象超過NSCache的countLimit和otalCostLimit屬性設置的限制
  3. App進入後臺
  4. 系統發出內存警告
  5. cache這個實例的生命週期結束前

NSCache需要注意的點

當收到內存警告,而我們又調用removeAllObjects,則無法再繼續往緩存中添加數據。
不提供緩存總的大小,想知道NSCache佔用的內存大小,只有通過添加緩存的cost自己計算。
NSCache自動釋放內存的算法是不確定的,有時是按照LRU(最近最久未使用)釋放,有時隨機釋放。
NSCache中的數據在APP重啓後會消失,因爲NSCache只是將數據保存在內存 的。

  • NSCache和NSMutableDictionary的區別
    NSCache是線程安全的,不需要加線程鎖,而NSMutableDictionary線程不安全。

代碼舉例

@interface NSCacheVC ()<NSCacheDelegate>
@property (nonatomic, strong) NSCache *myCache;
@end

@implementation NSCacheVC

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.view.backgroundColor = [UIColor purpleColor];

    self.myCache = [[NSCache alloc]init];
    self.myCache.delegate = self;

    for (int i = 0; i<10; i++) {
        [self.myCache setObject:[NSString stringWithFormat:@"%d", i] forKey:@(i) cost: 1];
    }

    for (int i = 0; i<10; i++) {
        NSLog(@"NSCache取出---%@", [self.myCache objectForKey:@(i)]);
    }

    /// 清除緩存
    [self.myCache removeAllObjects];
    /// 設置緩存限制
    self.myCache.totalCostLimit = 5;

    NSLog(@"設置緩存限制後=================");

    for (int i = 0; i<10; i++) {
        // 設置成本數爲1
        [self.myCache setObject:[NSString stringWithFormat:@"%d", i] forKey:@(i) cost: 1];
    }

    for (int i = 0; i<10; i++) {
        NSLog(@"NSCache取出---%@", [self.myCache objectForKey:@(i)]);
    }

    /// 清除緩存
    [self.myCache removeAllObjects];
    NSLog(@"設置緩存限制後但未設置成本數cost=================");

    for (int i = 0; i<10; i++) {
        [self.myCache setObject:[NSString stringWithFormat:@"%d", i] forKey:@(i)];
    }

    for (int i = 0; i<10; i++) {
        NSLog(@"NSCache取出---%@", [self.myCache objectForKey:@(i)]);
    }

    /// 清除緩存
    [self.myCache removeAllObjects];

}

// 即將回收對象的時候進行調用,實現代理方法之前要遵守NSCacheDelegate協議。
- (void)cache:(NSCache *)cache willEvictObject:(id)obj{
    NSLog(@"NSCache回收---%@", obj);
}

@end
複製代碼

打印結果

從打印結果可以看看出:

  1. 設置緩存限制且知道緩存成本數時,超出是會自動回收。但是設置緩存限制但不知道緩存成本數時不會自動回收。

  2. 回收時會調用 willEvictObject 的代理方法。

持久化存儲方式

  • Plist(NSArray\NSDictionary),只存儲數組、字典,但是數組和字典裏不能有自定義對象。
  • 偏好設置(Preference\NSUserDefaults),也不能存儲自定義對象。
  • 歸檔NSCoding(NSKeyedArchiver\NSkeyedUnarchiver),存儲自定義對象。
    侷限:一次性讀取和存儲操作。
  • SQLite3
    • 操作數據比較快
    • 可以局部讀取
    • 比較小型,佔用的內存資源比較少
  • Core Data

Plist

屬性列表是一種XML格式的文件,拓展名爲plist如果對是NSString、NSDictionary、NSArray、NSData、NSNumber等類型,就可以使用writeToFile:atomically:方法直接將對象寫到屬性列表文件中。

屬性列表-歸檔NSDictionary將一個NSDictionary對象歸檔到一個plist屬性列表中

// 將數據封裝成字典
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:@"張三" forKey:@"name"];
[dict setObject:@"155xxxxxxx" forKey:@"phone"];
[dict setObject:@"27" forKey:@"age"];
// 將字典持久化到Documents/stu.plist文件中
[dict writeToFile:path atomically:YES];
複製代碼

屬性列表-恢復NSDictionary讀取屬性列表,恢復NSDictionary對象

// 讀取Documents/stu.plist的內容,實例化
NSDictionaryNSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
NSLog(@"name:%@", [dict objectForKey:@"name"]);
NSLog(@"phone:%@", [dict objectForKey:@"phone"]);
NSLog(@"age:%@", [dict objectForKey:@"age"]);
複製代碼

屬性列表-NSDictionary的存儲和讀取過程

偏好設置

很多iOS應用都支持偏好設置,比如保存用戶名、密碼、字體大小等設置,iOS提供了一套標準的解決方案來爲應用加入偏好設置功能。
每個應用都有個NSUserDefaults實例,通過它來存取偏好設置。

比如,保存用戶名、字體大小、是否自動登錄:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:@"張三" forKey:@"username"];
[defaults setFloat:18.0f forKey:@"text_size"];
[defaults setBool:YES forKey:@"auto_login"];
複製代碼

讀取上次保存的設置

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *username = [defaults stringForKey:@"username"];
float textSize = [defaults floatForKey:@"text_size"];
BOOL autoLogin = [defaults boolForKey:@"auto_login"];
複製代碼

注意: UserDefaults設置數據時,不是立即寫入,而是根據時間戳定時地把緩存中的數據寫入本地磁盤。所以調用了set方法之後數據有可能還沒有寫入磁盤應用程序就終止了。出現以上問題,可以通過調用synchornize方法[defaults synchornize];強制寫入。

歸解檔

NSKeyedArchiver如果對象是NSString、NSDictionary、NSArray、NSData、NSNumber等類型,可以直接用NSKeyedArchiver進行歸檔和恢復。 不是所有的對象都可以直接用這種方法進行歸檔,只有遵守了NSCoding協議的對象纔可以。

  • NSCoding 協議有2個方法:
    1. encodeWithCoder: 每次歸檔對象時,都會調用這個方法。一般在這個方法裏面指定如何歸檔對象中的每個實例變量,可以使用encodeObject:forKey:方法歸檔實例變量。
    2. initWithCoder: 每次從文件中恢復(解碼)對象時,都會調用這個方法。一般在這個方法裏面指定如何解碼文件中的數據爲對象的實例變量,可以使用decodeObject:forKey方法解碼實例變量。
歸檔一個NSArray對象到Documents/array.archive
NSArray *array = [NSArray arrayWithObjects:@”a”,@”b”,nil];
[NSKeyedArchiver archiveRootObject:array toFile:path];
複製代碼
NSKeyedArchiver-歸檔Person對象(Person.h)
@interface Person : NSObject, NSCoding
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) float height;
@end

@implementation Person
- (void)encodeWithCoder:(NSCoder *)encoder {
    [encoder encodeObject:self.name forKey:@"name"];
    [encoder encodeInt:self.age forKey:@"age"];
    [encoder encodeFloat:self.height forKey:@"height"];
}
- (id)initWithCoder:(NSCoder *)decoder {
    self.name = [decoder decodeObjectForKey:@"name"];
    self.age = [decoder decodeIntForKey:@"age"];
    self.height = [decoder decodeFloatForKey:@"height"];
    return self;
}
@end

// 歸檔(編碼)
Person *person = [[Person alloc] init];
person.name = @"xxx";
person.age = 27;
person.height = 1.83f;
[NSKeyedArchiver archiveRootObject:person toFile:path];
// 恢復(解碼)
Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
複製代碼

如果父類也遵守了NSCoding協議,請注意: 應該在encodeWithCoder:方法中加上一句[super encodeWithCode:encode];確保繼承的實例變量也能被編碼,即也能被歸檔。 應該在initWithCoder:方法中加上一句self = [super initWithCoder:decoder];確保繼承的實例變量也能被解碼,即也能被恢復。

NSData -- 歸檔

使用 archiveRootObject:toFile: 方法可以將一個對象直接寫入到一個文件中,但有時候可能想將多個對象寫入到同一個文件中,那麼就要使用NSData來進行歸檔對象。 NSData可以爲一些數據提供臨時存儲空間,以便隨後寫入文件,或者存放從磁盤讀取的文件內容。可以使用[NSMutableData data]創建可變數據空間。

歸檔(編碼)

// 新建一塊可變數據區
NSMutableData *data = [NSMutableData data];
// 將數據區連接到一個NSKeyedArchiver對象
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
// 開始存檔對象,存檔的數據都會存儲到NSMutableData中
[archiver encodeObject:person1 forKey:@"person1"];
[archiver encodeObject:person2 forKey:@"person2"];
// 存檔完畢(一定要調用這個方法)
[archiver finishEncoding];
// 將存檔的數據寫入文件
[data writeToFile:path atomically:YES];
複製代碼

NSData-從同一文件中恢復2個Person對象恢復(解碼)

// 從文件中讀取數據
NSData *data = [NSData dataWithContentsOfFile:path];
// 根據數據,解析成一個NSKeyedUnarchiver對象
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
Person *person1 = [unarchiver decodeObjectForKey:@"person1"];
Person *person2 = [unarchiver decodeObjectForKey:@"person2"];
// 恢復完畢
[unarchiver finishDecoding];
複製代碼

利用歸檔實現深複製比如對一個Person對象進行深複製

// 臨時存儲person1的數據
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:person1];
// 解析data,生成一個新的Person對象
Student *person2 = [NSKeyedUnarchiver unarchiveObjectWithData:data];
// 分別打印內存地址
NSLog(@"person1:0x%x", person1);  // person1:0x7177a60
NSLog(@"person2:0x%x", person2); // person2:0x7177cf0
複製代碼

Core Data

Core Data 中的三個對象
  • NSManagedObject
    只要定義一個類繼承該類就會創建一張與之對應的表,也就是一個繼承與該類的類就對應一張表,每一個通過繼承該類創建出來的對象,都是該類對應的表中的一條數據。
  • NSManagedObjectContext
    用於操作數據庫,只要有類它就能對數據庫的表進行增刪改查
  • NSPersistentStoreCoordinator
    決定存儲的位置
Core data􏲅􏰀􏰴􏰭􏱸􏰔􏰝􏰴􏱤􏱱􏱢􏰌􏰢􏰇􏲆􏱌􏲀􏱃􏱕􏰽􏰐􏱘􏱦 本身並不是一個併發安全的架構,所以在多線程中實現 Core data􏰋􏱡􏱈􏱗 會有問題
  • 原因􏱈􏱗􏲀􏰒: CoreData 中的 􏰐􏰌NSManagedObjectContext􏲀􏱃􏱕􏰽􏰐􏰭􏱱􏱢 在多線程中不安。
  • 如果想要多線程訪問 CoreData􏰌􏰂􏱬􏱹􏰅􏰌􏰖􏰁􏱸􏰔􏰝􏱕􏰽􏰔􏰝, 最好的方法是一個線程一個 NSManagedObjectContext
  • 每個 􏰾􏰝NSManagedObjectContext􏱋􏱣􏱘􏱨􏰪􏱊􏱌􏱳􏱖􏰯􏰔􏰝 都可以使用同一個 NSPersistentStoreCoordinator 實例,因爲􏱘􏱨􏱬􏱙􏱸􏱒􏱜NSManagedObjectContext􏰋􏲀􏰶􏱖 會在使用 NSPersistentStoreCoordinator􏱓􏱺􏱝 前上鎖。

SQLite3

在 iOS 中使用 SQLite3,首先要添加庫文件 libsqlite3.dylib 和導入主頭文件 #import<splite3.h>

  • 什麼是 SQLite
    SQLite 是一款嵌入式的輕量關係型文件數據庫。
    它佔用資源非常的低,在嵌入式設備中,可能只需要幾百K的內存就夠了。
    它的處理速度比 Mysql、PostgreSQL 這兩款著名的數據庫都還快。

  • 什麼是數據庫
    數據庫(Database)是按照數據結構來組織、存儲和管理數據的倉庫。
    數據庫可以分爲2大種類:關係型數據庫(主流)、對象型數據庫。

  • 常用關係型數據庫
    PC端:Oracle、MySQL、SQL Server、Access、DB2、Sybase。
    嵌入式\移動客戶端:SQLite。

  • 數據庫是如何存儲數據的
    數據庫的存儲結構和excel很像,以表(table)爲單位

  • 數據庫存儲數據的步驟
    新建一張表(table)。
    添加多個字段(column,列,屬性)。
    添加多行記錄(row,每行存放多個字段對應的值)。

一些名詞

主鍵
  • 主鍵約束 如果t_student表中就name和age兩個字段,而且有些記錄的name和age字段的值都一樣時,那麼就沒法區分這些數據,造成數據庫的記錄不唯一,這樣就不方便管理數據。良好的數據庫編程規範應該要保證每條記錄的唯一性,爲此,增加了主鍵約束。也就是說,每張表都必須有一個主鍵,用來標識記錄的唯一性。
  • 什麼是主鍵 主鍵(Primary Key,簡稱PK)用來唯一地標識某一條記錄。 例如t_student可以增加一個id字段作爲主鍵,相當於人的身份證。 主鍵可以是一個字段或多個字段。
  • 主鍵的設計原則
    1. 主鍵應當是對用戶沒有意義的
    2. 永遠也不要更新主鍵
    3. 主鍵不應包含動態變化的數據
    4. 主鍵應當由計算機自動生成
  • 主鍵的聲明
    1. 在創表的時候用primary key聲明一個integer類型的id作爲t_student表的主鍵: create table t_student (id integer primary key, name text, age integer);
    2. 主鍵字段 只要聲明爲primary key,就說明是一個主鍵字段。 主鍵字段默認就包含了not null 和 unique 兩個約束。
    3. 如果想要讓主鍵自動增長(必須是integer類型),應該增加 autoincrement create table t_student (id integer primary key autoincrement, name text, age integer);
外鍵
  • 外鍵約束 利用外鍵約束可以用來建立表與表之間的聯繫。 外鍵的一般情況是:一張表的某個字段,引用着另一張表的主鍵字段。
  • 新建一個外鍵 create table t_student (id integer primary key autoincrement, name text, age integer, class_id integer, constraint fk_t_student_class_id_t_class_id foreign key (class_id) references t_class (id); t_student表中有一個叫做fk_t_student_class_id_t_class_id的外鍵。 這個外鍵的作用是用t_student表中的class_id字段引用t_class表的id字段。
字段類型
  • SQLite 將數據劃分爲以下幾種存儲類型: integer: 整型數據,⼤大⼩小爲4個字節。 bigint: 整型數據,⼤大⼩小爲8個字節。 smallint: 整數數據,⼤大⼩小爲 2 個字節。 tinyint: 從0到255的整數數據,存儲⼤大⼩小爲 1 字節。 float: 4字節浮點數。 double: 8字節浮點數。 real: 8字節浮點數。 text : 文本字符串。 blob : 二進制數據(比如文件)。

  • 其它類型 null: 空值。 blob: 二進制對象。 default: 缺省值 primary key: 主鍵 autoincrement: 主鍵⾃自動增⻓長

  • 實際上SQLite是無類型的 就算聲明爲integer類型,還是能存儲字符串文本(主鍵除外)。 建表時聲明啥類型或者不聲明類型都可以,也就意味着創表語句可以這麼寫:create table t_student(name, age); 爲了保持良好的編程規範、方便程序員之間的交流,編寫建表語句的時候最好加上每個字段的具體類型。

SQL語句

  • SQL語句的種類

    1. 數據定義語句(DDL:Data Definition Language) 包括create和drop等操作 在數據庫中創建新表或刪除表(create table或 drop table)
    2. 數據操作語句(DML:Data Manipulation Language) 包括insert、update、delete等操作 上面的3種操作分別用於添加、修改、刪除表中的數據
    3. 數據查詢語句(DQL:Data Query Language) 可以用於查詢獲得表中的數據 關鍵字select是DQL(也是所有SQL)用得最多的操作
      其他DQL常用的關鍵字有where,order by,group by和having
  • SQL語句的特點 不區分大小寫(比如數據庫認爲user和UsEr是一樣的)。 每條語句都必須以分號 ; 結尾。

  • SQL中的常用關鍵字有 select、insert、update、delete、from、create、where、desc、order、by、group、table、alter、view、index 等等。

  • 數據庫中不可以使用關鍵字來命名錶、字段。

創表

格式: create table 表名 (字段名1 字段類型1, 字段名2 字段類型2, …); create table if not exists 表名 (字段名1 字段類型1, 字段名2 字段類型2, …); 示例:

create table t_student (id integer, name text, age inetger, score real);
create table if not exists Student (
    ID integer primary key autoincrement,
    Name varchar(128),
    Age integer,
    Class interger default 0,
    RegisterTime datetime,
    Money float default 0,
    Birthday date
);
複製代碼
刪表

格式: drop table 表名; drop table if exists 表名; 示例:

drop table t_student;
複製代碼
插入數據(insert)

格式: insert into 表名 (字段1, 字段2, …) values (字段1的值, 字段2的值, …); 示例:

insert into t_student (name, age) values (‘張三’, 10);
複製代碼

注意: 數據庫中的字符串內容應該用單引號 ’ 括住; 所有字符串必須要加 ' ' 單引號; 整數,浮點數不⽤用加 ' ‘; 日期需要加單引號 ' ‘; 字段順序沒有關係; 對於⾃自動增⻓長的主鍵不需要插⼊入字段;

更新數據(update)

格式: update 表名 set 字段1 = 字段1的值, 字段2 = 字段2的值, … ; 示例:

update t_student set name = ‘jack’, age = 20;
複製代碼

注意: 上面的示例會將t_student表中所有記錄的name都改爲jack,age都改爲20

刪除數據(delete)

格式: delete from 表名; 示例:

// 刪除指定ID值爲2的記錄
delete from t_student where ID=2; 
// 刪除t_student表中所有的記錄(慎重)
delete from t_student;
複製代碼

注意: 上面的示例會將t_student表中所有記錄都刪掉

DQL語句 -- 條件語句where

如果只想更新或者刪除某些固定的記錄,那就必須在DML語句後加上一些條件

  • 條件語句的常見格式 where 字段 = 某個值 ; // 不能用兩個 = where 字段 is 某個值 ; // is 相當於 = where 字段 != 某個值 ; where 字段 is not 某個值 ; // is not 相當於 != where 字段 > 某個值 ; where 字段1 = 某個值 and 字段2 > 某個值 ; // and相當於C語言中的 && where 字段1 = 某個值 or 字段2 = 某個值 ; // or 相當於C語言中的 ||

示例:

// 將t_student表中年齡大於10 並且 姓名不等於jack的記錄,年齡都改爲 5
update t_student set age = 5 where age > 10 and name != ‘jack’;
// 刪除t_student表中年齡小於等於10 或者 年齡大於30的記錄
delete from t_student where age <= 10 or age > 30;
// 將t_student表中名字等於jack的記錄,score字段的值 都改爲 age字段的值  
update t_student set score = age where name = ‘jack’ ;
複製代碼
DQL語句 -- select

格式: select 字段1, 字段2, … from 表名; select * from 表名; // 查詢所有的字段 示例:

select name, age from t_student ;
select * from t_student ;
// 條件查詢
select * from t_student where age > 10 ; 
// 模糊查詢
select * from t_student where name  like  '%張%' or phone like '%張%';  
複製代碼
  • 起別名 格式(字段和表都可以起別名) select 字段1 別名 , 字段2 別名 , … from 表名 別名; select 字段1 別名, 字段2 as 別名, … from 表名 as 別名; select 別名.字段1, 別名.字段2, … from 表名 別名; 示例:

    // 給name起個叫做myname的別名,給age起個叫做myage的別名
    select name myname, age myage from t_student;
    // 給t_student表起個別名叫做s,利用s來引用表中的字段
    select s.name, s.age from t_student s;
    複製代碼
    
  • 計算記錄的數量 格式 select count (字段) from 表名; select count ( * ) from 表名; 示例:

    select count (age) from t_student ;
    select count ( * ) from t_student where score >= 60;
    select count(*) from t_student; 
    select avg(Age) from t_student; 
    select sum(Age) from t_student;
    複製代碼
    
  • 排序 按照某個字段的值,進行排序搜索

    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;
    複製代碼
    
DQL語句 -- limit

使用limit可以精確地控制查詢結果的數量,比如每次只查詢10條數據 格式: select * from 表名 limit 數值1, 數值2; 示例:

// 跳過最前面4條語句,然後取8條記錄
select * from t_student limit 4, 8;
// 取最前面的7條記錄
select * from t_student limit 7; // 相當於select * from t_student limit 0, 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 :指定字段的默認值 (建議:儘量給字段設定嚴格的約束,以保證數據的規範性) 示例: name字段不能爲null,並且唯一 age字段不能爲null,並且默認爲1 create table t_student (id integer, name text not null unique, age integer not null default 1);

表連接查詢

需要聯合多張表才能查到想要的數據 表連接的類型: 內連接:inner join 或者 join (顯示的是左右表都有完整字段值的記錄) 左外連接:left outer join (保證左表數據的完整性) 示例:

查詢0316iOS班的所有學生
select s.name, s.age from t_student s, t_class c where s.class_id = c.id and c.name = ‘0316iOS’;
複製代碼

數據庫操作語句

創建或打開數據庫
// path是數據庫文件的存放路徑
sqlite3 *db = NULL;
int result = sqlite3_open([path UTF8String], &db);
複製代碼

代碼解析: sqlite3_open()將根據文件路徑打開數據庫,如果不存在,則會創建一個新的數據庫。如果result等於常量SQLITE_OK,則表示成功打開數據庫。 sqlite3 *db:一個打開的數據庫實例。 數據庫文件的路徑必須以C字符串(而非NSString)傳入。

關閉數據庫

sqlite3_close(db);

執行不返回數據的SQL語句
char *errorMsg = NULL;  // 用來存儲錯誤信息
char *sql = "create table if not exists t_person(id integer primary key autoincrement, name text, age integer);"; // 執行創表語句
int result = sqlite3_exec(db, sql, NULL, NULL, &errorMsg);
複製代碼

代碼解析: sqlite3_exec()可以執行任何SQL語句,比如創表、更新、插入和刪除操作。但是一般不用它執行查詢語句,因爲它不會返回查詢到的數據 sqlite3_exec()還可以執行的語句:

  1. 開啓事務:begin transaction;
  2. 回滾事務:rollback;
  3. 提交事務:commit;
帶佔位符插入數據
char *sql = "insert into t_person(name, age) values(?, ?);";
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
    sqlite3_bind_text(stmt, 1, "母雞", -1, NULL);
    sqlite3_bind_int(stmt, 2, 27);
}
if (sqlite3_step(stmt) != SQLITE_DONE) {
    NSLog(@"插入數據錯誤");
}
sqlite3_finalize(stmt);
複製代碼

代碼解析: sqlite3_prepare_v2()返回值等於SQLITE_OK,說明SQL語句已經準備成功,沒有語法問題。 sqlite3_bind_text()大部分綁定函數都只有3個參數:

  1. 第1個參數是sqlite3_stmt *類型。
  2. 第2個參數指佔位符的位置,第一個佔位符的位置是1,不是0。
  3. 第3個參數指佔位符要綁定的值。
  4. 第4個參數指在第3個參數中所傳遞數據的長度,對於C字符串,可以傳遞-1代替字符串的長度。
  5. 第5個參數是一個可選的函數回調,一般用於在語句執行後完成內存清理工作。

sqlite_step():執行SQL語句,返回SQLITE_DONE代表成功執行完畢 sqlite_finalize():銷燬sqlite3_stmt *對象

查詢數據
char *sql = "select id,name,age from t_person;";
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
    while (sqlite3_step(stmt) == SQLITE_ROW) {
        int _id = sqlite3_column_int(stmt, 0);
        char *_name = (char *)sqlite3_column_text(stmt, 1);
        NSString *name = [NSString stringWithUTF8String:_name];
        int _age = sqlite3_column_int(stmt, 2);
        NSLog(@"id=%i, name=%@, age=%i", _id, name, _age);
    }
}
sqlite3_finalize(stmt);
複製代碼

代碼解析:
sqlite3_step(): 返回SQLITE_ROW代表遍歷到一條新記錄。
sqlite3_column_*(): 用於獲取每個字段對應的值,第2個參數是字段的索引,從0開始。

後期增加數據庫中的字段

􏰛􏰜􏰝􏰞􏰟􏰠􏰝􏰡􏰋􏰌􏰍􏰎1. 增加表字段 ALTER TABLE 表名􏰡􏰢 ADD COLUMN 􏰍􏰎􏰢字段名 字段類型􏰍􏰎􏰣􏰤; 2. 􏰥􏰦􏰡􏰍􏰎刪除表字段 ALTER TABLE 􏰡􏰢表名􏰡􏰢 DROP COLUMN 􏰍字段名 字段類型􏰍􏰎􏰣􏰤; 3. 􏰧􏰨􏰡􏰍􏰎修改表字段 ALTER TABLE 􏰡􏰢表名􏰡􏰢 RENAME COLUMN 􏰩􏰍􏰎􏰢舊字段名 TO 新字段名􏰪􏰍􏰎􏰢;

FMDB

  • 什麼是FMDB
    FMDB是iOS平臺的SQLite數據庫框架。
    FMDB以OC的方式封裝了SQLite的C語言API

  • FMDB的優點
    使用起來更加面向對象,省去了很多麻煩、冗餘的C語言代碼。
    對比蘋果自帶的Core Data框架,更加輕量級和靈活。
    提供了多線程安全的數據庫操作方法,有效地防止數據混亂。

FMDB有三個主要的類

FMDatabase:一個FMDatabase對象就代表一個單獨的SQLite數據庫 用來執行SQL語句。
FMResultSet:使用FMDatabase執行查詢後的結果集。
FMDatabaseQueue:用於在多線程中執行多個查詢或更新,它是線程安全的。

FMDB 使用

  1. 打開數據庫

通過指定SQLite數據庫文件路徑來創建FMDatabase對象 FMDatabase *db = [FMDatabase databaseWithPath:path]; if (![db open]) { NSLog(@"數據庫打開失敗!"); }

  • 文件路徑有三種情況
    具體文件路徑: 如果不存在會自動創建
    空字符串@"": 會在臨時目錄創建一個空的數據庫,當FMDatabase連接關閉時,數據庫文件也被刪除。
    nil; 會創建一個內存中臨時數據庫,當FMDatabase連接關閉時,數據庫會被銷燬。
  1. 執行更新
    在FMDB中,除查詢以外的所有操作,都稱爲“更新”。create、drop、insert、update、delete等。
    使用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"]
    複製代碼
    
  2. 執行查詢
    查詢方法

    - (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"];
    }
    複製代碼
    

FMDatabaseQueue

FMDatabase這個類是線程不安全的,如果在多個線程中同時使用一個FMDatabase實例,會造成數據混亂等問題。
爲了保證線程安全,FMDB提供方便快捷的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;

作爲一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流羣:834688868,不管你是大牛還是小白都歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 大家一起交流學習成長!

以下資料在羣文件可自行下載!

作者:orilme
鏈接:https://juejin.im/post/6891187709760110599

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章