0

Hướng dẫn sử dụng SQLite quản lý CSDL trong iOS Apps

Cũng như các ứng dụng khác ta có nhu cầu lưu trữ dữ liệu và lấy dữ liệu trong một CSDL. Đối với các ứng dụng của iPhone, chúng ta có thể sử dụng cơ sở dư liệu là SQLite hoàn toàn miễn phí. SQLite là một thư viện phần mềm khép kín, không cần kết nối với Server, không cần cấu hình, truy vấn dữ liệu bằng SQL. Sau đây sẽ giới thiệu cách sử dụng SQLite vào trong ứng dụng iPhone.

Tutorial này bao gồm 3 phần. Phần đầu tiên là phần quan trọng nhất, chúng ta sẽ tạo ra một class thực hiện tất cả các thao tác với database SQLite, class này có thể sử dụng lại trong các ứng dụng khác. Phần thứ 2, chúng ta sẽ không sử dụng Xcode mà sử dụng Terminal để tạo 1 CSDL SQLite. Cuối cùng, chúng ta sẽ tạo 1 ứng dụng CRUD đơn giản sử dụng cả class được viết ở phần thứ nhất và CSDL đã được tạo ở phần thứ 2 😃

Tạo project

Khởi động Xcode, trên màn hình Welcome, chọn Create a new Xcode project.

H1.png

Ở màn hình tiếp theo, chọn Single View Application trong mục Application.

H2.png

Đặt tên cho ứng dụng ở mục Product Name và chọn Devices là iPhone

H3.png

SQLite3 Library

Trước khi bắt đầu, chúng ta cần thêm SQLite3 Library vào project

Trong Project Navigator três Xcode, click vão tên project. Sau đó, vào tab General, ở mục Licked Frameworks and Libraries, click vào dấu nút dấu +

H4.png

Tìm và chọn libsqlite3.dylib

H5.png

Tạo Database Manager Class

Sau khi đã thêm SQLite3 Library vào project, điều đầu tiên sẽ làm là tạo ra 1 class quản lý tất cả những chức năng liên quan tới CSDL. Trong class này, chúng ta sẽ viết tất cả code cần thiết để thực hiện việc truy vấn dữ liệu trên CSDL SQLite3.

Chúng ta sẽ bằng đầu bằng việc thêm 1 file mới vào project. Vào File > New > File… trong menu bar của Xcode, hoặc chỉ việc nhấn tổ hợp phím Command+N. Đầu tiên, chọn Object-C class trong mục Cocoa Touch.

H6.png

Nhấn nút Next, tong bước tiếp theo: đầu tiên chọn Subclass of là NSObject. Tiếp theo gõ tên của class ở mục Class, của chúng ta là DBManager.

H7.png

Chúng ta đã có trong tay 1 class không có nội dung, bây giờ chúng ta sẽ viết code cho nó. Thêm một số khai báo trong file DBManager.h:

@property (nonatomic, strong) NSMutableArray *columnNamesArray;

@property (nonatomic) int affectedRows;

@property (nonatomic) long long lastInsertedRowID;

-(instancetype)initWithDatabaseFilename:(NSString *)dbFilename;

-(NSArray *)loadDataFromDB:(NSString *)query;

-(void)executeQuery:(NSString *)query;

File DBManager.m:

Import sqlite3.h

#import <sqlite3.h>

Khai báo một số properties và methods

@interface DBManager()

@property (nonatomic, strong) NSString *documentsDirectory;

@property (nonatomic, strong) NSString *databaseFilename;

@property (nonatomic, strong) NSMutableArray *resultsArray;

-(void)copyDatabaseIntoDocumentsDirectory;

-(void)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable;

@end

Method initWithDatabaseFilename: Xét documents directory lưu trữ file dữ liệu, chỉ định file CSDL, và cuối cùng copy các file dữ liệu từ ứng dụng vào documents directory

