Привет! Сегодня хочу разобрать один из самых недооцененных инструментов в
Swift:
OptionSet. Это не просто протокол, а элегантное решение для задач, с которыми мы сталкиваемся почти в каждом проекте.
Для чего нужен OptionSet:
Представьте, что вы создаете компонент списка задач. В разных местах приложения вам нужно показывать разные возможности: где-то поиск и фильтр, где-то только сортировку, а в третьем месте вообще все элементы управления сразу.
Чаще всего, когда в коде нужно описать какие-то варианты выбора, разработчики сразу вспоминают про enum. Это действительно правильный подход для ситуаций, где может быть выбран только один вариан, например при статусах заказа: «в обработке», «доставляется» или «выполнен».
Но вот в чем проблема: когда нам нужно выбрать сразу несколько вариантов одновременно (например, показать и поиск, и фильтр, и сортировку), обычное перечисление не подходит. Пришлось бы создавать отдельные варианты для всех возможных комбинаций: «поиск_и_фильтр», «поиск_и_сортировка» и так далее. Это очень неудобно и громоздко.
OptionSet решает эту задачу гораздо элегантнее. Он работает как набор независимых переключателей, представьте себе чекбоксы на сайте. Вы можете включать и выключать их в любой комбинации и для этого не нужно заранее прописывать все возможные сочетания.
Пример:
Допустим, мы делаем тот самый список задач. Вот как может выглядеть наша конфигурация:
struct TasksListOptions: OptionSet {
let rawValue: Int
static let showFilter = TasksListOptions(rawValue: 1 << 0) // 0001
static let showSearch = TasksListOptions(rawValue: 1 << 1) // 0010
static let showSort = TasksListOptions(rawValue: 1 << 2) // 0100
static let showLayoutSelector = TasksListOptions(rawValue: 1 << 3) // 1000
}
Вся мощь
OptionSet раскрывается, когда мы начинаем его использовать. Допустим, у нас есть экран «Сегодня» и экран «Все задачи». Для каждого из них мы можем легко задать нужный набор опций прямо при создании:
let todayOptions: TasksListOptions = [.showFilter, .showSearch]
let allTasksOptions: TasksListOptions = [.showFilter, .showSearch, .showSort, .showLayoutSelector]
// Для архива ничего не нужно
let archiveOptions: TasksListOptions = []
Теперь в коде это выглядит чисто и понятно. Когда мы создаем
ViewModel для каждого экрана, мы просто передаем соответствующий набор опций:
let todayViewModel = TasksListViewModel(options: todayOptions)
let allTasksViewModel = TasksListViewModel(options: allTasksOptions)
let archiveViewModel = TasksListViewModel(options: archiveOptions)
Самое удобное, что мы можем легко проверять, какие опции активны в любой момент:
class TasksListViewModel {
let options: TasksListOptions
var shouldShowSearch: Bool {
return options.contains(.showSearch)
}
var shouldShowSort: Bool {
return options.contains(.showSort)
}
}
Технически, можно было бы использовать
Set<Enum>, но зачем?
Приемущества OptionSet:
- Производительность: работа на уровне битовых операций - это максимально быстро.
- Минимальное потребление памяти: все флаги хранятся в одном числе.
- Готовый API: contains, insert, remove, union и другие операции из коробки.
Вывод:
OptionSet - это один из тех инструментов, который кажется сложным только до первого использования. На практике он очень упрощает код, делает его безопаснее и выразительнее.