iOS 教學, iOS進階

iOS 儲存資料與 SQLite 使用範例

資料儲存至硬碟的必要性

在 iOS App 開發時,有許多情況會需要存些東西在硬碟裡:

登入資訊

對於需要登入的App,為避免每次使用都要重新登入一次,就會需要把 Access Token 甚至是帳密存進硬碟裡。當然為了安全性考量,系統應該避免直接將密碼儲存,而應該設計 Access Token 的作法比較理想。

檔案暫存

已經下載過的圖檔或是資料檔,避免重覆下載浪費頻寬,就需要存在硬碟裡。對於使用非吃到飽的資費方案的使用者,這是很實用的功能。

資料庫暫存

對於許多需要作多面向的搜尋的 App 而言,每一個搜尋都要跟 Server 尋問會導致執行速度低落,以及頻寬的浪費。此時即可將問到的資料都先行存在硬碟裡,搜尋時先搜尋硬碟裡的資料,同時向 Server 尋問是否資料有更新。

多步驟程序

有許多時候,有些任務會需要依序執行數個步驟,比如依順序向伺服器發出多次命令,如果能將已執行完成的命令結果存在硬碟裡,可以避免重複命令,因此能減少頻寬,增進執行效率。

頁面位置暫存

記錄使用者上一次結束 App 時的頁面,而在 App 一開始時即跳至該頁面,會是一個不錯的貼心小功能。

背景App記憶體被釋放

當使用者切換至另一個很吃記憶體的App而導致App 進入背景時,不保證記憶體不會被適放。若沒有儲存至硬碟,將會導致重要的資料消失。

App 閃退

理論上,一個測試完整的 App 不應該存在閃退的情況,然而實際上一方面在測試完整之前 App 還是要能給內部外部使用者使用(這也是測試的一環),而另一方面,總是難免會有許多意外的情況出現;而身為一個 App 的設計者,會希望在即始偶爾閃退的情況下,App 還能夠維持它能夠使用的程度。比如說,如果輸入帳密之後,若不幸的會有10%的情況下會閃退,若 App 有將資訊存至硬碟,這10%的人再重開 App 時,並不需要再次輸入帳密,就可以將不適的感覺降至最低,也多少爭取了修正的時間。

iOS App 儲存資料方案

儲存的方案主要有下列幾種:

1. NSUserDefault

適合用來存輕量的 key-value 資料,比如說 token, user account info, app status 等等。

Write 範例

    [[NSUserDefaults standardUserDefaults] setObject:valueString forKey:keyString];
    [[NSUserDefaults standardUserDefaults] synchronize];  // save to disk

Read 範例

    NSString *value=[[NSUserDefaults standardUserDefaults] objectForKey:keyString];

2. 寫入 local file

適合放圖檔或是其它比較大的檔案。要使用這個方法,要先對於 App 的檔案存取限制路徑要有基本的了解,對於本機來說,常會使用到的檔案主要為 NSBundle 裡的檔案,以及 Document folder 裡檔案:

NSBundle

NSBundle 裡的檔案,也就是在 Project 裡有匯入的檔案,App 可以讀取,但是無法寫入。這點在後面要使用 SQLite 資料庫時也需要注意。

Document folder

另一個可以利用的檔案目錄,則是NSDocumentDirectory。App可以對這個目錄底下的檔案作完全的控制。
每個 App 都會有其配置的NSDocumentDirectory,不同 App 之間無法互通。電腦透過 iTunes 程式,可以將檔案放至 App 的 NSDocumentDirectory底下。這個目錄在 App 被移除時也會一併消滅。

一般的作法,是將設定檔案先放在 NSBundle 裡,而在 App 初始化時,檢查 Document folder 裡是否有同名的檔案,若沒有就將 NSBundle 裡的檔案複製至 Document folder 下,接下來就可以存取這個檔案了。

Read 範例

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *filePath= [documentsDirectory stringByAppendingPathComponent:fileName];
    UIImage *image = [[UIImage alloc] initWithContentsOfFile:filePath];

Write 範例

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *filePath= [documentsDirectory stringByAppendingPathComponent:fileName];
    NSData *imageData=UIImagePNGRepresentation(crayImage);
    if(imageData){
        [imageData writeToFile:filePath atomically:YES];
   }

3. Core Data

iOS 自定的一個資料服務,近似物件資料庫不過不完全相同。使用上直接以 Objective-C 物件方式來進行,對新手來說一開始會比較難上手,若能夠熟悉的話,也會有相當大的幫助。

Read 範例

-(NSArray *) getMemberNamed:(NSString *) name
{
    AppDelegate *appDelegate=(AppDelegate *)[[UIApplication sharedApplication] delegate];
    NSManagedObjectContext *managedObjectContext=[appDelegate managedObjectContext];
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Member" inManagedObjectContext: managedObjectContext];
    [fetchRequest setEntity:entity];
    
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name=%@", name];
    [fetchRequest setPredicate:predicate];
    
    NSError *error = nil;
    NSArray *fetchedObjects = [ managedObjectContext executeFetchRequest:fetchRequest error:&error];
    if (fetchedObjects == nil) {
        NSLog(@"Fetch error, something's wrong. %@",error);
    }
    return fetchedObjects;
    
}

Write 範例

NSArray *orangeMemberList=[self getMemberNamed:@"Orange"];
if(orangeMemberList){
    for (Member *member in orangeMemberList) {
        member.name=@"Apple";
    }
}
NSError *error = nil;

if (![managedObjectContext save:&error]) {
    NSLog(@"[ERROR] Saving managed context error: %@",error);
}else{
    NSLog(@"Managed context saved.");
}

