[Part 1] Làm việc với Model và Dictionary trong ObjC và Swift

Bài mở đầu, mình sẽ viết về vấn đề sử dụng Model và NSDictionary trong lập trình ObjC và Swift. Để không dài dòng mình vào luôn vấn đề:

Chúng ta gặp vấn đề này ở đâu trong lập trình?

  • Trong lập trình theo mô hình MVC hoặc MVVM chúng ta đều đụng tới khái niệm Model: Ví dụ làm Ruby on Rails thì Model nó là một ActiveRecord - đại khái là một bản mẫu để lấy các dữ liệu hoặc lưu dữ liệu dưới tầng logic. Nhưng trong lập trình di động thì nó không rõ ràng (nếu chúng ta không dùng SQLite hay bất kì một hệ database nào trên di động).
  • Trong việc lưu trữ thông tin (trong bộ nhớ) trong ứng dụng nói chung và trong lập trình ObjC/Swift trên ứng dụng di động nói riêng: Ví dụ lưu trữ User data để hiển thị lên các views. Trong ngữ cảnh này có thể hiểu Model là một bản mẫu, một pattern để từ đó sản sinh ra một kiểu mẫu dữ liệu thống nhất cùng loại để quản lý dữ liệu trong ứng dụng.

Nội dung bài viết này không bàn về vấn đề đầu, mà bàn về vấn đề sử dụng Model trong lập trình ObjC. Vậy ngoài Model thì người ta có thể sử dụng cái gì để lưu trữ dữ liệu và hiển thị lên views? Đó là NSDictionary hoặc một tập hợp các variables, vậy đâu là sự khác biệt giữa các cách này. Chúng ta sẽ cùng làm rõ vấn đề.

Model trong lập trình ObjC

Trong ObjC mình có thể dùng interface (à lúc học ObjC cái từ interface nó khá khó hiểu, nhưng đại khái là cứ hiểu nó là một cái class như ở bên java, interface kế thừa từ NSObject có nghĩa là mình tạo một cái class con kế thừa từ NSObject, nó có mọi thuộc tính/hàm public/internal của class. Khác ở chỗ là interface trong ObjC phải có kế thừa, nó không thể từ thinh không mà ra như class được) hoặc struct để implement. Sự khác biệt khi dùng interface và struct là ở chỗ nếu gán dữ liệu cho thuộc tính thì struct sẽ tạo một vùng nhớ mới (thay đổi địa chỉ) còn class chỉ gán con trỏ của thuộc tính đó tới vùng nhớ mới thôi (chỗ này hơi khó hiểu nhưng không quan trọng lắm). Trong bài này mình sẽ dùng class chứ không dùng struct để implement Model.

NSDictionary thì các bạn đọc trong document của Apple để biết một cách đầy đủ, ở đây mình sẽ chỉ tóm tắt những kiến thức đủ để hiểu thôi: Nó là một hash, nó là một thứ kì cục mà Apple tạo ra để lập trình viên sử dụng như một nơi lưu dữ liệu bằng cách lưu dữ liệu theo dạng key-value (value có thể là một con trỏ tới object: NSArray, Class...).

Vậy mấy cái ở trên dùng ở đâu và sự khác biệt là gì?

Để mọi thứ rõ ràng hơn mình sẽ nêu ra một ví dụ thực tế: Các bạn lấy dữ liệu từ trên server về thông qua API, hoặc đọc từ file lên hoặc parse từ đâu đó (XML, HTML, String...) một JSON. Và các bạn muốn lưu nó lại để hiển thị lên views. Lúc bây giờ các bạn có các sự lựa chọn như sau:

  • Lưu trữ vào NSDictionary rồi cứ thế mà lấy dữ liệu ra dạng key-value thôi (với user là NSDictionary), dạng như:
NSString *name = [user valueForKey:@"name"];  
  • Lưu trữ vào một Model rồi lấy dữ liệu ra:

khai báo:

@interface User : NSObject
    @property (nonatomic, copy) NSString *name;

    - (NSString *)getNameUppercase;
    - (NSString *)getNameLowercase;
@end

Sử dụng:

NSString *name = user.name;  
  • Bỏ nó vào một cái biến:
NSString *name = @"Milk Carrot";  