-(instancetype)initWithDatabaseFilename: (NSString *)dbFilename {
    self = [super init];
    if (self) {
        // Set the documents directory path
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        self.documentsDirectory = [paths objectAtIndex:0];

        // Keep the database filename.
        self.databaseFilename = dbFilename;

        // Copy the database file into the documents directory if necessary.
        [self copyDatabaseIntoDocumentsDirectory];
    }
    return self;
}

Method copyDatabaseIntoDocumentsDirectory: Thực hiện copy dữ liệu ra file

-(void)copyDatabaseIntoDocumentsDirectory {
    NSString *destinationPath = [self.documentsDirectory stringByAppendingPathComponent:self.databaseFilename];
    // If the database file does not exist in the documents directory
    if (![[NSFileManager defaultManager] fileExistsAtPath:destinationPath]) {
        // Copy it
        NSString *sourcePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:self.databaseFilename];
        NSError *error;
        [[NSFileManager defaultManager] copyItemAtPath:sourcePath toPath:destinationPath error:&error];

        // If any errors occurred, write log message
        if (error != nil) {
            NSLog(@"%@", [error localizedDescription]);
        }
    }
}

Chúng ta sử dụng NSFileMnager để kiểm tra sự tồn tại của file và copy nó nếu cần thiết. Chúng ta truy cập vào file gốc trong bundle: NSString *destinationPath = [self.documentsDirectory stringByAppendingPathComponent:self.databaseFilename];

Method trên được sử dụng mỗi khi một đối tượng DBManager được khởi tạo.

Method runQuery: Sử dụng để execute một SQLite Query và trả về kết quả, khá là dài, tôi sẽ giải thích từng đoạn 1 phía sau, còn đây là code đầy đủ của method.

-(void)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{
    // sqlite3 object
    sqlite3 *sqlite3Database;

    // Set the database file path
    NSString *databasePath = [self.documentsDirectory stringByAppendingPathComponent:self.databaseFilename];

    // Reset results array
    if (self.resultsArray != nil) {
        [self.resultsArray removeAllObjects];
        self.resultsArray = nil;
    }
    self.resultsArray = [[NSMutableArray alloc] init];

    // Reset column names array
    if (self.columnNamesArray != nil) {
        [self.columnNamesArray removeAllObjects];
        self.columnNamesArray = nil;
    }
    self.columnNamesArray = [[NSMutableArray alloc] init];

    // Open the database
    BOOL openDatabaseResult = sqlite3_open([databasePath UTF8String], &sqlite3Database);
    if(openDatabaseResult == SQLITE_OK) {
        // Will be stored the query after having been compiled into a SQLite statement
        sqlite3_stmt *compiledStatement;

        // Load all data from database to memory
        BOOL prepareStatementResult = sqlite3_prepare_v2(sqlite3Database, query, -1, &compiledStatement, NULL);
        if(prepareStatementResult == SQLITE_OK) {
            // If the query is non-executable.
            if (!queryExecutable){

                // Keep the data for each fetched row
                NSMutableArray *dataRowArray;

                // Loop through the results and add them to the results array
                while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
                    // Contain the data of a fetched row.
                    dataRowArray = [[NSMutableArray alloc] init];

                    // Total number of columns
                    int totalColumns = sqlite3_column_count(compiledStatement);

                    // Loop through all columns and fetch each column data
                    for (int i = 0; i 0) {
                        [self.resultsArray addObject:dataRowArray];
                    }
                }
            } else {
                // There are used for an executable query (insert, update, delete)

                // Execute the query.
                BOOL executeQueryResults = sqlite3_step(compiledStatement);
                if (executeQueryResults == SQLITE_DONE) {
                    // Keep the affected rows.
                    self.affectedRows = sqlite3_changes(sqlite3Database);

                    // Keep the last inserted row ID.
                    self.lastInsertedRowID = sqlite3_last_insert_rowid(sqlite3Database);
                } else {
                    // If could not execute the query, write log message
                    NSLog(@"DB Error: %s", sqlite3_errmsg(sqlite3Database));
                }
            }
        } else {
            // In the database cannot be opened, write log message
            NSLog(@"%s", sqlite3_errmsg(sqlite3Database));
        }

        // Release the compiled statement from memory.
        sqlite3_finalize(compiledStatement);

    }

    // Close the database.
    sqlite3_close(sqlite3Database);
}

