Tuesday, April 12, 2016

Phân biệt biến kiểu Property, Public, Protected, Private trong ngôn ngữ Objective C

- Theo kinh nghiệm làm việc của mình với các bạn trong nhóm khi lập trình Objective-C và cũng đọc qua code của những project cũ. Ít khi nào mọi người để ý và khai báo đúng với ý đồ của từng đối tượng, và vi phạm quy tắc tính đóng gói, tính bảo mật thông tin của đối tượng trong lập trình hướng đối tượng (Tham khảo lý thuyết Lập trình hướng đối tượng tại trang Wiki).

- Theo ngôn ngữ lập trình Java, người ta khuyến khích mỗi khi dùng biến kiểu public thì nên đặt 1 biến private và hỗ trợ những hàm getter/setter để truy suất biến private đó.
    + Nguyên nhân họ nói là đảm bảo tính đóng gói, và nếu sau này có thay đổi gì trên biến đó bạn có thể sửa được dễ dàng, chi tiết về vấn đề này ở đây.
    + Nói tóm tại thì nguyên nhân chính là có thể kiểm soát được truy xuất đến giá trị của 1 đối tượng từ bên ngoài, có thể dễ dàng mở rộng code bằng cách override lại những hàm getter/setter.

- Các bạn có thể áp dụng nguyên tắc đó từ bên Java qua ngôn ngữ lập trình Objective-C cũng được. Nhưng nên nhớ khi khai báo biến kiểu property system ObjC tự động sinh những hàm getter/setter và quản lý dùm bạn. Nếu bạn cần thêm những việc xử lý nào khác thì override lại phương thức getter/setter của property đó.

- Nếu cái gì public đều dùng property hay phương thức getter/setter hết thì những ngôn ngữ lập trình hướng đối tượng nên bỏ từ khoá public luôn đi cho rồi.

- Hiện tại mình vẫn chưa tìm ra tài liệu chính thống từ Apple nói nên dùng hay không nên dùng biến property cho biến public hay không. Vì thế chúng ta sẽ không bàn về vấn đề đó đúng hay sai. Tuỳ theo trường hợp mình áp dụng là được, hiện tại mình có những trường hợp như sau:
    + Các bạn mới học lập trình thì mình khuyên nên dùng property để khai báo biến public, để tránh những rủi ro mà bạn không lường trước được.
    + Dùng property để khai báo biến public để có 1 coding standard cho team và đảm bảo tính đóng gói trong lập trình hướng đối tượng.
    + Khi bạn cần tối ưu hoá code thì nên lưu ý vấn đề truy suất trực tiếp thông qua @public này, có thể giúp bạn được phần nào tốc độ xử lý.

- Vì thế mình viết bài này để phân tích khi nào thì cần dùng biến theo Property hay iVar (Public, Protected, Private) để các bạn tham khảo khi viết trên ngôn ngữ Objective-C (Swift mình sẽ nghiên cứu và phân tích sâu về nó sau). Các bạn có kiến thức tốt lập trình hướng đối tượng khi viết bằng các ngôn ngữ khác như Java, PHP,... có thể chuyển code đó qua Objective-C uyển chuyển và ngắn gọn. Thực chất ra cách khai báo những biến như vậy cũng rất giống những ngôn ngữ lập trình khác nhưng ít khi nào dân lập trình trên iOS bằng Objective-C dùng.

- Nếu bạn có 1 khách hàng khó và họ định nghĩa sẵn hệ thống của họ theo từng sơ đồ như class diagram đàng hoàng. Họ bắt bạn phải viết code theo đúng như thiết kế của họ thì chắc chắn bạn phải làm theo. Hiện tại mình thấy đa số dân lập trình toàn viết code làm sao chạy được, còn tính đúng đắn hay thiết kế như thế nào cho hợp lý thì thường bỏ qua. Thành ra code dư thừa rất nhiều và khó bảo trì sau này.

- Giả sử mình có 1 lớp tên là PCObject được định nghĩa như sau:

