iOS開發不徹底指南——數據存儲

原文連接html

有開發經驗的同窗應該都清楚,不論是前端,客戶端有些數據都須要在本地落地的;那麼這一章咱們就來一塊兒瞭解一下 iOS 的數據存取經常使用的方法。前端

在對 iOS 數據 進行具體的操做以前,咱們須要首先了解一下 Bundle 和沙盒的概念。java

1、Bundle 和沙盒

Bundle 和沙盒是 iOS 數據存儲的具體地方;linux

1. Bundle

Bundle 是一個目錄,其中包含了程序會使用到的資源.這些資源包含了如圖像,聲音,編譯好的代碼,nib 文件等待android

使用 pathForResource 的路徑爲真實環境 APP 下面的路徑ios

// 加載Info.plist資源,mainBundle的資源都是放到RootFolder下
    NSString *infoPath= [[NSBundle mainBundle] pathForResource:@"Info" ofType:@"plist"];
    NSLog(@"Info路徑爲: %@",infoPath);
    // 找到圖片路徑
    NSString *imgPath = [[NSBundle mainBundle] pathForResource:@"ff_IconAdd_25x25_@2x" ofType:@"png"];
    NSLog(@"圖片路徑爲: %@",imgPath);

    UIImageView *img = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
    img.image = [UIImage imageWithContentsOfFile:imgPath];

    [self.view addSubview:img];

複製代碼

輸出爲:git

Info路徑爲: /Users/xxx/Library/Developer/CoreSimulator/Devices/8EA1B565-17F4-47FF-8ECA-C23F3EF4594A/data/Containers/Bundle/Application/76750996-9428-448E-8662-553CA22DCB28/NSBundle的使用.app/Info.plist
圖片路徑爲: /Users/xxxx/Library/Developer/CoreSimulator/Devices/8EA1B565-17F4-47FF-8ECA-C23F3EF4594A/data/Containers/Bundle/Application/76750996-9428-448E-8662-553CA22DCB28/NSBundle的使用.app/ff_IconAdd_25x25_@2x.png
複製代碼

其餘經常使用的方法github

//獲取XML文件
NSString *filePath = [[NSBundle mainBundle] pathForResouse:@"re" ofType:@"xml"];
NSData *data = [[NSData alloc] initWithContentsOfFile:filePath];
//獲取app包的readme.txt文件路徑
NSString *path = [[NSBundle mainBundle] pathForResource:@"readme" ofType:@"txt"];


//app資源目錄路徑
NSString *resPath = [[NSBundle mainBundle] resourcePath];

//獲取app包路徑
NSString *path = [[NSBundle mainBundle] bundlePath];

//經過使用下面的方法獲得程序的main bundle
NSBundle *otherBundle = [NSBundle mainBundle];

//獲取資源目錄下a.bundle
NSString *path = [resPath stringByAppendingPathComponent:@"a.bundle"];
NSBundle *bundle = [NSBundle bundleWithPath:path];

//一旦咱們有了bundle,就能夠訪問其中的資源文件了。
NSString path = [otherBundle pathForImageResource:@"img"];
NSImage *img = [[NSImage alloc] initWithContentsOfFile:path];

//bundle中能夠包含一個庫. 若是咱們從庫獲得一個class, bundle會鏈接庫,並查找該類:
Class newClass = [otherBundle classNamed:@"Person"];
id person = [[newClass alloc] init];

//若是不知到class名,也能夠經過查找主要類來取得
Class aClass = [otherBundle principalClass];
id classInstance = [[aClass alloc] init];

//能夠看到, NSBundle有不少的用途.在這章中, NSBundle負責(在後臺)加載nib文件. 咱們也能夠不經過NSWindowController來加載nib文件, 直接使用NSBundle:
BOOL flag = [NSBundle loadNibNamed:@"ViewController" owner:someObject];
//注意: 咱們指定了一個對象someObject做爲nib的File」s Owner

//獲取屬性列表
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"ViewControllers" ofType:@"plist"]]
————————————————

複製代碼

獲取 mainBundle 包內容信息web

/1.獲取app的info.plist詳細信息

//build 版本號
NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
//version 版本號
NSString *version2 = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];

//應用標識:Bundle identifier
NSString *bundleId = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"];

//Bundle name
NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];

//應用展現的名字
NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];

//2.應用程序語言本地化
//app本地化宏
#define XLocalizedString(key, comment)[[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil]


複製代碼

2. 沙盒

每個 IOS 程序都是單獨運行在一個沙盒內部的,以保證不會篡改或者讀取其餘 APP 的信息。sql

