Saving Data in iOS

Screen Shot 2015-12-27 at 10.50.56 PM.png

Đối với các ứng dụng, việc lưu dữ liệu không chỉ là một tính năng thêm vào mà đó gần như là một điều bắt buộc đối với mỗi ứng dụng. Không chỉ là việc lưu các dữ liệu lớn mà ngay cả việc lưu lại các trạng thái, các thông số, các cài đặt của app sẽ giúp cho ứng dụng trở nên gần gũi, tiện lợi hơn rất nhiều cho người dùng

I. Lưu dữ liệu ra một file bất kỳ##

1. Lưu các biến String, Data###

Các dữ liệu cơ bản thường gặp nhất khi chúng ta sử dụng app là các dữ liệu String và Data (ví dụ như Image).

Đối với những dữ liệu cơ bản này, chúng ta có thể nhanh chóng thực hiện việc thao tác save, get dữ liệu thông qua các hàm đã được cung cấp sẵn cho mỗi class

// --- String ---
// Creating and Initializing a String from a File
init(contentsOfFile:encoding:)
init(contentsOfFile:usedEncoding:)
// Creating and Initializing a String from an URL
init(contentsOfURL:encoding:)
init(contentsOfURL:usedEncoding:)
// Writing to a File or URL
writeToFile(_:atomically:encoding:)
writeToURL(_:atomically:encoding:)
		// Example
        // Get string from file
        do {
            let savedString = try String(contentsOfFile: filePath, encoding: NSUTF8StringEncoding)
            print(savedString)
        } catch let error as NSError {
            print("error = " + error.description)
        }
        // Save string to file
        let saveString = "Hello world"
        do {
            try saveString.writeToFile(filePath, atomically: true, encoding: NSUTF8StringEncoding)
        } catch let error as NSError {
            print("Can not saving file")
            print(error.description)
        }
// --- NSData ---
// Creating and Initializing a NSData from a File
init(contentsOfFile:)
init(contentsOfFile:options:)
// Creating and Initializing a NSData from an URL
init(contentsOfURL:)
init(contentsOfURL:options:)
//Storing Data
writeToFile(_:atomically:)
writeToFile(_:options:)
writeToURL(_:atomically:)
writeToURL(_:options:)

2. Sử dụng NSFileManager###

Là công cụ quản lý file toàn diện nhất, NSFileManager cũng cung cấp cho chúng ta các hàm để get và save dữ liệu (dưới dạng NSData). Nhưng thông thường, những điều này thì bản thân NSData đã hỗ trợ rồi. Thay vào đó, chúng ta sẽ sử dụng NSFileManager như một công cụ để hỗ trợ việc tạo và quản lý các file, cây thư mục hơn là một công cụ để lưu dữ liệu.

3. Nên dùng khi nào ?###

Đây là cách lưu file cơ bản và tổng quát nhất, nó rất thuận tiện khi chúng ta muốn lưu ra các file riêng rẽ, có thể quản lý, sắp xếp file tùy ý. Đuôi file cũng không bị cố định, tùy thuộc vào các Data tương ứng mà chúng ta có thể lưu trực tiếp ra các file có thể mở bằng các ứng dụng khác nhau.

II. Các dữ liệu phân cấp

1. Property Lists###

Là các file có đuôi .plist, dùng để lưu các dữ liệu có dạng như một hệ thống phân cấp, như các biến NSArray, hay NSDictionary, với dung lượng không quá lớn. Việc lưu các dữ liệu kiểu này vào các file plist sẽ làm cho dữ liệu được lưu hiển thị một cách rõ ràng nhất

Trong iOS, Property List chỉ lưu được 1 số dạng dữ liệu cơ bản nhất định như bảng dưới đây. Tuy nhiên điều khó chịu này hoàn toàn có thể giải quyết được. Do đó, property lists xứng đáng là giải pháp hoàn hảo cho vấn đề này