- Trong lớp này mình có những biến như privateObject, publicObject, protectedObject, những hàm getter và setter của biến name, publicPropertyObject,... Nếu trong ngôn ngữ lập trình C, C++, Java hay bất kỳ ngôn ngữ nào khác thì bắt buộc bạn phải định nghĩa lớp theo giống cách define từng biến và tên hàm setter/getter như vậy. Còn như lập trình trên Objective C, bạn có thể viết gọn hơn bằng cách khai báo như thế sau:
Định nghĩa những biến trong file PCObject.h
file PCObject.m
Mình sẽ giải thích những đoạn code trong 2 file này:
- Mặc định khi khai báo 1 biến trong interface là protected (khi không có từ khoá @private, @public, @protected) giống như tên biến mình ví dụ là defaultProtectedObject.

- Từ khoá @interface là định nghĩa 1 lớp, nếu bạn để trong file .h (header file) thì những thứ bạn viết trong đó những đối tượng có thể thấy được tên những biến và method của bạn. Ngược lại bạn để trong file .m (implementation file) là chắc chắn những biến hay phương thức trong đó là Private. Và những đối tượng khác trong project hay ngoài project cũng không thể truy xuất và thấy được. Vì thế khi nào bạn cần giấu thông tin gì quan trọng thì nên khai báo những thức đó trong file .m . Ví dụ như khi mình khởi tạo đối tượng PCObject trong 1 đối tượng khác như ViewController. Thì bạn để ý thấy mình có thể thấy được tên biến như privateObject và protectedObject. Nhưng mình không thể thấy tên biến privateOtherObject, hay biến khai báo theo dạng property được khai báo trong file PCObject.m .


Để biết thêm về vấn đề này mỗi khi bạn khởi tạo 1 project mới thì bạn nhìn vào tên file ViewController.m. Bạn sẽ thấy có 1 @interface ViewController () được khai báo trong file đó. Nhiều người chắc chắn sẽ ít khi để ý những cái này. Nhưng theo mình code trước giờ thì những thứ như biến private, property hay phương thức private mình sẽ bỏ vào trong đó, bạn có thể xem 2 hình sau:
ViewController.h
ViewController.m
+ Thực tế khi bạn viết những object trong cùng project thì không có vấn đề gì, khi nào bạn viết những object mà cho 1 framework hay 1 custom library thì nên chú ý những thứ này. Vì khi bạn public library của bạn ra thì cần cho người sử dụng framework hay library biết đầu vào và đầu ra là gì để họ có thể dễ dàng sử dụng.

- Nếu bạn khai báo @private thì những biến khai báo sau từ khoá đó sẽ là biến Private, nếu gặp từ khoá khác như @public thì sẽ chuyển những biến sau đó là Public. ObjC quản lý theo từng phân vùng của từng từ khoá.

- Thay vì theo những ngôn ngữ khác mình sẽ phải khai báo nhưng tên hàm getter và setter theo định nghĩa của class. May thay ObjC hỗ trợ cho bạn cách tự động khai báo những hàm getter và setter bằng cách dùng từ khoá @property.

- Các bạn thấy mình khai báo 1 biến property trong file .h có (nonatomic, strong) là để cho rõ ràng nhìn vào họ sẽ biết. Nếu bạn không khai báo như vậy thì mặc định của Property là atomic, strong và readWrite. Theo mình nên define ra như vậy cho rõ ràng, để người khác đọc vào cũng đỡ bị nhầm lẫn và code của bạn tối ưu hoá hơn như hình bên dưới đề cập:


