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 и если он там содержится, печатаем.
Дешево и сердито
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 предоставляет современную реализацию, без лишней магии