Screen Shot 2015-12-23 at 11.41.09 AM.png Screen Shot 2015-12-23 at 11.45.27 AM.png

Screen Shot 2015-12-23 at 11.38.10 AM.png

2. NSUserDefaults###

NSUserDefaults là một file Property list, thường được dùng để lưu các setting hệ thống. Tuy vậy, chúng ta cũng có thể sử dụng chúng để lưu các thông tin, thường là các thông tin có dung lượng thấp, thay vì lưu chúng ra các file property list. Với việc lưu vào property list, bạn phải đọc toàn bộ nội dung file, sau đó lưu thông tin mình cần lưu vào một array, hoặc 1 dictionary rồi mới lưu vào file. Do đó việc lưu thẳng trực tiếp vào NSUserDefaults sẽ tiết kiệm rất nhiều thời gian của bạn.

3. Lưu các custom object###

Như chúng ta đã biết, không phải bất cứ khi nào dữ liệu chúng ta muốn save vào cũng là các dữ liệu cơ bản (String, Data, Bool ...). Phần dữ liệu mà chúng ta làm việc nhiều nhất chính là các object. Tuy vậy, Apple lại không hỗ trợ việc lưu trực tiếp cả object vào NSUserDefaults hoặc file .plist nói chung.

Để lưu các custom object vào NSUserDefaults, chúng ta cần thực hiện qua 1 bước trung gian để encode các custom object sang NSData, cũng như decode ngược lại từ NSData khi lấy object ra từ NSUserDefauts

Ví dụ với việc save object Job ở bên dưới

// Class Job
- (void)encodeWithCoder:(NSCoder *)encoder {
    //Encode properties, other class variables, etc
    [encoder encodeObject:self.jobID forKey:@"jobID"];
    [encoder encodeObject:self.companyName forKey:@"companyName"];
    [encoder encodeObject:self.position forKey:@"position"];
    [encoder encodeObject:self.wage forKey:@"wage"];
    [encoder encodeObject:self.category forKey:@"category"];
    [encoder encodeObject:self.subCategoryName forKey:@"subcategory"];
}

- (id)initWithCoder:(NSCoder *)decoder {
    if((self = [super init])) {
        //decode properties, other class vars
        self.jobID = [decoder decodeObjectForKey:@"jobID"];
        self.companyName = [decoder decodeObjectForKey:@"companyName"];
        self.position = [decoder decodeObjectForKey:@"position"];
        self.wage = [decoder decodeObjectForKey:@"wage"];
        self.categoryName = [decoder decodeObjectForKey:@"category"];
        self.subCategoryName = [decoder decodeObjectForKey:@"subcategory"];
    }
    return self;
}

// Save job To NSUserDefaults
- (void)saveJob:(Job *)job key:(NSString *)key {
    NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:job];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setObject:encodedObject forKey:key];
    [defaults synchronize];
}
// Load saved job from NSUserDefaults
- (MyObject *)loadSavedJobWithKey:(NSString *)key {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSData *encodedObject = [defaults objectForKey:key];
    Job *job = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject];
    return object;
}

III. Lưu và đọc dữ liệu với SQLite##

Đối với các ứng dụng có nhiều dữ liệu phức tạp, với các mối quan hệ chồng chéo, rõ ràng việc lưu trữ dữ liệu ở phần I và II không thể đáp ứng được yêu cầu về tốc độ tìm kiếm, sắp xếp, và thậm chí là hiệu năng về việc đọc và lưu dữ liệu mà ứng dụng yêu cầu. Với những ứng dụng kiểu này, chúng ta có thể sử dụng SQLite như một sự lựa chọn hoàn hảo.

1. SQLite :###

SQLite là hệ thống cơ sở dữ liệu quan hệ nhỏ gọn, hoàn chỉnh, có thể cài đặt bên trong các trình ứng dụng khác.

