Sử dụng Bitmasks trong Objective-C

438teg_b11fd26855df2b2.jpg

Bitmasks là gì?

Để biểu diễn nhiều trạng thái của một đối tượng, thay vì sử dụng nhiều biến lưu lại các trạng thái này, người ta sử dụng một biến để lưu lại trạng thái của tất cả. Kĩ thuật này được gọi là "bit masking".

Trong lập tình iOS, chắc hẳn khi xây dựng giao diện và làm việc với Auto resizing mask các bạn đều đã nhìn thấy câu lệnh sau:

view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

Câu lệnh trên có tác dụng làm cho một view tự động căn chỉnh kích thước tương ứng với view chứa nó, theo cả chiều width và height.

Bài viết này sẽ hướng dẫn chúng ta làm sao để định nghĩa và sử dụng bitmasks trong Objective-C.

Getting Started

Như đã nói ở trên, bitmask dùng một biến duy nhất để lưu lại nhiều trạng thái của một đối tượng. Giả sử chúng ta có một đối tượng là Person (người). Một Person có nhiều Trait (đặc điểm) khác nhau, ví dụ như:

  • Honest (thật thà),
  • Optimistic (lạc quan),
  • Polite (lịch thiệp),
  • Tall (cao lớn),
  • Beautiful (xinh đẹp),
  • Fat (béo),
  • BigEyes (mắt bồ câu lợn!!),
  • BlackHair (tóc đen),..

Thông thường với mỗi một đặt điểm chúng ta sẽ sử dụng một biến BOOL để kiểm tra xem người đó có phải người opimistic không? người đó có beautiful không, có big eyes không, v..v.. Với mỗi đặc điểm liệt kê ở trên thì ít nhất chúng ta đã cần đến 8 biến kiển BOOL để lưu trữ chúng. Số lượng đặc điểm càng lớn việc quản lý sẽ càng khó khăn.

Để giải quyết vấn đề này chúng ta sẽ tiến hành khai báo và sử dụng bitmask như sau:

Khai báo Bitmasks

Trước tiên khai bao một enum bao gồm toàn bộ các đặc điểm của một đối tượng:

typedef enum : NSUInteger {
   TraitsCharacterHonest       = (1 << 0),
   TraitsCharacterOptimistic   = (1 << 1),
   TraitsCharacterPolite       = (1 << 2),
   TraitsCharacterDevious      = (1 << 3),
   TraitsPhysicalTall          = (1 << 4),
   TraitsPhysicalBeautiful     = (1 << 5),
   TraitsPhysicalFat           = (1 << 6),
   TraitsPhysicalBigEyes       = (1 << 7),
   TraitsPhysicalRedHair       = (1 << 8)
} Traits;

Hoặc sử dụng macro của Apple xây dựng NS_OPTION (được cho là phù hợp nhất đối với bitmask).

typedef NS_OPTIONS(NSUInteger, Traits) {
    TraitsCharacterHonest       = (1 << 0),
    TraitsCharacterOptimistic   = (1 << 1),
    TraitsCharacterPolite       = (1 << 2),
    TraitsCharacterDevious      = (1 << 3),
    TraitsPhysicalTall          = (1 << 4),
    TraitsPhysicalBeautiful     = (1 << 5),
    TraitsPhysicalFat           = (1 << 6),
    TraitsPhysicalBigEyes       = (1 << 7),
    TraitsPhysicalRedHair       = (1 << 8),
};

Trong đối tượng Person, chúng ta khai báo một biến có kiểu Enum vừa khai báo ở trên:

@interface Person : NSObject

@property (nonatomic , assign) Traits traits;

@end

Tuyệt! Đã xong phần khai báo, bây giờ chúng ta sẽ xem làm thế nào để sử dụng bitmask.

Gán giá trị cho bitmask

Chúng ta có thể cài đặt giá trị cho bitmask như sau:

Person *john = [[Person alloc] init];
john.traits =  TraitsCharacterHonest | TraitsPhysicalTall;

Kiểm tra giá trị của bitmask Chúng ta sử dụng toán tử "&" (AND) để kiểm tra xem bitmask có bao gồm một thuộc tính nào đó hay không?

if (_traits & TraitsCharacterHonest) {
    // Do somethings with honest people
}
if (_traits & TraitsCharacterOptimistic) {
    //Do somethings with optimistic people
}
if (_traits & TraitsCharacterPolite) {
    //Do somethings with polite people
}
if (_traits & TraitsCharacterDevious) {
    //Do somethings with devious people
}

Thêm một hoặc một số giá trị

Để thêm một hoặc một số các giá trị ta sử dụng toán tử OR

_traits |= TraitsCharacterDevious;

Remove một trong số các giá trị

Đôi khi muốn xóa bỏ một số thuộc tính không còn cần thiết trong bitmask, chúng ta sử dụng toán tử "&" (AND) cùng với "~" (Reverse), như sau:

_traits &= ~TraitsPhysicalRedHair;