其中 Data Container 包含以下三個目錄:

  • Documents: 應用程序在運行時生成的一些須要長久保存的重要數據放在此文件中;

  • Library:

    • Cache:存放緩存文件,好比從網路上下載的數據或數據。通常用來保存應用須要長期使用的,數據量大,不須要備份的非重要數據。iTunes
    • Prefrence:保存應用的全部偏好設置,好比帳號,設置等。由系統自動管理
  • tmp: 用於保存應用在運行時產生的一些臨時數據文件,手機重啓,系統空間不足的,關閉應用等場景下可能會刪除該文件下的文件

3. 沙盒路徑訪問

  • 沙盒入口

    NSLog(@"%@",NSHomeDirectory());

  • 沙盒 tmp 目錄

    NSLog(@"%@",NSTemporaryDirectory());

  • 沙盒 Documents 目錄

// 1: 拼接方式
NSLog(@"%@",[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"]);
// 沙盒Documents目錄 2: 建議使用的方式
NSLog(@"%@",[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]);
複製代碼
  • 沙盒 Library 目錄
// 1: 拼接方式
   NSLog(@"%@",[NSHomeDirectory() stringByAppendingPathComponent:@"Library"]);
   // 沙盒Library目錄 2: 建議使用的方式
   NSLog(@"%@",[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) firstObject]);
複製代碼
  • 沙盒 Library 下的 Caches 目錄 建議使用的方式
NSLog(@"%@",[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject]);
複製代碼
  • 沙盒 Library 下的 Preferences 目錄,只能採用拼接的方式,此處使用的 Library+Preferences 的方式

NSLog(@"%@",[[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"Preferences"]);

2、數據存取: plist

plist 文件本質上是一個 xml 文件,按照key:value的形式存儲。

1. plist 文件的讀取

讀取系統自帶的 Info.plist

NSDictionary *sysInfo = NSBundle.mainBundle.infoDictionary;
NSLog(@"%@",sysInfo);
NSLog(@"%@",sysInfo[@"CFBundleShortVersionString"]);

複製代碼

自定義的 plist 文件

// 可使用NSBundle.mainBundle pathForResource 讀取項目工程目錄下的文件
    NSString *userPath = [NSBundle.mainBundle pathForResource:@"user" ofType:@"plist"];
    NSDictionary *userDic = [NSDictionary dictionaryWithContentsOfFile:userPath];
    NSLog(@"username= %@ , password = %@",userDic[@"username"],userDic[@"password"]);

    // 解析一個複雜的plist; value爲array的狀況
    NSString *cityPath = [NSBundle.mainBundle pathForResource:@"cityData" ofType:@"plist"];
    NSDictionary *cityDic = [NSDictionary dictionaryWithContentsOfFile:cityPath];

    NSArray *allKeys = cityDic.allKeys;
    for (int i=0; i<allKeys.count; i++) {
        NSLog(@" key = %@ , value = %@",allKeys[i],cityDic[allKeys[i]]);
    }

    // 讀取沙盒中Documents中的數據
    NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES)[0];
    NSString *dataPath = [path stringByAppendingPathComponent:@"data.plist"];
    NSDictionary *dataDict = [NSDictionary dictionaryWithContentsOfFile:path];
    NSLog(@"%@", dataDict);


複製代碼

注意點

  • plist 文件接收須要使用一個 NSDictionary
  • plist 文件中 key 對應的 value 須要使用合適的類型接收數據;(tips: 若是爲 Array 的狀況須要使用 NSArray 來接收)
  • plist 文件讀取主要使用: dictionaryWithContentsOfFile方法

3. plist 文件寫入

// 將cityData.plist存儲到沙盒的Documents中
    NSString *saveFilePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"city.plist"];
    [cityDic writeToFile:saveFilePath atomically:YES];

    // 寫入部分數據
    NSString *fujianPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"福建.plist"];
    [cityDic[@"福建"] writeToFile:fujianPath atomically:YES];
複製代碼

3、數據存取: NSUserDefaults(偏好設置)

1. NSUserDefaults 基礎使用(登陸 demo)

NSUserDefaults 偏好設置存儲,不須要路徑,通常用於存儲帳號密碼等信息。

// 存數據
- (void)saveForPreference {

    // 獲取偏好設置對象
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

    //存儲數據
    [defaults setObject:@"mrgao" forKey:@"name"];
    [defaults setInteger:24 forKey:@"age"];

    // 同步調用,馬上寫到文件中,不寫這個方法會異步,有延遲
    [defaults synchronize];
}
//讀數據
- (void)readForPreference {

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

    NSString *name = [defaults objectForKey:@"name"];
    NSInteger *age = [defaults integerForKey:@"age"];

    NSLog(@"%@-------%ld",name,age);
}



