Тестирование на соответствие протоколу RawRepresentable и приведение к нему - PullRequest
1 голос
/ 20 июня 2020

У меня есть некоторый общий c код, который позволяет мне читать и записывать различные типы в систему по умолчанию, например, средства получения и установки значений:

var value : T {
    
    get {
        if T.self == Int.self {
            return UserDefaults.standard.integer(forKey: storageKey) as! T
        } else if T.self == Double.self {
            return UserDefaults.standard.double(forKey: storageKey) as! T
        } else if T.self == Float.self {
            return UserDefaults.standard.float(forKey: storageKey) as! T
        } else if T.self == Bool.self {
            return UserDefaults.standard.bool(forKey: storageKey) as! T
        } else if T.self == String.self {
            return UserDefaults.standard.string(forKey: storageKey) as! T
        } else {
            return UserDefaults.standard.value(forKey: self.storageKey) as! T
        }
    }
    
    set(value) {
        UserDefaults.standard.set(value, forKey: storageKey)
        UserDefaults.standard.synchronize()
    }
}

Теперь я хочу добавить свои собственные типы перечислений в этот механизм, создавая их RawRepresentable<Int>, например

enum Direction : Int, RawRepresentable {
    case left = 0
    case right = 1
}

К сожалению, я не могу найти заклинание magi c, чтобы проверить, соответствует ли T протоколу RawRepresentable, и я не могу использовать T по протоколу RawRepresentable, потому что, что бы я ни пробовал, я всегда получаю Protocol 'RawRepresentable' can only be used as a generic constraint because it has Self or associated type requirements.

Я пробовал каждое заклинание where и as, пока не начал сомневаться в этом это вообще можно сделать!?

Я использую Swift 5, и цель состоит в том, чтобы создать новый экземпляр, вызвав CustomType(rawValue:) и получив значение Int, вызвав myValue.rawValue.

1 Ответ

1 голос
/ 20 июня 2020

Как сказал @vadian, все эти проверки типов можно заменить одним вызовом UserDefaults.standard.object() и условным приведением типов. Также тип свойства value должен быть необязательным для обработки случая, когда свойство не установлено (или имеет неправильный тип):

struct DefaultKey<T> {
    let storageKey: String
    
    var value: T? {
        get {
            return UserDefaults.standard.object(forKey: storageKey) as? T
        }
        nonmutating set {
            UserDefaults.standard.set(newValue, forKey: storageKey)
        }
    }
}

И затем вы можете определить ограниченное расширение метод, в котором вы специализируете вычисляемое свойство для случая типов RawRepresentable:

extension DefaultKey where T: RawRepresentable {
    var value: T? {
        get {
            if let rawValue = UserDefaults.standard.object(forKey: storageKey) as? T.RawValue {
                return T(rawValue: rawValue)
            }
            return nil
        }
        nonmutating set {
            UserDefaults.standard.set(newValue?.rawValue, forKey: storageKey)
        }
    }
}

Пример использования:

enum Direction : Int {
    case left = 0
    case right = 1
}

let key1 = DefaultKey<Int>(storageKey: "foo")
key1.value = 123
let key2 = DefaultKey<Direction>(storageKey: "bar")
key2.value = .right

print(key1.value as Any) // Optional(123)
print(key2.value as Any) // Optional(Direction.right)

Обратите внимание, что это все еще может взломать sh, если используется с не -типы списка свойств. На всякий случай вам придется ограничить расширения типами, которые, как известно, являются сохраняемыми по умолчанию пользователем (целые числа, числа с плавающей запятой, строки, ...):

protocol UserDefaultsStorable {}
extension Int: UserDefaultsStorable {}
extension Float: UserDefaultsStorable {}
// ...

struct DefaultKey<T> {
        let storageKey: String
}

extension DefaultKey where T: UserDefaultsStorable { .. }

extension DefaultKey where T: RawRepresentable, T.RawValue: UserDefaultsStorable { ... }
...