有没有办法存储 Set<Calendar.Component>?

Is there a way to store Set<Calendar.Component>?

我在 Swift 上有一个相当古老的项目,它仍然使用 NSCalendar.Unit,它非常方便,因为它的 rawValue 是一个 UInt,可以存储在我的核心数据数据库。我想转移到较新的 Swift API,所以我必须使用 Set<Calendar.Component>,但似乎 Calendar.Component 只是一个没有 rawValue 的 Swift 枚举。我发现它有一个hashValue,但是Apple不建议保存它,而且Calendar.Component也不符合Encodable。那么有什么方法可以在核心数据中存储 Set<Calendar.Component> 还是我坚持使用旧的 NSCalendar.Unit

您可以使 Calendar.Component 符合 RawRepresentable,从而得到 rawValue,然后将其存储为 RawValue。或者你也可以让它符合 Codable 然后存储为 Data.

extension Calendar.Component: RawRepresentable {
    public var rawValue: Int {
        switch self {
        case .calendar:
            return 0
        case .day:
            return 1
        case .era:
            return 2
        case .hour:
            return 3
        case .minute:
            return 4
        case .month:
            return 5
        case .nanosecond:
            return 6
        case .quarter:
            return 7
        case .second:
            return 8
        case .timeZone:
            return 9
        case .weekday:
            return 10
        case .weekdayOrdinal:
            return 11
        case .weekOfMonth:
            return 12
        case .weekOfYear:
            return 13
        case .year:
            return 14
        case .yearForWeekOfYear:
            return 15
        }
    }

    public init?(rawValue: Int) {
        switch rawValue {
        case 0:
            self = .calendar
        case 1:
            self = .day
        case 2:
            self = .era
        case 3:
            self = .hour
        case 4:
            self = .minute
        case 5:
            self = .month
        case 6:
            self = .nanosecond
        case 7:
            self = .quarter
        case 8:
            self = .second
        case 9:
            self = .timeZone
        case 10:
            self = .weekday
        case 11:
            self = .weekdayOrdinal
        case 12:
            self = .weekOfMonth
        case 13:
            self = .weekOfYear
        case 14:
            self = .year
        case 15:
            self = .yearForWeekOfYear
        default:
            return nil
        }
    }
}

Codable 一致性不是必需的,RawRepresentable 应该足够了,但留在这里作为参考。

extension Calendar.Component: Codable {
    enum DecodingError: Error {
        case unknownRawValue
    }

    /// Throwable initialiser that throws when an unknown `RawValue` is passed to it
    /// Necessary for `init(from decoder:)` to be able to delegate to a non-failable init
    init(value: Int) throws {
        guard let component = Calendar.Component(rawValue: value) else {
            throw DecodingError.unknownRawValue
        }
        self = component
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let rawValue = try container.decode(Int.self)
        try self.init(value: rawValue)
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(self)
    }
}

你可以让Calendar.Component符合OptionSet协议。这将允许您使用单个值保存您的 Calendar.Component 集:

extension Calendar.Component: OptionSet, CaseIterable, Codable {
    public init(rawValue: RawValue) {
        switch rawValue {
            case 1 << 0: self = .era
            case 1 << 1: self = .year
            case 1 << 2: self = .month
            case 1 << 3: self = .day
            case 1 << 4: self = .hour
            case 1 << 5: self = .minute
            case 1 << 6: self = .second
            case 1 << 7: self = .weekday
            case 1 << 8: self = .weekdayOrdinal
            case 1 << 9: self = .quarter
            case 1 << 10: self = .weekOfMonth
            case 1 << 11: self = .weekOfYear
            case 1 << 12: self = .yearForWeekOfYear
            case 1 << 13: self = .nanosecond
            case 1 << 14: self = .calendar
            case 1 << 15: self = .timeZone
        default: self = []
        }
    }
    public var rawValue: Int {
        switch self {
        case .era: return 1 << 0
        case .year: return 1 << 1
        case .month: return 1 << 2
        case .day: return 1 << 3
        case .hour: return 1 << 4
        case .minute: return 1 << 5
        case .second: return 1 << 6
        case .weekday: return 1 << 7
        case .weekdayOrdinal: return 1 << 8
        case .quarter: return 1 << 9
        case .weekOfMonth: return 1 << 10
        case .weekOfYear: return 1 << 11
        case .yearForWeekOfYear: return 1 << 12
        case .nanosecond: return 1 << 13
        case .calendar: return 1 << 14
        case .timeZone: return 1 << 15
        }
    }
    public init() { self = [] }
    public static let allCases: [Calendar.Component] = [
        .era, .year, .month, .day, .hour, .minute, .second, .weekday, .weekdayOrdinal, .quarter, .weekOfMonth, .weekOfYear, .yearForWeekOfYear, .nanosecond, .calendar, .timeZone]
}

并实现 Set RawValue 初始值设定项和 rawValue 属性:

extension Set where Element: OptionSet & CaseIterable, Element.RawValue: FixedWidthInteger {
    var rawValue: Element.RawValue {
        var rawValue: Element.RawValue = .zero
        for (index, element) in Element.allCases.enumerated() where contains(element) {
            rawValue |= (1 << index)
        }
        return rawValue
    }
    init(rawValue: Element.RawValue) {
        self.init()
        for (index, element) in Element.allCases.enumerated()
            where (rawValue & (1 << index)) != 0 {
             insert(element)
        }
    }
}

用法:

let componentSet: Set<Calendar.Component> = [.year, .month, .day]
let rawValue = componentSet.rawValue  // 14
let loadedSet: Set<Calendar.Component> = .init(rawValue: rawValue) //  [month, day, year]