複製代碼

舉一個保存用戶名和密碼的例子:

//
//  ViewController.m
//  NSBundle的使用
#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *userName;
@property (weak, nonatomic) IBOutlet UITextField *password;
- (IBAction)savePassword:(id)sender;
- (IBAction)login:(id)sender;
@property (weak, nonatomic) IBOutlet UISwitch *remenber;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSDictionary *info = [self readForPreference];
    self.userName.text =info[@"name"];
    self.password.text =info[@"pass"];
    //    字典類型,傳遞Bool類型的值,須要轉換成 NSNumber; 獲取值的時候,先要獲取NSNumber,而後在獲取對應的boolean的值
    self.remenber.on =[info[@"isOn"] boolValue];
}

// 存數據
- (void)saveForPreference {

    // 獲取偏好設置對象
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSString *username = self.userName.text;
    NSString *password = self.password.text;
    //存儲數據
    [defaults setObject:username  forKey:@"username"];
    [defaults setObject:password  forKey:@"password"];
    //    [defaults setInteger:24 forKey:@"age"];
    // 同步調用,馬上寫到文件中,不寫這個方法會異步,有延遲
    [defaults synchronize];
}
//讀數據
- (NSDictionary *)readForPreference {

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

    NSString *name = [defaults valueForKey:@"username"];
    NSString *pass = [defaults valueForKey:@"password"];
    Boolean isOn = [defaults boolForKey:@"isOn"];
    //    NSInteger *age = [defaults integerForKey:@"age"];
    NSLog(@"%@-------%@",name,pass);
//    字典類型,傳遞Bool類型的值,須要轉換成 NSNumber
    return @{@"name":name,@"pass":pass,@"isOn": [NSNumber numberWithBool:isOn]};
}

- (IBAction)savePassword:(id)sender {
    UISwitch *swc = (UISwitch *)sender;
    NSLog(@"%@", self.remenber.isOn ? @"YES" : @"NO");
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setBool:self.remenber.isOn forKey:@"isOn"];
    [defaults synchronize];
}

- (IBAction)login:(id)sender {
    [self saveForPreference];
}
@end
複製代碼

注意點

  • NSDictionary 不能直接傳播 Bool 類型的值,須要先將其轉換成 NSNumber 而後讀取的時候轉換成 bool;

  • NSLog 打印的時候不能直接打印 Bool 類型的值,建議使用這種方式展現: NSLog(@"%@", boolValue ? @"YES" : @"NO");

  • 偏好設置存儲 記得調用 [defaults synchronize]; 存儲

那麼咱們使用 NSUserDefaults 保存的數據最終存放到哪裏?

前面咱們講過沙盒裏面有一個 Library->Preferences 是專門用來存放應用的全部偏好設置,好比帳號,設置等,由系統自動管理。

那咱們一探究竟:

結論: NSUserDefaults 的數據最終存放在沙盒下的 Library->Preferences 文件夾中,且類型爲 plist 類型

2. 使用 NSUserDefaults 製做新特性頁面

所謂新特性頁面,就是在一個 APP 的某一個版本下,按照的時候展現一次該特性頁面;之後再次打開就不展現了。例如:王者榮耀新賽季更新的時候,開場賽季介紹動畫播放,當此賽季咱們再次打開的時候 就不會播放此動畫了.

- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
    // Override point for customization after application launch.
    self.window = [[UIWindow alloc] initWithWindowScene:(UIWindowScene *)scene];
    // 獲取當前應用的版本
    NSString *currentVersion = NSBundle.mainBundle.infoDictionary[@"CFBundleShortVersionString"];
    // 獲取到本地的版本
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSString *localVersion = [defaults valueForKey:@"version"];
    // 若是版本更新了就顯示新特性頁面
    if (![currentVersion isEqualToString:localVersion]) {
        UIViewController *newVC = [[UIViewController alloc] init];
        newVC.view.backgroundColor = UIColor.greenColor;
        self.window.rootViewController= newVC;
        // 保存當前版本
        [defaults setObject:currentVersion forKey:@"version"];
        [defaults synchronize];
    }else{
        UIViewController *normalVC = [[UIViewController alloc] init];
        normalVC.view.backgroundColor = UIColor.redColor;
        self.window.rootViewController= normalVC;
    }

    [self.window makeKeyAndVisible];
}

複製代碼

4、數據存取: 歸檔-NSKeyedArchiver