4. SQLite 資料庫

一個輕量方便好用的關連式資料庫。對於熟悉關聯式資料庫與SQL語法的開發者們來說,這是個不錯的選擇。

本文中介紹 SQLite 在 iOS App 中的用法與範例。

SQLite簡介

SQLite是一個簡易的關聯式資料庫(Relational Database)。在這波 App 開發的浪潮中,SQLite受到廣大的歡迎。它使用單一的檔案來運作,以函式庫的方式直在內建在 App 裡,所需增加的空間不大,由於不需開連線所以運作快速。在iOS, Android, Symbian, WebOS 等都可以使用 SQLite。SQLite 在 iOS 中已有內建函式庫,使用上十分方便。

使用步驟

1. 加入 Framework

Framework 裡加入 libsqlite3.tbd

疑問一:為何這裡有 libsqlite3.tbd 與 libsqlite3.0.tbd?二者有何不同?
答:libsqlite3.tbd是連結至「最新的sqlite library」,而目前最新的是libsqlite3.0.tbd,所以雖然加二者的效果相同,但是為了維護性,應該加 libsqlite3.tbd

疑問二:為何早先的 XCode 版本是使用 libsqlite3.dylib 而現在則是 libsqlite3.tbd?
答:新版的 XCode 改使用 tbd 檔來減少檔案大小

2. 匯入header 檔

法一:在要使用的.h檔裡加上 #import <sqlite3.h>
法二:在<你的專案名稱>-prefix.pch檔裡加上#import <sqlite3.h>
若只有一二個檔案會用到 SQLite,則使用法一;否則就使用法二

3. 建立sqlite檔案法一:預先建立再複製法

3.1 先建立好 sqlite 檔案

打開終端機,然後輸入

sqlite3 member.db

在 sqlite3> 下,輸入

CREATE TABLE IF NOT EXISTS MEMBER (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, address TEXT, phone TEXT);

然後打下面的指令離開

.quit

然後就可以看到 member.db 這個檔案

3.2 將它加入 project Bundle 裡

3.3 在一APP開啟時,將這個檔案複製到 App 目錄下

        NSFileManager *fileManager = [NSFileManager defaultManager];
        NSString *bundlePath = [[NSBundle mainBundle] pathForResource:databaseName ofType:NULL];
        [fileManager copyItemAtPath:bundlePath toPath:databasePath error:&error];

3. 建立sqlite檔案法二:程式從頭建立法

在程式一開始檢查有沒有 sqlite 檔案,若沒有的話進行初始化(Create Database Tables…)

-(BOOL) createDatabase:(NSString *)databaseName{
    NSString *docsDir;
    NSArray *dirPath;
    sqlite3 *db;
    
    // Get the documents directory
    dirPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    docsDir = [dirPath objectAtIndex:0];
    
    // Build the path to the database file
    NSString *databasePath = [[NSString alloc] initWithString: [docsDir stringByAppendingPathComponent: databaseName]];
    
    NSFileManager *filemgr = [NSFileManager defaultManager];
    
    if ([filemgr fileExistsAtPath: databasePath ] == NO) {
        const char *dbpath = [databasePath UTF8String];
        
        if (sqlite3_open(dbpath, &db) == SQLITE_OK) {
            char *errMsg;
            // create SQL statements
            const char *sql = "CREATE TABLE IF NOT EXISTS MEMBER (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, address TEXT, phone TEXT)";
            
            if (sqlite3_exec(db, sql, NULL, NULL, &errMsg) != SQLITE_OK) {
                NSLog( @"Failed to create table");
                return NO;
            }
            sqlite3_close(db);
            return YES;
        }
        else {
            NSLog( @"Failed to open/create database");
            return NO;
        }
    }else{
        NSLog(@"Database already created.");
        return YES;
    }
}

4. 使用範例

4.1 新增

            char *errorMsg;
            const char *insertSql="insert into member(name,company) values('Orange','iOTEC Systems')";
            if (sqlite3_exec(db, insertSql, NULL, NULL, &errorMsg)==SQLITE_OK) {
                NSLog(@"INSERT OK");
            }else{
                NSLog(@"Insert error: %s",errorMsg);
            }

4.2 查詢

            const char *sql = "select * from member";
            sqlite3_stmt *statement =nil;
            if (sqlite3_prepare_v2(db, sql, -1, &statement, NULL) == SQLITE_OK) {
                while (sqlite3_step(statement) == SQLITE_ROW) {
                    
                    NSString *_id,*name, *company;
                    
                    _id = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 0)];
                    name = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 1)];
                    company = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 2)];
                    
                    NSLog(@"Record: %@> %@ , %@",_id, name, company);
                }
                
                sqlite3_finalize(statement);
            }

4.3 修改

            char *errorMsg;
            const char *sql = "UPDATE member SET name='Apple' WHERE name='Orange'";
            
            if (sqlite3_exec(db, sql, NULL, NULL, &errorMsg)==SQLITE_OK) {
                NSLog(@"UPDATE OK");
            }else{
                NSLog(@"UPDATE error: %s",errorMsg);
            }

4.4 刪除

            char *errorMsg;
            const char *sql = "DELETE FROM member WHERE name='Apple'";
            
            if (sqlite3_exec(db, sql, NULL, NULL, &errorMsg)==SQLITE_OK) {
                NSLog(@"DELETE OK");
            }else{
                NSLog(@"DELETE error: %s",errorMsg);
            }

範例程式請參考SQLite example on GitHub

One thought on “iOS 儲存資料與 SQLite 使用範例

Leave a Reply to Bachelor of Visual Communication Design Telkom University Cancel reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.