Đây gần như là tất cả những gì chúng ta có thể làm với bitmask, vậy câu hỏi đặt ra là tại sao phải sử dụng bitmask thay vì sử dụng nhiều biến BOOL?

Tại sao sử dụng bitmask?

Những Developer tinh ý sẽ nhận thấy rằng sử dụng bitmask trong ví dụ trên không tiết kiệm bộ nhớ bằng việc khai báo 8 biến kiểu BOOL. Với 8 biến BOOL chúng ta thực chất chỉ cần 8 byte cho chúng. Trong khi đó, với bitmask, chúng ta mất cả thảy 9 byte (8 byte cho enum, và 1 btye cho thuộc tính traits).

Vậy thì tại sao chúng ta lại sử dụng bitmask? Vì 2 lý do sau đây:

1. Khi có nhiều đối tượng, mỗi đối tượng chứa một bitmask

Hãy xét một ví dụ cụ thể, là chúng ta đang xây dựng một trò chơi mà hero phải chiến đấu với những con quái vật. Mỗi con quái vật có thể chịu đựng được một số loại tấn công khác nhau bao gồm: băng, hỏa, độc, sét, tê liệt, mù.

Để theo dõi được khả năng chịu đựng được các loại tấn công trên, nếu sử dụng mỗi biến BOOL với mỗi loại thì ta sẽ có 6 biến BOOL với mỗi quái vật. Nếu có 100 con ta có 600 biến, tương ứng với 600 byte trong bộ nhớ.

Mặt khác, nếu sử dụng bitmask, mỗi quái vật ta chỉ cần duy nhất 1 byte để lưu lại khả năng chống chịu của chúng, và 6 byte để thiết lập các tùy chọn. Với 100 quái vật, tổng cộng chúng ta chỉ tốn 106 byte bộ nhớ, chỉ nhỉnh hơn 1/6 một chút. (Giá trị này có thể khác nhau tùy vào hệ điều hành 32bit hay 64bit)

Ngày nay việc quả lý bộ nhớ trong một app iOS có vẻ như không còn quá quan trọng, và các lập trình viên đôi khi cũng ít quan tâm đến vấn đề này. Tuy nhiên, với một ứng dụng lớn, có hàng trăm nghìn đối tượng tương tự nhau thì việc sử dụng bitmask có thể giảm thiểu đáng kể việc rò rỉ bộ nhớ. Tối ưu hóa ứng dụng là một bước thực sự cần thiết để có một ứng dụng tốt nhất.

2. Khi chúng ta cần một hàm truyền vào đầy đủ các đặc điểm tùy chọn

Hãy tưởng tượng ra, ta cần xây dựng một phương thức để thay đổi giá trị của tất cả các trạng thái của một đối tượng, với cách sử dụng nhiều biến BOOL, phương thức của chúng ta sẽ có dạng như sau:

-(void)someMethod:(BOOL) option1 option2:(BOOL)option2 option3:(BOOL)option3 option4:(BOOL)option4 option5:(BOOL)option5 option6:(BOOL)option6 option7:(BOOL)option7 option8:(BOOL)option8 option9:(BOOL)option9 option10:(BOOL)option11 option12:(BOOL)option12 option13:(BOOL)option13 option14:(BOOL)option14 option15:(BOOL)option15 option16:(BOOL)option16{
    //Do somethings
}

Và khi tiến hành gọi phương thức thì sẽ giống như sau:

[self someMethod:YES option2:YES option3:NO option4:YES option5:YES option6:NO option7:YES option8:YES option9:NO option10:YES option12:NO option13:NO option14:YES option15:YES option16:YES];

Mặc dù cú pháp Objective-C hỗ trợ label cho mỗi param nhưng điều này cũng đem lại bất tiện khi phương thức vì thế trông thật khổng lồ, không một developer nào muốn xây dựng một phương thức như thế này.

Thay vào đó, mọi việc thật đơn giản với bitmask:

[self someMethod:option1 | option2 | option3 | option9 | option11];

Chúng ta chỉ cần quan tâm đến những option được chọn, còn những option khác chúng ta hoàn toàn không cần đề cập đến, quả thực rất tiện lợi đúng không nào?

Tổng kết

Bài viết này đã hướng dẫn chúng ta làm thế nào để khái báo cũng như sử dụng bitmask trong Objective-C, và nó hoàn toàn cũng có thể sử dụng được trong các ngôn ngữ khác.

Chúng ta có thể tổng kết lại như sau:

Sử dụng bitmask

  • Để truy vấn đến từng trạng thái của bit mask ta sử dụng toán tử AND:
if (_traits & TraitsCharacterHonest) {
        // Do somethings with honest people
    }
  • Để thêm một trạng thái của bitmask, ta sử dụng toán tử OR
 _traits |= TraitsCharacterDevious | TraitsCharacterOptimistic;
  • Để xóa một trạng thái của bitmask ta sử dựng toán tử AND cùng với REVESER
_traits &= ~TraitsCharacterOptimistic;

Nguồn tham khảo

vipan.com

learncpp.com

stackoverflow