歸檔通常都是保存自定義對象的時候,使用歸檔.由於 plist 文件不可以保存自定義對象.若是一個字典當中保存有自定義對象,若是把這個字典寫入到文件當中,它是不會生成 plist 文件的.

1. NSKeyedArchiver 的基礎使用

NSArray, NSString, NSInteger, NSDictionary 等 iOS 自帶的數據類型,歸檔是原生支持的。

@interface NSKeyedArchiverViewController ()
- (IBAction)archive:(id)sender;
- (IBAction)unarchive:(id)sender;
@property(strong,nonatomic) NSData *data;
@end

@implementation NSKeyedArchiverViewController

- (IBAction)archive:(id)sender {
// 數據存儲,此處案例是序列化一個數組
    self.data =[NSKeyedArchiver archivedDataWithRootObject:@[@"a",@"b",@"c"] requiringSecureCoding:YES error:nil];
}

- (IBAction)unarchive:(id)sender {
// 數據讀取,轉換成數組
  NSArray *array=  [NSKeyedUnarchiver unarchivedObjectOfClass:NSArray.class fromData:self.data error:nil];
    NSLog(@"%@",array);
}
@end
複製代碼

1. NSKeyedArchiver 自定義對象歸檔

上面講到 NSArray, NSString, NSInteger, NSDictionary 等 iOS 自帶的數據類型,歸檔是原生支持的,可是若是是自定義對象,就和上面的方式有所不一樣。

若是咱們按照上面的方式去將自定義對象歸檔:

NSError *error;
    self.userData= [NSKeyedArchiver archivedDataWithRootObject:user requiringSecureCoding:YES error:&error];
    if(error!=nil){
        NSLog(@"%@",error);
    }
複製代碼

確定會報錯:由於咱們沒有讓自定義的對象去遵照 安全編碼NSSecureCoding 協議

Domain=NSCocoaErrorDomain Code=4866 "The data couldn’t be written because it isn’t in the correct format." UserInfo={NSUnderlyingError=0x6000016d0210 {Error Domain=NSCocoaErrorDomain Code=4864 "This decoder will only decode classes that adopt NSSecureCoding. Class 'User' does not adopt it." UserInfo={NSDebugDescription=This decoder will only decode classes that adopt NSSecureCoding. Class 'User' does not adopt it.}}}
複製代碼

因此,咱們自定義對象歸檔有以下幾個步驟:

    1. 讓自定義對象實現NSSecureCoding協議;
    1. 至少實現NSSecureCoding協議中的三個方法;
    • encodeWithCoder
    • initWithCoder
    • supportsSecureCoding

以前使用的數組實際上是已經幫咱們實現了encodeWithCoderinitWithCoder;那麼咱們如何編寫?

// User.h 實現NSSecureCoding協議
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface User : NSObject<NSSecureCoding>
@property(nonatomic,copy) NSString *userName;
@property(nonatomic,assign) int age;
@end

NS_ASSUME_NONNULL_END

複製代碼

在 User.m 去實現協議的幾個方法

#import "User.h"

@implementation User
// 編碼,將對象寫入流中
- (void)encodeWithCoder:(nonnull NSCoder *)coder {
    // 要記得將全部的對象都序列化
    [coder encodeObject:self.userName forKey:@"userName"];
    [coder encodeInt:self.age forKey:@"age"];

}
// 解碼,將流初始化爲對象
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder {
    if(self = [super init]){
        self.userName = [coder decodeObjectForKey:@"userName"];
        self.age = [coder decodeIntForKey:@"age"];
    }
    return self;
}
// 是否支持安全編碼
+ (BOOL)supportsSecureCoding{
    return YES;
}

@end

複製代碼

自定義對象 User 的歸檔和解檔:

- (IBAction)archive:(id)sender {
    // 自定義對象須要存儲在沙盒中
    User *user = [[User alloc] init];
    user.userName=@"mrgaogang";
    user.age=123;
    NSError *error;
    self.userData= [NSKeyedArchiver archivedDataWithRootObject:user requiringSecureCoding:YES error:&error];
    if(error!=nil){
        NSLog(@"%@",error);
    }

    // 咱們也能夠將歸檔的數據存放在沙盒中
    NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"User.data"];
    [self.userData writeToFile:filePath atomically:YES];
}

- (IBAction)unarchive:(id)sender {
    // 從沙盒中取出咱們歸檔的數據
    NSData *data = [NSData dataWithContentsOfFile:[NSTemporaryDirectory() stringByAppendingPathComponent:@"User.data"]];

    User *user= [NSKeyedUnarchiver unarchivedObjectOfClass:User.class fromData:data error:nil];

    NSLog(@"userName = %@ , age = %d",user.userName,user.age);

}
複製代碼
# 打印狀況
userName = mrgaogang , age = 123
複製代碼