Ban đầu chúng ta thực hiện 4 nhiệm vụ: khởi tạo đối tưởng SQLite 3 để xử lý các CSDL, database path để lưu đường dẫn tới file, và 2 mảng resultsArray và columnArrays

// sqlite3 object
sqlite3 *sqlite3Database;

// Set the database file path
NSString *databasePath = [self.documentsDirectory stringByAppendingPathComponent:self.databaseFilename];

// Reset results array
if (self.resultsArray != nil) {
    [self.resultsArray removeAllObjects];
    self.resultsArray = nil;
}
self.resultsArray = [[NSMutableArray alloc] init];

// Reset column names array
if (self.columnNamesArray != nil) {
    [self.columnNamesArray removeAllObjects];
    self.columnNamesArray = nil;
}
self.columnNamesArray = [[NSMutableArray alloc] init];

Mở CSDL, và nếu không có lỗi thì sử dụng sqlite3_prepare_v2 function để thực thi SQL query

// Open the database
BOOL openDatabaseResult = sqlite3_open([databasePath UTF8String], &sqlite3Database);
if(openDatabaseResult == SQLITE_OK) {
    // Will be stored the query after having been compiled into a SQLite statement
    sqlite3_stmt *compiledStatement;

    // Load all data from database to memory
    BOOL prepareStatementResult = sqlite3_prepare_v2(sqlite3Database, query, -1, &compiledStatement, NULL);
    if(prepareStatementResult == SQLITE_OK) {

    }
}

Kiểm tra xem có phải query có phải là dạng executable hay không (update hay select). Nếu là select, lấy dữ liêuj và gán vào mảng data results

    // If the query is non-executable.
    if (!queryExecutable){

        // Keep the data for each fetched row
        NSMutableArray *dataRowArray;

        // Loop through the results and add them to the results array
        while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
        // Contain the data of a fetched row.
        dataRowArray = [[NSMutableArray alloc] init];

        // Total number of columns
        int totalColumns = sqlite3_column_count(compiledStatement);

        // Loop through all columns and fetch each column data
        for (int i = 0; i 0) {
            [self.resultsArray addObject:dataRowArray];
        }
    }
} else {

Nếu là update thì chỉ chạy SQLite query

} else {
    // There are used for an executable query (insert, update, delete)

    // Execute the query.
    BOOL executeQueryResults = sqlite3_step(compiledStatement);
    if (executeQueryResults == SQLITE_DONE) {
        // Keep the affected rows.
        self.affectedRows = sqlite3_changes(sqlite3Database);

        // Keep the last inserted row ID.
        self.lastInsertedRowID = sqlite3_last_insert_rowid(sqlite3Database);
    } else {
        // If could not execute the query, write log message
        NSLog(@"DB Error: %s", sqlite3_errmsg(sqlite3Database));
    }
}

Method loadDataFromDB: gọi method runQuery dưới dạng select (non-executable)

-(NSArray *)loadDataFromDB:(NSString *)query{
    // Run not executable query
    [self runQuery:[query UTF8String] isQueryExecutable:NO];
    // Returns loaded results
    return (NSArray *)self.resultsArray;
}

Method executeQuery: gọi method runQuery dưới dạng update (executable)

-(void)executeQuery:(NSString *)query{
    // Run executable query
    [self runQuery:[query UTF8String] isQueryExecutable:YES];
}

Dữ liệu mẫu

Database Manager class đã sẵn sàng để sử dụng, guy nhiên chúng ta vẫn chưa tiến hành tạo 1 CSDL để có thể ứng dụng class ấy. Vì vậy tôi sẽ hướng dẫn bạn cách tạo 1 CSDL SQLite bằng Terminal.