Các bạn sẽ thấy rằng ở cách số 1 và 2 (cái số 3 nó hiển nhên nên không có gì để nói) sẽ có điểm tiện lợi là ta có thể quản lí dễ dàng dữ liệu hơn. Vậy điểm khác biệt ở đây là gì?

  • Điểm khác biệt đầu tiên đó là Model thì base on class còn NSDictionary bản chất là một hash: Nếu dùng class thì việc truy cập tới biến name thông qua địa chỉ của con trỏ *name, còn việc lấy name ở trong NSDictionary thông qua hash table.

  • Class cho phép ta định nghĩa một cách tường minh các thuộc tính cũng như cho phép ta định nghĩa thêm các hàm phụ trợ. Ví dụ như hàm getNameUppercase hoặc getNameLowercase. Trong khi đó thì NSDictionary không định nghĩa được tường minh các thuộc tính, mà ta chỉ có thể get danh sách một list các keys. Trong Model thì kiểu của các thuộc tính phong phú hơn. Ví dụ biến *name ở user trong class luôn có kiểu là NSString thì trong NSDictionary nó chỉ có một kiểu là AnyObject (id).

  • Nếu bỏ qua các vấn đề về gợi ý hàm/thuộc tính của IDE (vì lỡ như có ai đó biến thái như mình code ObjC bằng VIM thì sao :v) thì việc truy cập tới các thuộc tính bằng class sẽ tiện lợi và rõ ràng hơn NSDictionary, vì sao? Vì bạn không thể dùng user.éc được! trình biên dịch sẽ ném cái lỗi ra ngay bởi vì bạn đâu có khai báo cái thuộc tính éc đâu, nhưng với NSDictionary bạn vẫn gọi được [user valueForKey:@"éc"].

  • Default value và crash, một vấn đề khá không ổn ở đây là nếu dùng hash mà truy cập vào một cái thuộc tính nested, lỡ nó nil thì nó crash luôn. Ví dụ [[user valueForKey:@"éc"] valueForKey:@"tèo"] nếu [user valueForKey:@"éc"] bị nil thì nó sẽ crash khi lấy giá trị bằng key @"tèo". Dĩ nhiên Model base on class cũng vẫn sẽ crash nhưng mà ta hoàn toàn có thể tránh khỏi điều đó mà không làm xấu code (kiểu phải check nil trước khi lấy nested) bằng cách thêm các hàm get value/ set default value vào Model. Hơn nữa trong ObjC [nil callAFucntion] không crash :v

Tổng kết lại ta có khá nhiều vấn đề cần suy nghĩ ở đây, nếu tạm bỏ qua vấn đề vền benchmark performance và memory lúc này thì rõ ràng sử dụng Model base on class thì có nhiều quyền năng hơn. Chúng ta hoàn toàn có thể làm thêm rất nhiều thứ vào bên trong nó ví dụ như là init các giá trị mặc định, viết thêm các hàm tiện ích vào bên trong model để tổ chức code tốt hơn, đẹp hơn, tránh cách việc crash khi truy xuất dữ liệu nested. Nâng cao hơn ta có thể lợi dụng Model với các tiện ích mà lập trình hướng đối tượng mang lại như kế thừa, tạo callback khi một thuộc tính thay đổi (dạng key-value observing) và rất nhiều thứ khác nữa. Ngược lại NSDictionary là một thư viện hash có sẵn nên việc sử dụng đơn giản và tiện lợi hơn, việc không quan tâm tới giá trị value trong key-value vừa bất lợi vừa là điểm ta có thể thực hiện nhiều black-magic và hack để giải quyết nhanh các vấn đề.

Phần tiếp theo mình sẽ bàn kĩ hơn về từng vấn đề bên trong việc sử dụng Model và NSDictionary và cách tạo ra một Model thuận tiện cho lập trình bao gồm:

  • Model tự init data dựa trên JSON mà không phải tự viết hàm initWithDictionary cho mỗi model mới khai báo dựa trên thư viện objc-runtime.
  • Model cho phép add các target cũng như cài đặt callback để tự động trigger events mỗi khi một thuộc tính của Model thay đổi giá trị bằng cách viết thư viện key-value observing. Vấn đề ở đây là nó sẽ không crash và tối ưu hoá hơn khi sử dụng cái mặc định của Apple.
  • Model lấy ý tưởng tương tự như một ActiveRecord trong Ruby on Rails.

Huy

I gave away my old skin to hold you, as a new me. Love you, Cà Rốt Sữa

Saigon, [email protected]