5、數據存取: 數據庫-sqlite3

1. sqlite 簡單介紹

SQLite,是一款輕型的數據庫,是遵照 ACID 的關係型數據庫管理系統,它包含在一個相對小的 C 庫中. sqlite 是嵌入式的,佔用內存小,且支持 windows,linux,unix 操做系統,因此當前的 android/ios 都是支持的

sqlite 支持的數據類型:

  • NULL : NULL 值
  • INTEGER: 整形值
  • REAL: 浮點值
  • TEXT: 文本字符串,存儲時使用編碼方式爲 utf-8 或者 utf-16
  • BLOB: 二進制文件存儲

sqlite 經常使用函數:

  • sqlite3_open: 開數據庫
  • sqlite3_close: 關數據庫
  • sqlite3_exec: 執行函數
  • sqlite3_prepare_v2: 查詢時使用的,執行獲得結果集
  • sqlite3_step : 查詢時使用的,遍歷結果集
  • sqlite3_column_text: 查詢時使用,獲取對應 column 的值

2. sqlite3 基礎使用-工程搭建及自定義 Model

(1) 引入 sqlite3 包

因爲 sqlite3 並不是 ios 自帶的,因此咱們須要額外的引入:

(2) 自定義 Model

因爲操做數據庫,對應的映射確定是一個對象,那麼咱們首先將 Model 構造出來:

//
//  Person.h
//  SQLite數據庫操做
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject
@property(nonatomic,copy) NSString *name;
@property(nonatomic,copy) NSString *phone;
@property(nonatomic,copy) NSString *address;

- (instancetype) initWithName:(NSString *) name andPhone:(NSString *) phone andAddress:(NSString *) address;
@end

NS_ASSUME_NONNULL_END

複製代碼

並實現對應的構造函數

//
//  Person.m
//  SQLite數據庫操做
#import "Person.h"

@implementation Person
- (instancetype)initWithName:(NSString *)name andPhone:(NSString *)phone andAddress:(NSString *)address{
    if(self = [super init]){
        self.name= name;
        self.phone=phone;
        self.address=address;
    }
    return self;
}

- (NSString *)description
{
    return [NSString stringWithFormat:@"name = %@ , phone = %@, address = %@", self.name,self.phone,self.address];
}
@end
複製代碼

3. sqlite 操做工具類(增刪改查)

因爲對數據庫的操做是獨立的,咱們不該該將其放在 UI/Controller 中,而應該將數據庫的操做單獨放在一個文件中進行;以便於後續的維護和程序的開發。

(1) 對外聲明 sql 操做方法

此處主要演示對數據庫的六種操做:

  • 數據庫的建立
  • 表格的建立
  • 數據的添加
  • 數據的刪除
  • 數據的更新
  • 數據的查詢
//
//  DBTools.h
//  SQLite數據庫操做

#import <Foundation/Foundation.h>
#import "Person.h"
NS_ASSUME_NONNULL_BEGIN

@interface DBTools : NSObject
// 建立db
- (void) createDB;
// 建立表
- (void) createTable:(NSString *) tableName;
// 插入數據
- (void) insertPerson:(Person *) person;
// 條件刪除
- (void) deletePerson:(NSString *) personName;
// 更新數據
- (void) updatePerson:(Person *) person;
// 查詢全部數據
- (NSArray<Person *> *) queryPerson;
// 經過條件查詢
- (NSArray<Person *> *) queryPersonByName:(NSString *) personName;
//  數據庫的名字
@property(nonnull,copy) NSString *dbName;

@end

NS_ASSUME_NONNULL_END

複製代碼

(2) 數據庫的增刪改查

//
//  DBTools.m
//  SQLite數據庫操做

#import "DBTools.h"
#import "Person.h"
#import "sqlite3.h"

@interface DBTools(){
    sqlite3 *sqlite;
}

@end
@implementation DBTools