Đối với mục đích của ví dụ n ày, chúng ta sẽ không tạo ra 1 CSDL phức tạp. Ngược lại chúng ta chỉ tạo 1 bảng đặt tên là peopleInfo vớ các field như sau: peopleInfoID: kiểu integer, primary key firstname : kiểu text lastname: kiểu text age: kiểu integer

Chúng ta sẽ sử dụng terminal để tạo CSDL SQLite:

H8.png

Chạy những commands sau:

sqlite3 sampledb.sql
CREATE TABLE peopleInfo(peopleInfoID integer primary key, firstname text, lastname text, age integer);
.quit

Viết app

Mục đích chính của bài viết này là giới thiệu chi tiết cho các bạn về cách thao tác với SQLite trên iOS. Với Database Manager class đã viết trước đó, bạn hoàn toàn có thể sử dụng trong các project khác để thực hiện các thao tác với database. Vì vậy trong bài viết này, tôi sẽ chỉ đi qua một cách nhanh chóng để viết về cách tạo ứng dụng sử dụng SQLite và Database Manager class, việc bạn cần làm là tự tạo cho mình những ứng dụng như thế này sử dụng những gì chúng ta đã làm từ những phần phía trên.

Main story board:

H9.png

ViewController:

Các elements trong ViewController: UITableView, UITableViewCell

Sử dụng Database manager class lấy dữ liệu từ file database, ở đây là sampledb.sql:

- (void)viewDidLoad
{
    [super viewDidLoad];

    // Initialize delegate and data source
    self.peopleTable.delegate = self;
    self.peopleTable.dataSource = self;

    // Initialize the dbManager property
    self.dbManager = [[DBManager alloc] initWithDatabaseFilename:@"sampledb.sql"];

    // Load the data.
    [self loadData];
}

-(void)loadData{
    // Form the query
    NSString *query = @"select * from peopleInfo";

    // Get the results
    if (self.peopleInfoArray != nil) {
        self.peopleInfoArray = nil;
    }
    self.peopleInfoArray = [[NSArray alloc] initWithArray:[self.dbManager loadDataFromDB:query]];

    // Reload the table view
    [self.peopleTable reloadData];
}

Hiển thị dữ liệu ra TableView

-(UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
    // Dequeue the cell
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"peopleTableCell" forIndexPath:indexPath];

    NSInteger indexOfFirstname = [self.dbManager.columnNamesArray indexOfObject:@"firstname"];
    NSInteger indexOfLastname = [self.dbManager.columnNamesArray indexOfObject:@"lastname"];
    NSInteger indexOfAge = [self.dbManager.columnNamesArray indexOfObject:@"age"];

    // Set the loaded data to the appropriate cell labels
    cell.textLabel.text = [NSString stringWithFormat:@"%@ %@", [[self.peopleInfoArray objectAtIndex:indexPath.row] objectAtIndex:indexOfFirstname], [[self.peopleInfoArray objectAtIndex:indexPath.row] objectAtIndex:indexOfLastname]];

    cell.detailTextLabel.text = [NSString stringWithFormat:@"Age: %@", [[self.peopleInfoArray objectAtIndex:indexPath.row] objectAtIndex:indexOfAge]];

    return cell;
}

Thực hiện DELETE query để xoá dữ liệu, sau đó load lại data

-(void)tableView: (UITableView *)tableView commitEditingStyle: (UITableViewCellEditingStyle)editingStyle forRowAtIndexPath: (NSIndexPath *)indexPath {

    if (editingStyle == UITableViewCellEditingStyleDelete) {
        // Delete the selected record

        // Find the record ID
        int recordIDToDelete = [[[self.peopleInfoArray objectAtIndex:indexPath.row] objectAtIndex:0] intValue];

        // Prepare the query
        NSString *query = [NSString stringWithFormat:@"DELETE FROM peopleInfo WHERE peopleInfoID = %d", recordIDToDelete];

        // Execute the query
        [self.dbManager executeQuery:query];

        // Reload the table view
        [self loadData];
    }
}

