两种"多选"方案的基因差异
维度 | OptionSet | Enum + Set |
---|---|---|
底层模型 | 位图(bitset) | 哈希集合(HashSet) |
存储大小 | 固定位宽(UInt8/Int/UInt64) | 动态哈希表 |
性能 | 位运算 O(1) | 哈希操作 O(1) |
组合能力 | 原生支持按位或、补集、交集 | 需手动写集合运算 |
可读性 | 需要位运算知识 | 更接近自然语言 |
最大选项数 | 受位宽限制(UInt8=8,UInt=64) | 理论无上限 |
场景 | 性能敏感、C 接口、系统 API | 业务逻辑、易读的权限/标签 |
系统框架中的 OptionSet 速览
swift
// UIView 自动布局掩码
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
// 动画选项(可以同时指定多个)
UIView.animate(withDuration: 0.3,
delay: 0,
options: [.curveEaseInOut, .allowUserInteraction]) { ... }
// JSON 序列化
let data = try JSONSerialization.data(withJSONObject: obj,
options: [.prettyPrinted, .fragmentsAllowed])
自定义 OptionSet:四行代码搞定位运算
最原始写法(不推荐)
swift
struct UserRight {
let rawValue: Int
static let create = UserRight(rawValue: 1) // 0001
static let edit = UserRight(rawValue: 2) // 0010
static let read = UserRight(rawValue: 4) // 0100
static let delete = UserRight(rawValue: 8) // 1000
}
extension UserRight: OptionSet {}
位左移写法(推荐)
swift
struct UserRight: OptionSet {
let rawValue: Int
static let create = UserRight(rawValue: 1 << 0) // 0001
static let edit = UserRight(rawValue: 1 << 1) // 0010
static let read = UserRight(rawValue: 1 << 2) // 0100
static let delete = UserRight(rawValue: 1 << 3) // 1000
// 常用组合
static let admin: UserRight = [.create, .edit, .read, .delete]
static let editor: UserRight = [.read, .edit]
}
技巧:用
1 << n
比2ⁿ
直观,且编译器会检查越界。
与位运算配合
swift
var rights: UserRight = [.read, .edit]
let b0 = rights.contains(.read) // true
let b1 = rights.contains(.delete) // false
let r = rights.insert(.delete) // 返回新的 OptionSet
rights.remove(.read) // 去掉读权限
let b2 = rights == .admin // false
可读性升级:打印友好
swift
extension UserRight: CustomStringConvertible {
var description: String {
let map: [(Self, String)] = [
(.create, "create"),
(.edit, "edit"),
(.read, "read"),
(.delete, "delete")
]
return map
.filter { contains($0.0) }
.map(\.1)
.joined(separator: ", ")
}
}
print(UserRight.admin) // "create, edit, read, delete"
Enum + Set:当可读性更重要时
定义协议(一次写完,到处复用)
swift
protocol OptionProtocol: RawRepresentable, Hashable, CaseIterable {}
枚举声明
swift
enum UserRightOption: String, OptionProtocol {
case create, edit, read, delete
}
// 使用 Set 作为容器
typealias UserRights = Set<UserRightOption>
extension Set where Element == UserRightOption {
static var editor: UserRights { [.read, .edit] }
// 这里的好处是可以直接使用Enum的allCases,而如果是optionSet只能手动写全部的case
static var admin: UserRights { Set(Element.allCases) }
}
与 OptionSet 互转
swift
extension Set where Element: OptionProtocol {
/// 转成 Int 位图,方便与 C 层交互
var rawValue: Int {
var value = 0
for (index, element) in Element.allCases.enumerated() where contains(element) {
value |= (1 << index)
}
return value
}
}
let set: UserRights = [.read, .edit]
let bits = set.rawValue // 6 (0b110)
实战选型建议
场景 | 推荐 | 理由 |
---|---|---|
系统 API、CoreGraphics、Metal 标志 | OptionSet | 必须与 C 位图交互 |
网络请求参数、数据库字段 | OptionSet | 体积小、易序列化 |
业务权限、标签、筛选器 | Enum + Set | 可读、易扩展、无位宽限制 |
需要超过 64 个选项 | Enum + Set | OptionSet 位宽不够 |
性能基准(macOS, 1M 次操作)
操作 | OptionSet (Int) | Set |
---|---|---|
contains |
0.05 ms | 0.21 ms |
insert |
0.03 ms | 0.35 ms |
内存/实例 | 8 Byte | 24 Byte+ |
PS: 这里只是展示了最基础的num+set用法。enum还有关联值,可以玩更多花活
一句话总结
- OptionSet = 位图,极致轻量,适合与底层、系统、性能敏感代码打交道。
- Enum + Set = 哈希集合,可读性高,适合业务层快速迭代。
选型口诀:"对外接口选 OptionSet,对内业务选 Enum+Set,超过 64 个选项直接 Enum。"