// 建立數據庫,存放在沙盒中
- (void)createDB{
    //    1. 查找沙盒
    NSString *docPath= [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    //    2. 拼接數據庫
    NSString *dbPath = [docPath stringByAppendingPathComponent: [NSString stringWithFormat:@"%@.db",self.dbName]];
    NSLog(@"%@",dbPath);

    //    3. 建立數據庫,須要傳遞2個參數,1: 數據庫名稱(c語言的字符串), 2: 數據庫的句柄,後續咱們須要使用句柄進行數據庫的操做
    int result = sqlite3_open([dbPath UTF8String], &sqlite);

    if(result == SQLITE_OK){
        NSLog(@"數據庫建立成功");
    }else{
        NSLog(@"數據庫建立失敗");
    }
}
// 數據庫的關閉
- (void) closeDB{
    sqlite3_close(sqlite);
}
// 針對建立表格,增長,刪除均可以統一使用執行 sqlite3_exec
- (void) execSQL:(NSString *) sql{
    //    1. 執行以前須要打開數據庫
    [self createDB];
    //    2. 建立sql語句
    NSLog(@"%@",sql);
    char *error;
    //    3. 執行sql語句
    int result= sqlite3_exec(sqlite, [sql UTF8String], NULL, NULL, &error);
    if(result == SQLITE_OK){
        NSLog(@"sql執行成功");
    }else{
        NSLog(@"sql執行失敗, %s" ,error);
    }
    //    4. 必定要記得關閉數據庫
    [self closeDB];
}
// 執行建立表格的sql語句
- (void)createTable:(NSString *)tableName{
    NSString *sql = [NSString stringWithFormat: @"create table %@(id integer primary key autoincrement, name text , phone text ,address text)",tableName];
    [self execSQL: sql];
}
// 執行插入的sql語句
- (void)insertPerson:(Person *)person{
    NSString *sql = [NSString stringWithFormat: @"insert into person (name , phone ,address) values ('%@','%@','%@')",person.name,person.phone,person.address];
    [self execSQL: sql];
}

// 執行刪除的sql語句
- (void)deletePerson:(NSString *)personName{
    NSString *sql = [NSString stringWithFormat: @"delete from person where name='%@'",personName];
    [self execSQL: sql];
}

// 執行更新的sql語句
- (void)updatePerson:(Person *)person{
    NSString *sql = [NSString stringWithFormat: @"update person set phone='%@' ,address='%@' where name='%@'",person.phone,person.address,person.name];
    [self execSQL:sql];
}

// 查詢所有
- (NSArray<Person *> *)queryPerson{
    [self createDB];
    NSString *sql = [NSString stringWithFormat: @"select name,phone,address from person"];

    sqlite3_stmt *stmt;
    // 使用sqlite3_prepare_v2查詢數據
    int result= sqlite3_prepare_v2(sqlite, [sql UTF8String], -1, &stmt, NULL);

    NSMutableArray *array = [NSMutableArray array];
    if(result == SQLITE_OK){
        //   遍歷結果集
        while (sqlite3_step(stmt) == SQLITE_ROW) {
            // 參數爲結果集和查詢結果集的第幾列;且返回的結果爲c語言的string
            const char *cName= (const char*) sqlite3_column_text(stmt, 0);
            const char *cPhone= (const char*) sqlite3_column_text(stmt, 1);
            const char *cAddress=   (const char*) sqlite3_column_text(stmt, 2);
            // 要記得將c語言的string 改爲NSString
            Person *person = [[Person alloc] initWithName:[NSString stringWithUTF8String:cName] andPhone:[NSString stringWithUTF8String:cPhone] andAddress:[NSString stringWithUTF8String:cAddress]];
            [array addObject:person];

        }
    }else{
        NSLog(@"查詢失敗");
    }

    [self closeDB];

    return array;

}

//  條件查詢
- (NSArray<Person *> *)queryPersonByName:(NSString *)personName{
    [self createDB];
    NSString *sql = [NSString stringWithFormat: @"select * from person where name = '%@'",personName];

    sqlite3_stmt *stmt;
    // 使用sqlite3_prepare_v2查詢數據
    int result= sqlite3_prepare_v2(sqlite, [sql UTF8String], -1, &stmt, NULL);

    NSMutableArray *array = [NSMutableArray array];
    if(result == SQLITE_OK){
        //   遍歷結果集
        while (sqlite3_step(stmt) == SQLITE_ROW) {
            // 參數爲結果集和查詢結果集的第幾列;且返回的結果爲c語言的string
            const char *cPhone= (const char*) sqlite3_column_text(stmt, 0);
            const char *cAddress=   (const char*) sqlite3_column_text(stmt, 1);
            // 要記得將c語言的string 改爲NSString
            Person *person = [[Person alloc] initWithName:personName andPhone:[NSString stringWithUTF8String:cPhone] andAddress:[NSString stringWithUTF8String:cAddress]];
            [array addObject:person];

        }
    }else{
        NSLog(@"查詢失敗");
    }

    [self closeDB];

    return array;


}

@end

複製代碼

(3) 調用增刪改查

//
//  ViewController.m
//  SQLite數據庫操做

#import "ViewController.h"
#import "DBTools.h"
@interface ViewController ()
- (IBAction)createDB:(id)sender;
- (IBAction)createTable:(id)sender;
- (IBAction)insert:(id)sender;
- (IBAction)deleteData:(id)sender;
- (IBAction)updateData:(id)sender;
- (IBAction)queryData:(id)sender;
@property(nonatomic,strong) DBTools *dbTools;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.dbTools = [[DBTools alloc] init];
    self.dbTools.dbName = @"person";
}