Khi bên EditInfoViewController thực hiện update thành công 1 record, thì tiến hành load lại data

-(void)editingInfoWasFinished{
    // Reload the data
    [self loadData];
}

EditInfoViewController

Các elements trong EditInfoViewController: 3 TextField dùng để nhập dữ liệu, 1 BarButton cho nút Save

Khởi tạo dữ liệu, load dữ liệu nếu là edit

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Initialize some delegates
    self.firstnameTextField.delegate = self;
    self.lastnameTextField.delegate = self;
    self.ageTextField.delegate = self;

    // Set the navigation bar tint color.
    self.navigationController.navigationBar.tintColor = self.navigationItem.rightBarButtonItem.tintColor;

    // Initialize the dbManager object.
    self.dbManager = [[DBManager alloc] initWithDatabaseFilename:@"sampledb.sql"];

    // If record id is not -1, load data info to edit
    if (self.recordIDToEdit != -1) {
        [self loadInfoToEdit];
    }
}

-(void)loadInfoToEdit{
    // Select a person data by id query
    NSString *query = [NSString stringWithFormat:@"SELECT * FROM peopleInfo WHERE peopleInfoID = %d", self.recordIDToEdit];

    // Load data from database
    NSArray *results = [[NSArray alloc] initWithArray:[self.dbManager loadDataFromDB:query]];

    // Set the loaded data to the text fields
    self.firstnameTextField.text = [[results objectAtIndex:0] objectAtIndex:[self.dbManager.columnNamesArray indexOfObject:@"firstname"]];
    self.lastnameTextField.text = [[results objectAtIndex:0] objectAtIndex:[self.dbManager.columnNamesArray indexOfObject:@"lastname"]];
    self.ageTextField.text = [[results objectAtIndex:0] objectAtIndex:[self.dbManager.columnNamesArray indexOfObject:@"age"]];
}

@end

Thực hiện INSERT hoặc UPDATE dữ liệu sau khi nhấn nút Save

- (IBAction)saveInfo:(id)sender {
    NSString *query;
    if (self.recordIDToEdit == -1) {
        // If record id is -1, add a new
        query = [NSString stringWithFormat:@"INSERT INTO peopleInfo VALUES(null, '%@', '%@', %d)", self.firstnameTextField.text, self.lastnameTextField.text, [self.ageTextField.text intValue]];
    } else {
        // If record id is not -1, update the record
        query = [NSString stringWithFormat:@"UPDATE peopleInfo SET firstname = '%@', lastname = '%@', age = %d WHERE peopleInfoID = %d", self.firstnameTextField.text, self.lastnameTextField.text, self.ageTextField.text.intValue, self.recordIDToEdit];
    }
    // Execute the query
    [self.dbManager executeQuery:query];

    // If the query was successfully executed then pop the view controller
    if (self.dbManager.affectedRows != 0) {
        NSLog(@"Query was executed successfully. Affected rows = %d", self.dbManager.affectedRows);

        // Inform the delegate that the editing was finished
        [self.delegate editingInfoWasFinished];

        // Pop the view controller.
        [self.navigationController popViewControllerAnimated:YES];
    }
    else{
        NSLog(@"Could not execute the query");
    }
}

Tổng kết

Trong bài này chúng ta đã tìm hiểu cách làm việc với SQLite trên iOS, chúng ta có được 1 Database Manager class có thể tái sử dụng trong hầu hết project sử dụng SQLite. Hy vọng sau bài viết này, các bạn có thể tạo được những ứng dụng vô tuyệt vời có sử dụng SQLite, good luck.

Sau đây là hình ảnh của sản phẩm sau khi đã hoàn thành:

H10.png H11.png H12.png

Link source code: SQLiteSample (github)


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí