Realm property with Optional String Enum

swift
realm

#1

Realm в данный момент не поддерживает тип проперти как String Enum и возможно еще не скоро сможет. Int Enum поддерживает, но с ним все ок.
Так вот, модель содержит много разных полей, тип которых это String Enum, да еще и опциональный.
Для работы предлогают небольшой трюк

@objc private dynamic var _amountType: String?
var amountType: StringEnum? {
    get { return StringEnum(rawValue: _amountType ?? "") }
    set { _amountType = newValue?.rawValue }
}

Может быть есть способ сократить это дело?
Я пробовал смотреть в сторону property wrapper, но он не подходит, т.к. там используется только один тип.
Мне же нужно в итоге 2 типа, String - сохранение rawValue в базу и StringEnum - для самой работы с проперти.


#2

Можно так закостылять :slight_smile:

class User: NSObject {
    @Container(EnumString.self) @objc dynamic var name: String? = ""
    @Container(EnumInt.self) @objc dynamic var age: Int = 0
}

enum EnumString: String {
    case first, second
}

enum EnumInt: Int {
    case first, second = 90
}

@propertyWrapper
struct Container<V, R: RawRepresentable> {
    var wrappedValue: V
    var projectedValue: R? { (wrappedValue as? R.RawValue).flatMap(R.init) }
    init(wrappedValue value: V, _ type: R.Type) { wrappedValue = value }
}

let user = User()
user.name = "first"
user.age = 90

print("\(user.name)\n\(user.$name)\n\(user.age)\n\(user.$age)")

Или так

class User: NSObject {
    @Container<String?, EnumString> @objc dynamic var name = ""
    @Container<Int, EnumInt> @objc dynamic var age = 0
}

enum EnumString: String {
    case first, second
}

enum EnumInt: Int {
    case first, second = 90
}

@propertyWrapper
struct Container<V, R: RawRepresentable> {
    var wrappedValue: V
    var projectedValue: R? { (wrappedValue as? R.RawValue).flatMap(R.init) }
}

#3

Сказать что я удивлен, это ничего не сказать.
Я не до конца прочитал о Property Wrapper и не знал что там еще есть и projectedValue.
Спасибо вам большое.

Остался один вопрос. Проекция проперти является иммутабл, значит не получится в случае с енумом присваивать значение енума
user.$age = .first // выдаст ошибку


#4
@propertyWrapper
struct Container<V, R: RawRepresentable> {
    var wrappedValue: V
    var projectedValue: R? {
        get { (wrappedValue as? R.RawValue).flatMap(R.init) }
        set {
            guard let value = newValue?.rawValue as? V else { return }
            wrappedValue = value
        }
    }
}

let user = User()
user.$name = .first

print(user.name) // Optional("first")

#5

Вот жеж. Я щас сам пытался и запутался в преобразованиях.
Еще раз спасибо.


#6

На данный момент Realm не создает в базе ячейку, если проперти обернута в такой враппер.
Сам враппер работает корректно.
Создал вопрос по этому поводу в гитхабе реалма, буду ждать ответа.


#7

Наверно objc проперти не может корректно работать с @propertyWrapper, баг свифта.


#8

Мне тут такое навеяло, под капотом (в objc) это будет NSString, а в свифте enum:

typealias ObjcEnumString = _ObjcContainer & _ObjectiveCBridgeable

protocol _ObjcContainer {
    typealias _ObjectiveCType = NSString
    var rawValue: String { get }
    init?(rawValue: String)
}

extension _ObjcContainer {
    func _bridgeToObjectiveC() -> NSString {
        rawValue as NSString
    }
    static func _forceBridgeFromObjectiveC(_ source: NSString, result: inout Self?) {
        result = Self(rawValue: source as String)
    }
    static func _conditionallyBridgeFromObjectiveC(_ source: NSString, result: inout Self?) -> Bool {
        Self(rawValue: source as String) != nil
    }
    static func _unconditionallyBridgeFromObjectiveC(_ source: NSString?) -> Self {
        Self(rawValue: source! as String)!
    }
}

enum Type: String, ObjcEnumString {
    case first, second
}

class User: NSObject {
    @objc dynamic var type: Type!
}

let u = User()

u.setValue("first", forKey: "type")

switch u.type {
case .first:
    print("first")
default:
    print("default")
}

Должно сработать.


#9

Мне даже сказать тут нечего. С Obj-C опыта совсем мало было. До такого я бы не додумался.
Но у меня есть сомнения что это сработает, иначе бы это уже давно сделали из коробки.

Вам лайк за помощь. Проверю сегодня и отпишусь.


#10

Не сработало :pensive:

Только если приводить к типу:

typealias ObjcEnumString = _ObjcContainer & ReferenceConvertible

protocol _ObjcContainer {
    typealias ReferenceType = NSString
    var rawValue: String { get }
    init?(rawValue: String)
}
extension _ObjcContainer {
    func _bridgeToObjectiveC() -> NSString { rawValue as NSString }
    static func _forceBridgeFromObjectiveC(_ source: NSString, result: inout Self?) {
        result = Self(rawValue: source as String)
    }
    static func _conditionallyBridgeFromObjectiveC(_ source: NSString, result: inout Self?) -> Bool {
        Self(rawValue: source as String) != nil
    }
    static func _unconditionallyBridgeFromObjectiveC(_ source: NSString?) -> Self {
        Self(rawValue: source! as String)!
    }
    var debugDescription: String { rawValue }
    var description: String { rawValue }
}

enum Type: String, ObjcEnumString {
    case first, second
}

class Test: Object {
    @objc dynamic var type: NSString!
}

let t = Test()
t.type = Type.first as NSString

try! realm.write {
    realm.add(t)
}

realm.objects(Test.self).forEach { obj in
    switch obj.type as Type {
    case .first:
        print("first")
    default:
        print("default")
    }
}

В realm похоже какая то дополнительная проверка типов свойств.