// 建立數據庫
- (IBAction)createDB:(id)sender {
    [self.dbTools createDB];
}
// 建立表格
- (IBAction)createTable:(id)sender {
    [self.dbTools createTable:@"person"];
}
// 插入數據
- (IBAction)insert:(id)sender {
    Person *person = [[Person alloc] initWithName:@"mrgaogang" andPhone:@"12345678900" andAddress:@"中國-廣東深圳"];
    [self.dbTools insertPerson:person];
}
// 刪除數據
- (IBAction)deleteData:(id)sender {
    [self.dbTools deletePerson: @"mrgaogang"];
}
// 更新數據
- (IBAction)updateData:(id)sender {
    NSArray *array = [self.dbTools queryPersonByName:@"mrgaogang"];
    if(array.count > 0){
        Person *person = [array firstObject];
        [self.dbTools updatePerson:person ];
    }
}
// 查詢數據
- (IBAction)queryData:(id)sender {
    NSArray *array = [self.dbTools queryPersonByName:@"mrgaogang"];
    if(array.count > 0){
        for (Person *p in array) {
            NSLog(@"%@",p);
        }
    }

}
@end


複製代碼

6、 FMDB 的使用

FMDB 是什麼?

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

FMDB 的優勢

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

1. FMDB 的安裝

此處使用 cocospods 的方式安裝,固然你也能夠將源代碼下面的 fmdb 拷貝下來,放在工程內部。若是不知道如何使用 cocospods的同窗能夠參考此文章:cocospods的使用

pod init
複製代碼

而後編輯Podfile並新增 FMDB:

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'FMDB的使用' do
    # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
    use_frameworks!

    # Pods for MyApp2

    pod 'FMDB'
    # pod 'FMDB/FTS' # FMDB with FTS
    # pod 'FMDB/standalone' # FMDB with latest SQLite amalgamation source
    # pod 'FMDB/standalone/FTS' # FMDB with latest SQLite amalgamation source and FTS
    # pod 'FMDB/SQLCipher' # FMDB with SQLCipher
end

複製代碼
pod install

複製代碼

安裝以後打開: FMDB的使用.xcworkspace 記住 是 : FMDB的使用.xcworkspace而不是 FMDB的使用.xcodeproj.

若是編譯有以下報錯

解決辦法:刪除Pods.frameworks,剩餘有Pods_.framework

2. Model的創建

Model和以前的Person幾乎相似。 聲明Model的屬性及構造函數

//
//  Student.h
//  FMDB的使用
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Student : NSObject

@property(nonatomic,copy) NSString *name;
@property(nonatomic,assign) int age;
@property (nonatomic,copy) NSString *sex;

- (instancetype)initWithName:(NSString *) name andAge:(int) age andSex:(NSString *) sex;

@end

NS_ASSUME_NONNULL_END
複製代碼

實現Model的構造函數和description方法

//
//  Student.h
//  FMDB的使用

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Student : NSObject

@property(nonatomic,copy) NSString *name;
@property(nonatomic,assign) int age;
@property (nonatomic,copy) NSString *sex;

- (instancetype)initWithName:(NSString *) name andAge:(int) age andSex:(NSString *) sex;

@end

NS_ASSUME_NONNULL_END


複製代碼

3. 數據庫的增刪改查

其實總體的流程和純操做sqlite3是相似的,主要有以下幾個變化:

  • 建立庫成功以後使用FMDatabase存儲

  • 再也不直接操做sqlite3_exec而是使用executeUpdate執行增長,刪除和修改;

  • 使用executeUpdate的時候,再也不使用字符串拼接的方式,而是使用佔位符?,且executeUpdate第一個參數爲sql,後面爲可變參數,參數的個數和?的個數一致;

  • 使用executeUpdate的時候,注意傳入的都必須是對象,對於int等基礎類型,須要使用@()包裹傳遞參數。

  • 查詢操做直接使用executeQuery,且使用更加直觀的FMResultSet存儲結果集

  • 從結果集中獲取數據也使用更加直觀的:stringForColumn , intForColumn等 獲取

#import "DBTools.h"
#import "FMDB.h"

