Bitmask (bitwise) values in Core Data
Swift’s OptionSet is great type. Here’s how you save and read such values in Core Data.
OptionSet is great tool of the Swift language. It’s fantastic for modelling multi-option values like: blog tags, various kinds of flags and markers etc.
Here’s a typical (for me) use case: customise cell design where content can have label and icon, might have a badge, maybe disclosure indicator, some kind of background view with or without shadow etc. You can either create a separate UICVCell subclass for each combo or do them all at once and then supply a value with enabled options:
struct LayoutFlags: OptionSet {
public let rawValue: Int
public init(rawValue:Int) {
self.rawValue = rawValue
}
static let label = Self(rawValue: 1)
static let icon = Self(rawValue: 2)
static let badge = Self(rawValue: 4)
static let disclosure = Self(rawValue: 8)
static let backView = Self(rawValue: 16)
static let shadowBackView = Self(rawValue: 32)
}
let value: LayoutFlags = [.label, .badge]
Another implementation might be to model bitwise masks:
struct BIOMarker: OptionSet {
let rawValue: Int64
init(rawValue: Int64) {
self.rawValue = rawValue
}
static let RAW = BIOMarker(rawValue: 1 << 0)
static let HRM = BIOMarker(rawValue: 1 << 1)
static let PLX = BIOMarker(rawValue: 1 << 2)
}
let value: BIOMarker = [.RAW, .PLX]
How can you save these values into Core Data store, which has no idea about OptionSet
? Simple: you save/restore the rawValue
which is Int
or Int64
or anything else.
To avoid modelling the transformation manually, you can build on the idea I wrote about in 2017: use custom userInfo of the CoreData modeler to set actual type name you want to use, for (mogenerator) example:
attributeValueScalarType: BIOMarker
First we make OptionSet
adopt CoreDataRepresentable
protocol described in that post:
extension OptionSet where Self: CoreDataRepresentable {
var coredataValue: Self.RawValue { return self.rawValue }
init?(coredataValue: CoreDataBaseType) {
self.init(rawValue: coredataValue as! Self.RawValue)
}
}
Our OptionSet type needs to specify what’s the fallback value, simplest way is empty set:
extension BIOMarker: CoreDataRepresentable {
static let coredataFallback: BIOMarker = []
}
Then using mogenerator or SwiftGen, you can parse the model and generate the code we need for entity attributes where such userInfo is set. This is the code we need generated:
var bioMarker: BIOMarker {
get {
let key = "bioMarker"
willAccessValue(forKey: key)
defer { didAccessValue(forKey: key) }
if let primitiveValue = primitiveValue(forKey: key) as? BIOMarker.CoreDataBaseType, let value = BIOMarker(coredataValue: primitiveValue) {
return value
}
return BIOMarker.coredataFallback
}
set {
let key = "bioMarker"
willChangeValue(forKey: key)
defer { didChangeValue(forKey: key) }
setPrimitiveValue(newValue.coredataValue, forKey: key)
}
}
Code like this is very scriptable which is the reason I mention mogenerator or SwiftGen. Never maintain model parsing code by hand, you are certain to forget updating it at some point; it’s best to setup Run Script build phase that does this on each build.
OK — now that we have such values in Core Data store, can we build predicates to read data based on these values? Sure we can — SQLite has support for bitwise operators since 2001 so it’s easy to write a predicate to read all entities where HRM
bit is set:
let hrm: BIOMarker = [.HRM]
NSPredicate(
format: "(%K & %i) != 0",
"bioMarker",
hrm.rawValue
)