+1

Code ngắn gọn hơn với OptionSet trong Swift

Nếu bạn muốn biết cách xử lý với Bitmasks trong Swift hay là bạn đã từng nghe đến OptionSet chưa? Bài viết này sẽ giúp bạn hiểu hơn về điều đó 😄

1. OptionSet là gì?

Về cơ bản, OptionSet là một protocol, khi confirm nó chúng ta có thể làm việc với bit mask. Nó là một đại diện cho việc gán mỗi bit tương ứng với một option. Hãy cùng tìm hiểu thêm nhé.

2. Cách implement OptionSet.

Dưới đây là một ví dụ cơ bản về sử dụng OptionSet:

struct MyStruct: OptionSet {
    let rawValue: Int
    static let firstOption = MyStruct(rawValue: 1 << 0)
    static let secondOption = MyStruct(rawValue: 1 << 1)
    static let thirdOption = MyStruct(rawValue: 1 << 2)
}

Giờ hãy giải thích về đoạn code trên nhé. Việc confirm OptionSet protocol yêu cầu cần khai báo rawValue, nó không nhất thiết phải thuộc loại Int mà là bất kì loại nào của FixedWidthInteger như Int8 hay Int16. Tiếp theo, bạn cũng cần khai báo static các option. Đó là tất cả những gì bạn cần làm với OptionSet 😄

Chắc hẳn bạn sẽ thắc mắc vì sao lại cần dùng static<< ?

Về mặt logic, các option cần được khai báo static vì bạn sẽ truy cập trực tiếp vào nó mà không cần khởi tạo đối tương. Tiếp theo, option được khai báo như một instance của struct với việc gán rawValue với mã nhị phân vì thế cần sử dụng toán tử <<

Ví dụ:

let one = 0b0001 // 1
let shiftedZero = one << 2 // 4 (0b0100)
let twentyOne = 0b0010101 // 21
let shiftedTewntyOne = twentyOne << 2 // 84 (1010100)

Ở ví dụ trên, chúng ta chỉ chuyển dịch các bit thành 1 và 21

static let firstOption = MyStruct(rawValue: 1 << 0)
static let secondOption = MyStruct(rawValue: 1 << 1)
static let thirdOption = MyStruct(rawValue: 1 << 2)

nó cũng giống như:

static let firstOption = MyStruct(rawValue: 1)
static let secondOption = MyStruct(rawValue: 2)
static let thirdOption = MyStruct(rawValue: 4)

Vậy tại sao lại cần dùng << có thể thuận tiện hơn khi đảm bảo rằng bạn không phá vỡ quy tắc của công thức đếm nhị phân (1,2,4,8,16,..)

Tại sao lại cần sử dụng OptionSet?

Bạn có thể đơn giản coi nó như một option kết hợp với một biến. Hãy cùng xem ví dụ dưới đây:

Ví dụ:

Hãy thử xem chúng ta có một ứng dụng cho phép người dùng chọn vật nuôi yêu thích. Với yêu cầu đó chắc hẳn bạn sẽ nghĩ ngay tới enum.

enum Pet {
    case pussycat, puppy, hamster, chameleon
}

và dễ dàng sử dụng:

let favortiePet: Pet = .puppy

Tuy nhiên, chúng ta đã biết rằng mọi thứ thay đổi rất nhiều, yêu cầu của ứng dụng đã thay đổi là người dùng có thể lựa chọn nhiều vật nuôi yêu thích. Chúng ta sẽ cần làm thế này:

let favortiePets: [Pet] = [.pussycat, .puppy]

Hiện tại thì có vẻ hợp lí, tuy nhiên trong một số trường hợp điều này không được tốt cho lắm. Chẳng hạn, nếu giá trị này cần được gửi đến một phía máy chủ thì sao? nó sẽ được gửi dưới dạng danh sách?

Bây giờ chúng ta hãy thử sử dụng OptionSet để thấy sự khác biệt:

struct Pet: OptionSet {
    let rawValue: UInt8
    static let pussycat = Pet(rawValue: 1 << 0)
    static let puppy = Pet(rawValue: 1 << 1)
    static let hamster = Pet(rawValue: 1 << 2)
    static let chameleon = Pet(rawValue: 1 << 3)
}
let singlePet: Pet = .puppy
let multiplePets: Pet = [.puppy, .pussycat]

Chú ý rằng multiplePets có type là Pet và không phải [Pet]. Nếu bạn thử in rawValue bạn sẽ thấy rằng singlePet là 2 (1<<1) và multiplePets là 3 (1<<1 + 1<<0)

Bằng cách thực hiện theo phương pháp sử dụng OptionSet, bạn có thể đại diện cho nhiều tùy chọn bằng cách chỉ sử dụng một UInt8 duy nhất:

Thú cưng yêu thích là pussycat: rawValue = 1

Vật nuôi yêu thích là hamster và chameleon: rawValue = 12 (4 + 8)

Vật nuôi yêu thích là  tất cả:  rawValue = 15 (1 + 2 + 4 + 8)

Ngoài ra còn một tính năng thú vị của OptionSet là các thao tác liên quan đến operation ví dụ:

let options1: Pet = [.pussycat, .puppy]
let options2: Pet = [.puppy, .hamster, .chameleon]
let intersection = options1.intersection(options2)
print(intersection) // Pet(rawValue: 2) 👉 puppy
let union = options1.union(options2)
print(union) // Pet(rawValue: 15) 👉 pussycat, puppy, hamster & chameleon
let subtracting = options1.subtracting(options2)
print(subtracting) // Pet(rawValue: 1) 👉 pussycat
let contains = options1.contains(.hamster)
print(contains) // false

Lưu ý:

OptionSet hoạt động dự trên bit masks, bạn có thể thấy nó là một lựa chọn tốt để lưu trữ danh sách hữu hạn các option dưới dạng một biến duy nhất. Tuy nhiên, hãy lưu ý để sử dụng nó một cách thông minh! và nó không phải là Collection

Nguồn tham khảo:

https://medium.com/@ahmadfayyas/swift-more-elegant-code-optionset-205e4866b4aa


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.