@interface DBTools ()

@property (strong,nonatomic) FMDatabase *fmdb;

@end
@implementation DBTools

- (void) createDB{
    //    1. 找到沙盒路徑
    NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    //   2. 找到db文件
    NSString *fileName =  [docPath stringByAppendingPathComponent: @"student.db"];
    
    self.fmdb = [FMDatabase databaseWithPath:fileName];
    
    BOOL success = [self.fmdb open];
    
    if(success){
        NSLog(@"打開數據庫成功");
    }else{
        NSLog(@"打開數據庫失敗");
        
    }
    
}


- (void) createTable{
    //用以前必定要打開數據庫
    [self createDB];
    
    NSString *sql = @"create table t_stu(id integer primary key autoincrement, name text,  age integer, sex text)";
    
    BOOL isSuccess = [self.fmdb executeUpdate:sql];
    
    if(isSuccess){
        NSLog(@"建立表成功");
    }
    else{
        NSLog(@"建立表失敗");
    }
    
    //用完關閉數據庫
    [self.fmdb close];
    
}
- (void) insertStudent:(Student *) student{
    
    [self createDB];
    
    NSString *sql = @"insert into t_stu(name,age,sex) values (?,?,?)";
    // 注意FMDB操做數據必須使用對象,age是int類型,不符合,因此須要轉換成對象的方式
    BOOL isSuccess = [self.fmdb executeUpdate:sql,student.name,@(student.age),student.sex];
    
    if(isSuccess){
        NSLog(@"插入成功");
    }
    else{
        NSLog(@"插入失敗");
    }
    
    //用完關閉數據庫
    [self.fmdb close];
    
    
}
- (void) deleteStudent:(NSString *) stuName{
    
    [self createDB];
    
    NSString *sql = @"delete from t_stu where name=?";
    
    BOOL isSuccess = [self.fmdb executeUpdate:sql,stuName];
    
    if(isSuccess){
        NSLog(@"刪除成功");
    }
    else{
        NSLog(@"刪除失敗");
    }
    
    //用完關閉數據庫
    [self.fmdb close];
    
}
- (void) updateStudent:(Student *) student{
    [self createDB];
    
    NSString *sql = @"update t_stu set age=? , sex=? where name=?";
    
    BOOL isSuccess = [self.fmdb executeUpdate:sql,@(student.age),student.sex,student.name];
    
    if(isSuccess){
        NSLog(@"更新成功");
    }
    else{
        NSLog(@"更新失敗");
    }
    
    //用完關閉數據庫
    [self.fmdb close];
    
}
- (NSArray<Student *>*) queryStudentByName:(NSString *) stuName{
    [self createDB];
    
    NSString *sql = @"select name,age,sex from  t_stu  where name=?";
    
    FMResultSet *resultSet=[self.fmdb executeQuery:sql,stuName];
 
    NSMutableArray *array = [NSMutableArray array];
    while ([resultSet next]) {
        NSString *name = [resultSet stringForColumn:@"name"];
        int age = [resultSet intForColumn:@"age"];
        NSString *sex = [resultSet stringForColumn:@"sex"];
        Student *stu = [[Student alloc] initWithName:name andAge:age andSex:sex];
        [array addObject:stu];
    }
    
    //用完關閉數據庫
    [self.fmdb close];
    return array;
}

@end

複製代碼

4. 事務的操做

- (void)insertStudents:(NSArray<Student *> *)students{
    [self createDB];
    // 開啓事務
    [self.fmdb beginTransaction];
    
    @try {
        for (Student *student in students) {
            NSString *sql = @"insert into t_stu(name,age,sex) values (?,?,?)";
            // 注意FMDB操做數據必須使用對象,age是int類型,不符合,因此須要轉換成對象的方式
            BOOL isSuccess = [self.fmdb executeUpdate:sql,student.name,@(student.age),student.sex];
            
            if(isSuccess){
                NSLog(@"插入成功");
            }
            else{
                NSLog(@"插入失敗");
            }
        }
    } @catch (NSException *exception) {
        // 回滾數據
        [self.fmdb rollback];
    } @finally {
        // 提交數據
        [self.fmdb commit];
    }
    
    //用完關閉數據庫
    [self.fmdb close];
}

複製代碼

測試:

NSMutableArray *array = [NSMutableArray array];
    for (int i=0; i<1000; i++) {
         Student *stu = [[Student alloc] initWithName:@"mrgaogang" andAge:i andSex:@"male"];
        [array addObject:stu];
    }
    [self.dbTools insertStudents: array];

複製代碼

參考

相關文章
相關標籤/搜索