- Còn vấn đề khai báo nonatomic và atomic như thế nào rất nhiều chủ đề trên mạng nói về nó, các bạn có thể tìm hiểu thêm. Theo mình trước giờ làm thì đụng đến thread mình mới dùng đến atomic, chứ bình thường mình dùng là nonatomic. Vì atomic nó đảm bảo vùng nhớ của biến đó khi chạy trên nhiều thread do đó sẽ chạy chậm hơn nonatomic. Các bạn có thể xem qua tại đây.
   + Các bạn đừng nhầm lẫn với việc 1 biến nó được lưu trữ tại 1 vùng nhớ theo dạng con trỏ và code chạy trên safe-thread.
   + Để mình giải thích chi tiết tại sao atomic chậm hơn nonatomic như sau: Quay lại với kiến thức con trỏ của đối tượng trong C++ hay ObjC, 1 đối tượng được lưu trữ 1 vùng nhớ nào đó, nếu chỉ có 1 thread(thread-safe hay non thread-safe) truy suất dữ liệu cùng vùng nhớ tại cùng thời điểm thì không có vấn đề gì. Còn nếu có 2 threads hoặc nhiều threads (có thể là thread-safe hay non thread-safe) cùng truy suất cùng 1 vùng nhớ đó tại cùng 1 thời điểm, chắc chắn sẽ bị xung đột xảy ra. Vì thế ngôn ngữ ObjC mới đẻ ra cái thuộc tính là atomic để tránh việc xung đột này. Việc check này mình ko biết xử lý như thế nào, về nguyên tắc thì chúng sẽ dùng cơ chế lock và unlock để kiểm soát, nhưng nó chắc sẽ làm chậm tốc độ ứng dụng của bạn đi xíu.
    + Vì thế đừng hỏi tại sao Apple nó đẻ ra nonatomic và atomic làm gì cho mình phải đau đầu, cứ tuỳ cơ ứng biến với nó. Theo mình để ý mình mặc định mỗi khi bạn kéo 1 đối tượng UI (như UIView hay Button,.. gì đó) tạo thành những biến property để có thể xử lý trong code là dùng nonatomic và weak. Nguyên nhân tại sao? Đối tượng liên quan đến giao diện thì bắt buộc phải chạy trên safe-thread và chỉ 1 thread có thể truy suất vùng nhớ của property đó tại 1 thời điểm do đó mặc định Apple để như vậy, còn weak để khai báo property này được trỏ đến vùng nhớ của file .xib hay storyboard. Nếu vùng nhớ đó bị huỷ thì đối tượng đó sẽ bị huỷ theo nên Apple cũng chọn thuộc tính này là mặc định cho UI property. Tuỳ theo từng trường hợp bạn xử lý như thế nào thì có thể đổi lại thành strong để nó có thể giữ lại. Trường hợp mình code trước giờ bắt buộc phải dùng strong cho View hay UI Object nào khác là như thế này: Ví dụ Mình dùng file .xib hay storyboard, có 1 màn hình mình có 2 view con là View1 và View2, mỗi khi bạn nhấn button để chuyển trạng thái và code sẽ làm động tác xoá View1 khỏi supper view để thêm View2 vào supper view hay ngược lại thì bắt buộc phải dùng strong. Vì khi gọi hàm remove view đó tại supper view mà theo kiểu weak thì chắc chắn system sẽ xoá vùng nhớ đó ngay, vì thế bạn để ý trên màn hình sẽ ko thể nào thấy view đó nếu bạn dùng weak.

- Theo nguyên tắc khi khai báo property theo kiểu ban đầu thì bắt buộc bạn phải khai báo 1 biến ở local ví dụ như _publicPropertyObject, _publicReadOnlyPropertyObject,... để lưu trữ giá trị trong đó. Và phải khai báo @synthesize đằng sau từ khoá @implementation như sau:
@synthesize publicPropertyObject = _publicPropertyObject;
@synthesize privatePropertyObject = _privatePropertyObject;
Nhưng hiện tại compiler trong XCode 6 hay XCode 7 đã tự động làm những động tác đó cho bạn mỗi khi bạn build. Do đó nếu bạn đọc code từ 1 dự án cũ mà thấy họ khai báo như vậy thì cũng đừng ngạc nhiên nhé.
Các bạn có thể xem chi tiết tại đây.

- Trong file .m bạn thấy mình khai báo biến property như privatePropertyObject và privateOtherObject là như nhau. Vì mặc định khai báo theo kiểu như vậy nó là kiểu strong rồi, bạn có thể sửa thành weak tuỳ theo nhu cầu bạn sử dụng để tối ưu hoá vùng nhớ. Theo mình đọc code theo kiểu Non-ARC thường mọi người hay dùng property kiểu retain để tận dụng cách giữ vùng nhớ đó để không để bị release giữa chừng khi đang chạy ứng dụng. Theo mình thì cách nào cũng như nhau thôi. Trừ khi nào mình có hàm để override thì mình mới dùng property để khai báo, còn không thì khai báo theo kiểu privateOtherObject là được rồi.

- Trong property có từ khoá readonly có nghĩa là biến này chỉ hỗ trợ hàm getter, khi bạn gọi hàm setter thì sẽ báo error.

Mình xin tóm tắt lại những vấn đề như sau:

Làm thế nào truy suất được những biến Public, Protected và Private?