Đặc điểm của SQLite là gọn, nhẹ, đơn giản. Không cần cài đặt, không cần cấu hình hay khởi động mà có thể sử dụng ngay. Dữ liệu database cũng được lưu ở một file duy nhất. Không có khái niệm user, password hay quyền hạn trong SQLite database.

Đối với các hệ thống lớn thì việc sử dụng SQLite không được thích hợp nhưng với các ứng dụng mobile thì SQLite chính là sự lựa chọn hoàn hảo nhất.

2. FMDB (Flying Meat Database)###

FMDB là một thư viện hỗ trợ bạn trong việc thao tác với SQLite. Nói cách khác FMDB là một Objective-C wrapper của SQLite. Công việc của FMDB là giúp bạn thoải mái hơn trong việc thực hiện các câu lệnh SQLite.

Bạn có thể clone source code ở đây https://github.com/ccgus/fmdb

3. CoreData###

CoreData là một framework được Apple xây dựng để hỗ trợ chúng ta thao tác với SQLite database theo cách hướng đối tượng mà không phải quan tâm tới các câu lệnh của SQLite. Nó sẽ xem các bản ghi trong SQLite Database như một object, một table như một class

4. Nên sử dụng SQLite (với FMDB hỗ trợ) hay CoreData###

Rõ ràng, CoreData là một framework được Apple xây dựng để làm việc với SQLite database, rõ ràng nó được Apple khuyến khích sử dụng hơn cả. Tính hướng đối tượng, mô hình database được xây dựng trực quan, giúp việc viết và đọc code cũng rõ ràng hơn việc sử dụng FMDB để làm việc trực tiếp SQLite. Rõ ràng là rất tốt cho việc maintain cũng như phát triển code sau này. Người mới có thể đọc, hiểu mô hình database một cách cực kỳ dễ dàng. Hơn thế nữa, Apple còn hỗ trợ coder tới tận răng qua việc xử lý cache dữ liệu, tích hợp CoreData với UITableView với class NSFetchedResultsController. Bạn đọc dữ liệu từ database, lấy ra 1 list các object và hiển thị nó thành từng dòng trong tableview. Rõ ràng, cặp bài trùng CoreData + TableView quá tiện lợi và tuyệt vời.

Rất tốt, nhưng CoreData cũng có nhược điểm của nó, chính vì việc xem mỗi bản ghi như một đối tượng, và cache nó vào trong một bộ nhớ tạm làm việc thay đổi 1 số lượng lớn Object cùng lúc sẽ có 1 độ trễ cao hơn so với làm việc trực tiếp với SQLite.

Ví dụ : Với một trình RSS Reader, bạn cần đánh dấu 10.000 bản ghi là đã đọc. Với CoreData, bạn cần load ra 10.000 object, xét object.read = YES cho 10.000 object. Dù rằng bạn không muốn load cả object lên để làm gì cả. Trong trường hợp này, sử dụng SQLite với 1 câu lệnh đơn giản là update trường read của database thành YES với uniqueID trong một khoảng cho trước.

Tuy vậy, với mức độ yêu cầu của một ứng dụng mobile, có lẽ việc sử dụng CoreData là đủ về performance và là tuyệt vời để code cũng như maintain cho cả dự án

Kết##

Trên đây tôi đã giới thiệu cho bạn một số cách lưu dữ liệu. Tùy thuộc vào yêu cầu của ứng dụng mà bạn có thể sử dụng 1 hoặc nhiều cách thức để thực hiện việc lưu và get dữ liệu. Việc lưu dữ liệu tốt sẽ giúp cho công việc của chúng ta trở nên dễ dàng hơn rất nhiều. Bài viết trên chỉ giới thiệu về các cách lưu và lấy dữ liệu trong iOS, còn về mức độ đi sâu hơn như việc làm thế nào để sử dụng CoreData thì bạn có thể tham khảo các bài viết chuyên sâu hơn về CoreData ở ngay trong Viblo nhé. 😄 Thanks for reading.