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

相关推荐
Coder_张三1 天前
iOS 26 适配
前端·ios·swift
用户38034165882971 天前
崩溃率从 0.3% 到 0.04%:我们做对的不是修 bug,是让每个崩溃只发生一次
ios·swift
东坡肘子2 天前
当 Build in Public 重新转入地下 -- 肘子的 Swift 周报 #157
人工智能·swiftui·swift
用户38034165882972 天前
Flutter 迁 SwiftUI:AI 把页面"搬"过来只用了两周,我们补结构花了两个月
ios·swift
茶底世界之下2 天前
四角变换后的最小包围视口,为什么不能只把宽高取整?
ios·swift
茶底世界之下3 天前
预乘 Alpha 图层合成为什么会越叠越暗?
架构·swift
茶底世界之下5 天前
时间线效果层,为什么不能只复用一个处理器?
ios·swift
Cc.Y5 天前
Swift深度链接的极简之道
ios·cocoa·swift
传奇开心果编程6 天前
【SwiftUI提高练中学】第9课 安全与隐私
swiftui·移动开发·swift·编程语言理论·开发范式·编程框架·声明式ui
茶底世界之下6 天前
动态切换 nearest / linear 时,为什么不能只替换 sampler
ios·swift