OptionSet и Bit flags

swift

#1

Bit flags

Bit flags - древняя фича пришедшая к нам из языков отцов наших (у кого-то уже дедов) aka C и С++. Из названия понятно что какие то биты могут быть включены (флаги подняты, ну мне по крайне мере так понимается), стало быть, это должно позволить нам записывать несколько логических значений в одну переменную.

Для начала объявим псевдоним Option для UIInt16, это будет тип для наших значений:

typealias Option = UInt16

Затем объявим значения Option которые будем присваивать переменной:

let
    Option0: Option = 1 << 0,
    Option1: Option = 1 << 1,
    Option2: Option = 1 << 2,
    Option3: Option = 1 << 3,
    Option4: Option = 1 << 4

<< Оператор побитового сдвига влево и он описан здесь, не закрывайте страницу она ещё пригодится.

В двоичном виде это можно представить как:

//Option0 == 0000 0000 0000 0001
//Option1 == 0000 0000 0000 0010
//Option2 == 0000 0000 0000 0100
//Option3 == 0000 0000 0000 1000
//Option4 == 0000 0000 0001 0000

Единица (десятичная) это один бит и мы просто двигаем этот бит на 0…4 позиций влево.

Можно открыть калькулятор и подвигать её туда сюда:

Объявим константу options и запишем туда несколько значений Option:

// let options = Option0 // можно и одно
let options: Option = Option0 | Option1 | Option3

Палочка это оператор OR который хорошо описан здесь он возвращает единицы (двоичные) на тех позициях на которых они есть.

Эх были времена мы и в свифте так делали.

В options теперь хранятся три Option и теперь точно должно быть понятно зачем двигать единицы:

// 0000 0000 0000 1011

Ну и что бы польза от этого была, объявим функцию которая принимает тип Option и проверят какие значения в нем содержатся:

func test(option: Option) {
    if Option0 & option == Option0 {
        print("Option0")
    }
    if Option1 & option == Option1 {
        print("Option1")
    }
    if Option2 & option == Option2 {
        print("Option2")
    }
    if Option3 & option == Option3 {
        print("Option3")
    }
    if Option4 & option == Option4 {
        print("Option4")
    }
}

& оператор энд и он возвращает единицу только если она есть в значениях слева и справа (описание здесь), дальше сравниваем полученный результат с каждым Option и если он там содержится, печатаем.

Дешево и сердито :wink:

OptionSet

OptionSet - работает аналогичным образом)

Объявим структуру Option, которая соответствует протоколу OptionSet, который обязывает объявить целочисленную переменную rawValue:

struct Option: OptionSet {
    var rawValue: UInt16
}

И зададим несколько значений подобно вышеописанной схеме:

struct Option: OptionSet {
    var rawValue: UInt16
    static let option0 = Option(rawValue: 1 << 0)
    static let option1 = Option(rawValue: 1 << 1)
    static let option2 = Option(rawValue: 1 << 2)
    static let option3 = Option(rawValue: 1 << 3)
    static let option4 = Option(rawValue: 1 << 4)
}

И объявление константы options теперь будет выглядет так:

let options: Option = .option0

Или можно использовать литерал массива для нескольких значений (никаких непонятных операторов):

let options: Option = [.option0, .option1, .option3]

Но всё удобство не в этом, а в том что протокол OptionSet по дефолту реализует много удобных методов для взаимодействия со значениями, например метод contains, и вновь переписанная функция test будет выглядеть гораздо читабельней:

func test(option: Option) {
    if option.contains(.option0) {
        print("Option0")
    }
    if option.contains(.option1) {
        print("Option1")
    }
    if option.contains(.option2) {
        print("Option2")
    }
    if option.contains(.option3) {
        print("Option3")
    }
    if option.contains(.option4) {
        print("Option4")
    }
}

Все методы и их описание можно посмотреть нажав в Xcode “jump to definition” по OptionSet.

Преимущества я думаю очевидны - OptionSet предоставляет современную реализацию, без лишней магии :+1: