OptionSet vs Enum:Swift 中如何优雅地表达“多选”?

两种"多选"方案的基因差异

维度 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 << n2ⁿ 直观,且编译器会检查越界。

与位运算配合

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。"

相关推荐
如此风景15 小时前
Swift异步详解
swift
HarderCoder16 小时前
强制 SwiftUI 重新渲染:`.id()` 这把“重启键”你用对了吗?
swift
HarderCoder16 小时前
Swift 6.2 新语法糖:在字符串插值里直接给 Optional 写默认值
swift
HarderCoder17 小时前
窥探 `@Observable` 的“小黑盒”:private 属性到底会不会被观察?
swift
zzywxc78717 小时前
AI 在金融、医疗、教育、制造业等领域有着广泛的应用,以下是这些领域的一些落地案例
人工智能·python·spring cloud·金融·swift·空间计算
HarderCoder18 小时前
Swift 并发避坑指南:自己动手实现“原子”属性与集合
swift
HarderCoder1 天前
惊!只是 `import Foundation`,`String.contains("")` 的返回值居然变了?
swift
HarderCoder1 天前
Swift 6.2 新武器:`weak let` —— 既弱引用又不可变的安全魔法
swift
HarderCoder1 天前
吃透 Swift 的 `@autoclosure`:把“表达式”变“闭包”的延迟利器
swift
HarderCoder1 天前
@Observable 遇上属性包装器:一键绕过‘计算属性’禁令的 Swift 5.9 实战技巧
swift