- Bạn có thể dùng ký hiệu "->" để truy xuất những biến kiểu Public, Ví dụ mình khởi tạo biến có kiểu là PCObject trong đối tượng khác như sau:
như hình trên bạn thấy biến kiểu public thì bạn có thể truy suất trực tiếp được, còn private và protected thì không cho phép gọi và bị gạch ngang tên biến đó.

- Ví dụ thứ 2 bạn có lớp ObjectA kế thừa từ PCObject thì bạn có thể truy suất được những thuộc tính như Protected và Public của lớp cha. Nếu bạn truy suất đến những biến Private thì sẽ bị báo error như hình sau:

- Tóm lại cách truy suất giống như bất kỳ ngôn ngữ lập trình hướng đối tượng khác.

Khi nào dùng biến theo kiểu Property hay Public?

- Nếu bạn mới học lập trình ObjC thì mình khuyên nên dùng property thay cho biến public thông thường, để có thể tránh những trường hợp rủi ro mà bạn chưa lường trước được. Tạo chuẩn coding standard cho team, dễ dàng mở rộng code sau này. Đúng theo chuẩn của ngôn ngữ Java, cũng như tính đóng gói trong lập trình hướng đối tượng.

- Nếu bạn thực việc việc tính toán mà cần đến tốc độ xử lý cao thì có thể dùng @public, ví dụ như game 3D ...

Thời gian thực thi chương trình giữa biến kiểu Property và iVar cái nào chạy nhanh hơn?

- Theo phán đoán của mình thì việc dùng biến kiểu iVar sẽ chạy nhanh hơn property vì nó truy suất trực tiếp, còn property thì truy suất gián tiếp thông qua phương thức getter/setter.

- Có 1 bài blog tại trang Big Nerd Ranch cũng nói về điều này, các bạn có thể xem chi tiết tại đây. Mình xin nói sơ cách họ test và kết quả như thế nào tại đây:
+ Có 1 đoạn code lặp lại và truy xuất biến thông qua getter và ivar như sau:

Sơ đồ sau là đồ thị khi họ chạy trên thiết bị iPhone5, đơn vị được tính là Frames per Second:
Họ kết luận là phiên bản trên iOS 6 thì property chạy chậm hơn ivar 5 lần, còn trên iOS 7 thì chậm hơn 3.5 lần.
Khi phân tích debug thì property có những dòng lệnh như sau:
Còn của ivar như sau: Vì thế nguyên nhân sâu xa là property thực hiện nhiều lệnh hơn ivar nên chậm hơn.

Khi nào khai báo biến Private trong file .h (header) và file .m (implement)?

- Nếu bạn khai báo 1 biến Private mà muốn đối tượng khác khi truy vấn có thể  thấy tên biến và những comment đó để biết sử dụng như thế nào thì nên khai báo trong file .h .
- Ngược lại bạn muốn giấu luôn những tên biến Private hay phương thức private đó đi thì nên khai báo trong interface tại file .m , để đối tượng khác không thấy được mỗi khi truy xuất. Cách làm như thế nào mình có đề cập ở trên.

Các bạn có câu hỏi nào khác có thể comment tại đây, mình sẽ trả lời sau. Cám ơn các bạn đã đọc bài này.

Tài liệu tham khảo:

- Phân tích cách dùng property trong Objective-C. Theo mình đánh giá trang blog này phân tích khá chuyên sâu về property từ Non-ARC và ARC. Ai còn chưa hiểu rõ nên đọc bài này.
- So sánh giữa property và iVar (biến cục bộ của 1 lớp).

6 comments:

  1. thanks anh
    bài viết rất chi tiết và bổ ích

    ReplyDelete
  2. This comment has been removed by a blog administrator.

    ReplyDelete
    Replies
    1. Mời bạn góp ý chỗ nào chưa chuẩn với ạ, mình là dev mới nên đọc cái nào hợp lý là mình cho là đúng :3 ? cám ơn.

      Delete
    2. A Sữa comment gì bị admin xoá rùi.hihi

      Delete
    3. @nhuan ta: Do bạn này nói bài viết mình sai, nhưng khi phân tích với mình và mọi người trên group facebook "iOS Developers VN", sau cùng bạn đó đã nhận là sai, nhưng mà không chỉnh lại nên mình mới xóa comment đi.

      Delete

Note: Only a member of this blog may post a comment.