在上文,我們介紹了ios開發中的其中2種數據持久化方式:屬性列表、歸檔解檔。本節將繼續介紹另外2種iOS持久化數據的方法:資料庫 SQLite3、Core Data 的運用; 在本節,將通過對4個文本框內容的創建、修改,退出後臺,再重新回到後臺,來認識這兩種持久化數據的方式。效果圖如下【圖1】: 【
在上文,我們介紹了ios開發中的其中2種數據持久化方式:屬性列表、歸檔解檔。本節將繼續介紹另外2種iOS持久化數據的方法:資料庫 SQLite3、Core Data 的運用;
在本節,將通過對4個文本框內容的創建、修改,退出後臺,再重新回到後臺,來認識這兩種持久化數據的方式。效果圖如下【圖1】:
【圖1 GUI界面效果圖】
【本次開發環境: Xcode:7.2 iOS Simulator:iphone6S plus By:啊左】
一、資料庫SQLite3
SQLite(Strutcured Query Language,結構化查詢語言),是iOS的嵌入式SQL資料庫,在存儲和檢索大量數據方面非常有效,屬於輕量級資料庫,但是功能很強大。 安卓和ios開發使用的都是SQLite資料庫。 而另一種持久化數據方式,core Data是對SQLite的封裝,因為iOS中使用的SQLite是純C語言的。
1、鏈接到SQLite3庫
在Xcode中,使用Single View Application模板創建一個新項目,命名為persistence3。
新建項目選中項目導航列表(最左邊)的頂部然後在主區域的TARGETS部分選中persistence3,註意要從TARGETS選中而不是從PROJECT部分。
選中後,點擊“Build Phases”,打開在第三欄,按“+”添加“libsqlite3.0.tbd”【註意:Xcode7後dylib尾碼改成tbd,如果仍要添加.bylib為尾碼的鏈接,在添加framework那個對話框,最下麵有個 "add other..." 點開之後, 按command+shift+G , 路徑輸入 /usr/lib/ ,然後 找到你需要的lib文件 就ok了。。 好吧,我還是習慣添加.dyib...】
【圖2 鏈接SQLite3.dyib】
3是版本號,是SQLite的第三個版本。它是始終指向最新版本的SQLite3庫的; 在“Main.storyboard”中拖入4個標簽、4個文本框控制項,拖動並對齊標簽與文本框,並依次修改標簽文本如【圖1】,“ViewController.h”中添加一個裝載4個文本框的數組“lineFields”:#import <UIKit/UIKit.h> @interface ViewController : UIViewController @property (nonatomic,strong)IBOutletCollection(UITextField) NSArray *lineFields;//存儲4個文本框欄位的數組
@end
然後打開輔助編輯器,通過control鍵將4個文本框連接到 lineFields 這個數組,確保連接順序為從頂部到底部!
在項目導航面板中,點擊"ViewController.m" ,將以下2段代碼添加到 @implementation與 @end 的中間,與上文相同,這個方法在後面會一直調用:
#import "ViewController.h" #import <sqlite3.h> //導入SQLite3,註意是擴折號 //SQLite 是不區分大小寫的 @implementation ViewController { sqlite3 *sqlite; //資料庫 } //懶載入 -(NSString *)datafilePath { NSArray *array = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *path = [array objectAtIndex:0]; return [path stringByAppendingPathComponent:@"data.sqlite"]; }
在這裡,我們介紹一下iOS9的一個新的變化:UIAlertController。小小地在這裡運用一下:
//警告提示框,為後面的操作向用戶提示信息 -(void)alert:(NSString *)mes { /*知識點:ios 9.0 後,簡單的UIAlertView已經不能用了。 UIAlertController代替了UIAlertView彈框 和 UIActionSheet下彈框 */ //UIAlertControllerStyleAlert:中間; UIAlertControllerStyleActionSheet:顯示在屏幕底部; UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"警告" message:mes preferredStyle:(UIAlertControllerStyleAlert)]; UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"取消" style:(UIAlertActionStyleCancel) handler:nil]; UIAlertAction *defult = [UIAlertAction actionWithTitle:@"確定" style:(UIAlertActionStyleDefault) handler:nil]; [alert addAction:cancel]; [alert addAction:defult]; [self presentViewController:alert animated:YES completion:nil]; //呈現 }
- (void)viewDidLoad 以及通知的方法代碼:
1 - (void)viewDidLoad { 2 [super viewDidLoad]; 3 int result = sqlite3_open([[self datafilePath]UTF8String], &sqlite); 4 //不等於SQLITE_OK,則表示打開資料庫的時候遇到問題 5 if(result != SQLITE_OK) 6 { 7 sqlite3_close(sqlite); 8 [self alert:@"資料庫打開失敗"]; 9 } 10 11 //定義一個語句,其中if not exists表示:如果不存在數據表,則新建一個。 若存在,則此命令自動退出. 所以這個語句可以在每次啟動時調用 12 NSString *createSql = @"CREATE TABLE IF NOT EXISTS 'wenbenkuang'(id INTEGER PRIMARY KEY,datatext TEXT NOT NULL)"; 13 char * error; 14 int ret = sqlite3_exec(sqlite,[createSql UTF8String], NULL, NULL, &error); //SQLite是純C語言的。SQL語句需要使用“UTF8String”方法把NSString轉換為char.
15 if(ret != SQLITE_OK) 16 { 17 [self alert:[NSString stringWithFormat:@"數據表創建失敗%s",error]]; 18 } 19 20 //使用select語句載入數據,並要求資料庫按行號準備排序,以便我們以相同的順序獲取,否則將使用sqlite3內部存儲順序 21 NSString *preSql = @"SELECT id,datatext FROM 'wenbenkuang'ORDER BY id"; 22 sqlite3_stmt *statmt; 23 if(sqlite3_prepare_v2(sqlite,[preSql UTF8String], -1, &statmt, nil) == SQLITE_OK) //SQLITE_OK表成功載入 24 { 25 while (sqlite3_step(statmt) == SQLITE_ROW) 26 { 27 int row = sqlite3_column_int(statmt, 0); //獲取行號 28 char *rowData = (char *)sqlite3_column_text(statmt, 1); //獲取該行數據 29 NSString *dataString = [[NSString alloc]initWithUTF8String:rowData]; 30 UITextField *textfield = self.lineFields[row]; 31 textfield.text = dataString; 32 } 33 //完成陳述 34 sqlite3_finalize(statmt); 35 } 36 //關閉資料庫 37 sqlite3_close(sqlite); 38 39 //註冊一個觀測者,進入後臺時發送通知; 40 UIApplication *app = [UIApplication sharedApplication]; 41 [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(applicationWillResignActiveNotification:)
name:UIApplicationWillResignActiveNotification object:app]; 42 } 43 -(void)applicationWillResignActiveNotification:(NSNotification *)notification 44 { 45 int result = sqlite3_open([[self datafilePath] UTF8String], &sqlite); 46 if (result != SQLITE_OK) { 47 [self alert:@"資料庫打開失敗"]; 48 sqlite3_close(sqlite); 49 } 50 for(int i=0;i<4;i++) 51 { 52 UITextField *tetxField = self.lineFields[i]; 53 char *updataSql = "INSERT OR REPLACE INTO 'wenbenkuang'(id,datatext) VALUES(?,?);"; 54 sqlite3_stmt *stmt; 55 if(sqlite3_prepare_v2(sqlite, updataSql, -1, &stmt, nil) == SQLITE_OK) 56 { 57 sqlite3_bind_int(stmt, 1, i); 58 sqlite3_bind_text(stmt, 2, [tetxField.text UTF8String], -1, NULL); 59 } 60 if(sqlite3_step(stmt) != SQLITE_DONE) 61 { 62 [self alert:@"數據更新失敗"]; 63 } 64 sqlite3_finalize(stmt); 65 } 66 sqlite3_close(sqlite); 67 }
在這段的viewDidLoad代碼中:
(行號為3-9)我們首先創建或者打開資料庫,如果打開時遇到問題,則拋出警告框。
(行號為11-18)資料庫將所有的數據存儲在表中。因此,創建一個名為“wenbenkuang”數據表,包含一個標識為“id”的鍵,與一個名為"datatext"的不為空的文本項,如果已存在相同名稱的表,則退出創建,不執行操作,所以該資料庫語句可以在每次啟動時調用一次,而不會影響到現有的資料庫;【SQLite是純C語言的。SQL語句需要使用“UTF8String”方法把NSString轉換為char.】
(行號為20-23)載入數據,使用select語句載入數據,並要求資料庫按行號準備排序,以便我們以相同的順序獲取,否則將使用sqlite3內部存儲順序; (行號為25-32)遍歷返回各行,定義一個int和char獲取數據,然後,我們通過從資料庫獲取的數據設置我們的文本欄位。 最後,關閉資料庫,操作結束; 在 “applicationWillResignActiveNotification:” 方法中,我們也是首先打開資料庫,創建一個欄位名稱,以便檢測到輸出,然後設計一條帶2個綁定變數的INSERT OR REPLACE的SQL語句,第一變數表行,第二個表存儲的實際欄位值,接下來聲明一個指向語句的指針,為語句添加綁定變數,並將值綁定到2個綁定標量中, 通過調用sqlite3_step來執行更新,迴圈執行該語句。 完成後,關閉操作資料庫;接下來我們運行調試一下。 按“command+R”運行程式,為4個文本框輸入欄位,關閉Xcode,或按下“command+shift”,點擊2次“H”鍵,退出該應用,然後再次打開, 看看是是否文本框中保留有原來自己的輸入的欄位。 二、Core Data的運用 同樣的,這次我們依然通過創建一個簡單的persistence應用,來展示如何通過蘋果自帶的Core Data框架來實現持久化。 第一步,我們應該很熟悉了。在Xcode中,使用Single View Application模板創建一個新項目,命名為persistence4。 如【圖3】,先別按Next,勾選“Use Core Data”,其中“Devices”為設備選項,你可以選擇“iphone”或者“ipad”,這裡我們選擇通用“Universal”。點擊Next! 【圖3 創建persistence4】 這次在敲入代碼前,我們還需要進行一系列的討論,好吧。。。“Core Data”確實麻煩些。 1、Core Data的數據模型 之前,如上文的iOS開發中的4種數據持久化方式【一、屬性列表與歸檔解檔】所介紹的,在使用Core Data之前我們創建數據模型的方式便是創建一個NSObject的子類並讓它們遵循NSCoding和NSCopying協議,以便能夠對它們進行歸檔解檔。但是,在Core Data中,我們使用了不同方式,不需要創建一個新子類,而是先在數據模型中創建實體(Entity),然後在代碼中為這些實體創建托管對象(Managed Object)。 實體,就是對對象的描述; 托管對象,表示在運行的過程該實體的具體實例; (可以這麼理解:實體、托管對象之間的關係類似於類與類的對象;) 2、鍵-值編碼形式 在我們使用NSDictionary的時候,就已經使用到這種編碼的形式了,但與NSDictionary相比,Core Data會複雜一些,只是基本概念是相同的。具體的操作方法如下: 首先定義一個托管對象: NSManagedObject *managedObject; 那麼,就可以通過相應的方法獲取name特性的數據值了:NSString *nameData = [managedObject valueForKey:@"name"];
為name特性設置新的屬性值:
[managedObject setValue:@"newNameData" forKey:@"name"];
3、動手:模型的創建。
在左邊的項目導航面板上面單擊“persistence4.xcdatamodeld”文件,此時打開了Xcode的數據模型編輯器,編輯器的面板中已經列出了數據模型中的所有實體、獲取請求和配置。
由於我們還未創建數據模型,因此列表是空的,單擊實體面板左下方的加號圖標(Add Entity),此時創建了一個名為:“Entity”的實體:
(提醒一下的是,右下角的“Editor Style”選項:table視圖和graph視圖。這兩種視圖在數據模型上沒有區別,只是顯示的方式不同而已。如果你的模型裡面包含多個實體,那麼graph視圖的顯示方式會非常有用,它以圖形化的方式呈現了所有實體之間的關係;由於table視圖顯示了當前實體更為詳細的信息,因為我們在創建這個實體的時候,還是選用預設的table實體)
然後點擊該實體,在右邊的數據模型編輯器上面,把第一個name欄位改為我們接下來使用的“Line”欄位,所以我們這樣就算創建了一個名為“Line”的實體了。
接下來就為“Line”實體添加新特性,單擊並按住右下角的加號圖標“Add Attribute”,當然添加特性的話也可以直接點擊“+”圖標就可以了,這裡只是為了方便讀者看到該選項所表達的意思。
點擊“+”後,可以看到新增了一個名為“attribute”的特性,把它修改為“lineNumber”,修改Type為“Interger16”,並把右邊圈紅的Optional取消選中狀態。
再次單擊“+”,把新特性修改為“lineText”,修改Type為“String”,這裡的Optional預設勾選(該選項用於防止我們創建的“lineText”文本在用戶給定的欄位為空,而“lineNumber”表行號,行號不會出現為空的情況,)
下麵是代碼部分,與SQLite的創建類似:
在“Main.storyboard”中拖入4個標簽、4個文本框控制項,拖動並對齊標簽與文本框,並依次修改標簽文本如【圖1】,“ViewController.h”中添加一個裝載4個文本框的數組“lineFields”:
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@property (nonatomic,strong)IBOutletCollection(UITextField) NSArray *lineFields; //存儲4個文本框欄位的數組
@end
然後打開輔助編輯器,通過control鍵將4個文本框連接到 lineFields 這個數組,確保連接順序為從頂部到底部!
單擊“AppDelegate.h”,我們能夠已經包含了數據類型定義需要的代碼了,我們做一些註釋:
//COREDATA托管上下文
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
//托管模型
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
//存儲時(持久化)協調者
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
//保存托管上下文
- (void)saveContext;
//取得當前應用程式文檔路徑
- (NSURL *)applicationDocumentsDirectory;
點擊“ViewController.m”,下麵,我們進行一連串的代碼:
1 #import "ViewController.h" 2 #import "AppDelegate.h" //需要導入app代理類 3 4 static NSString *const Zline = @"Line"; 5 static NSString *const ZlineNumber = @"lineNumber"; 6 static NSString *const ZlineText = @"lineText"; 7 8 @implementation ViewController 9 10 - (void)viewDidLoad { 11 [super viewDidLoad]; 12 //獲取應用程式代理 13 AppDelegate *appDe = [UIApplication sharedApplication].delegate; 14 //獲取托管對象上下文(此時如果資料庫不存在就不會創建資料庫) 15 NSManagedObjectContext *context = [appDe managedObjectContext]; 16 //創建請求(並傳遞實體描述“line”給它) 17 NSFetchRequest *request = [[NSFetchRequest alloc]initWithEntityName:Zline]; 18 //通過上下文context執行請求request,獲取記錄對象的數組 19 NSArray *objetcs = [context executeFetchRequest:request error:nil]; 20 if(objetcs == nil) //確保返回的是有效的數組 21 { 22 NSLog(@"數組創建失敗"); 23 } 24 //分別提取每個托管對象保存的數據 25 for(NSManagedObject *managed in objetcs) 26 { 27 //獲取行號(註意轉換為int) 28 int lineNum = [[managed valueForKey:ZlineNumber] intValue]; 29 //獲取文本 30 NSString *lineText = [managed valueForKey:ZlineText]; 31 32 UITextField *textField = self.lineFields[lineNum]; 33 textField.text = lineText; 34 } 35 36 //後臺處理 37 UIApplication *app = [UIApplication sharedApplication]; 38 [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(applicationWillResignActiveNotification:) name:
UIApplicationWillResignActiveNotification object:app]; 39 } 40 41 -(void)applicationWillResignActiveNotification:(NSNotification *)notification 42 { 43 AppDelegate *appDe = [UIApplication sharedApplication].delegate; 44 NSManagedObjectContext *context = appDe.managedObjectContext; 45 46 for(int i =0;i<4;i++) 47 { 48 NSFetchRequest *request = [[NSFetchRequest alloc]initWithEntityName:Zline]; 49 //predicate謂語,創建請求,但是為確認存儲中是否已有一個與欄位對應的托管對象,創建謂語,是為了給欄位標識正確的對象。 50 request.predicate = [NSPredicate predicateWithFormat:@"(lineNumber=%d)",i]; //註意謂語的拼寫:@"(lineNumber = %d)",i 51 NSArray *objects = [context executeFetchRequest:request error:nil]; 52 if(objects ==nil) 53 { 54 NSLog(@"數組創建失敗"); 55 } 56 57 //因為我們不知道是要從存儲中載入托管對象,還是創建新的托管對象,因此先創建空的托管對象 58 NSManagedObject *managed = nil; 59 if([objects count]>0) //檢查返回有效的對象,因此載入 60 { 61 managed = [objects objectAtIndex:0]; 62 } 63 else //檢查到無有效對象,因此創建新的托管對象 64 { 65 //創建實體、插入托管對象到獲取的上下文,我們直接用下麵這句代碼,省去很多流程 66 managed = [NSEntityDescription insertNewObjectForEntityForName:Zline inManagedObjectContext:context]; 67 } 68 UITextField *textField = self.lineFields[i]; 69 //使用鍵-值碼方式更新設置行號和文本: 70 [managed setValue:[NSNumber numberWithInt:i] forKey:ZlineNumber]; 71 [managed setValue:textField.text forKey:ZlineText]; 72 } 73 //完成迴圈。 74 //最後一步:持久化數據: 75 [appDe saveContext]; 76 } 77 78 @end
我們解析一下上面的代碼,首先需要導入我們創建Core Data模型時Xcode創建的已有代碼的“AppDelegate.h”。
(4-6行) :定義包括實體“Line”、行號“lineNumber”、文本“lineText”等的字元段,方便我們後面的代碼編寫;(記得別拼錯,別問我為什麼怎麼說...我就是剛剛在演示的時候一直報錯,才發現原來只是字元串拼錯了...555..心疼啊左);
(12-23行): 通過上下文context,執行請求request獲取記錄對象的數組。並確認數組有效。
(24-34行):分別提取每個托管對象保存的數據,並賦值給對應行號的文本框文本欄位;
(36-39行):後臺處理
(48-55行):執行帶有謂語的請求。
(57-71行):分托管對象是否已經存在2種情況,進行編碼設置行號和文本
(75行): 持久化數據(記得帶上這行代碼)
好了,關於Core Data數據模型和GUI界面,以及代碼我們已經設計編寫完畢。
按“command+R”運行程式,為4個文本框輸入欄位,關閉Xcode,或按下“command+shift”,點擊2次“H”鍵,退出該應用,然後再次打開, 如果運行沒有發生錯誤的話,那麼我們已經設計了一個簡單的Core Data儲存數據應用“persistence4”了。 至此,我們包括上文iOS開發中的4種數據持久化方式【一、屬性列表與歸檔解檔】的演練,已經為大家展示了有關iOS開發中的4種數據持久化方式。 希望能夠分享與讀者。 只是,對於這兩篇關於iOS開發數據持久化的所搭建的應用"persistence"例子,也只是一個簡單的應用,具體的應用設計大家可以發揮創造力,讓數據持久化在移動開發應用過程中更加便捷。 當然,更多更深入的數據持久化、數據處理,我們只懂得這些是遠遠不夠的,需要我們通過網路上共用的內容以及自己工作學習中不斷積累,才能使這些知識點更加深入,更加適應iOS開發工作。