Swift 5 хранит и передает KeyPaths - PullRequest
0 голосов
/ 29 февраля 2020

Допустим, у меня есть следующий класс:

class User: NSObject {
  var name = "Fred"
  var age = 24
  var email = "fred@freddy.com"
  var married = false
}

Я хочу написать обобщенную c функцию, которая принимает список KeyPath s для известного типа класса, прочитайте значения и распечатать на экран. Проблема в том, что я не могу получить следующий код для компиляции, так как тип KeyPath s Value неизвестен и будет отличаться для каждого раза. Что мне нужно сделать, чтобы это работало в общем?

Рассмотрим следующее:

struct KeyPathProperties<T> {
  var name: String
  var relatedKeyPaths: [KeyPath<T, Any>]
}

extension KeyPath where Root == User {
  var properties: KeyPathProperties<Root> {
    switch self {
      case \Root.name:
        return KeyPathProperties(name: "name", relatedKeyPaths: [\Root.age, \Root.email])
      default:
        fatalError("Unknown key path")
    }
  }
}

Эта строка не компилируется:

return KeyPathProperties(name: "name", relatedKeyPaths: [\Root.age, \Root.email])

с этой ошибкой:

Cannot convert value of type 'KeyPath<User, Int>' to expected element type 'KeyPath<User, Any>'

Это то, что я sh могу сделать, например:

let myUser = User()

var keyPathProps = KeyPathProperties(name: "name", relatedKeyPaths: [\User.age, \User.email])

for keyPath in props.relatedKeyPaths {
  print("Value: \(myUser[keyPath: keyPath])")
}

Выше не будет, конечно, компилироваться. По сути, я хочу хранить keyPaths в массиве во время выполнения, поэтому я могу в определенный момент времени получить значения из User. Мне нужно знать, могу ли я переписать вышеупомянутое каким-либо образом, чтобы компилятор мог безопасно и правильно определить тип значения keyPath во время выполнения.

Это концептуальный вариант использования для гораздо более сложной архитектуры Проблема, которую я пытаюсь решить с помощью, надеюсь, меньшего количества кода.

ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ:

Во время выполнения я буду sh, чтобы отслеживать изменяемые свойства - эти свойства хранятся в Измененный массив Props в каждом объекте / экземпляре. В какой-то момент во время выполнения я смогу sh перечислить этот массив KeyPaths и напечатать их значения следующим образом:

for modifiedKeyPath in self.modifiedProps { 
  print ("\(self[keyPath: modifiedKeyPath])" 
}

Короче - мне нужно иметь возможность перехватить обобщенный c тип KeyPath в пределах KeyPathProperties. Как мне этого добиться?

СТОРОННОЕ ПРИМЕЧАНИЕ: я уже могу легко добиться этого, используя KeyPaths на основе строковых стилей Swift 3 (добавив @objc к свойствам класса). Я могу сохранить массив keyPaths как строки, а затем сделать:

let someKeyPath = #keyPath(User.email)
...

myUser.value(forKeyPath: someKeyPath)

Я просто не могу сделать это с помощью Swift 4 KeyPaths в общем.

1 Ответ

0 голосов
/ 29 февраля 2020

Ошибка говорит вам о вашем неправильном представлении:

Cannot convert value of type 'KeyPath<User, Int>' 
    to expected element type 'KeyPath<User, Any>'

Вы, кажется, думаете, что можете использовать KeyPath<User, Int>, где ожидается KeyPath<User, Any>, якобы на том основании, что Int является Любые. Но это не правда. Это обобщенные c типы, а обобщенные c типы не являются ковариантными, то есть не существует принципа замены обобщенных элементов на основе их параметризованных типов. Эти два типа фактически не связаны.

Если вам нужен массив путей ключей независимо от их параметризованных типов, вам потребуется массив PartialKeyPath или AnyKeyPath. Кажется, что в вашем случае использования root объект одинаков во всем, поэтому, вероятно, вам нужен PartialKeyPath.

...