Получить все ключевые пути из структуры в Swift 4

Скажем, у меня есть эта структура:

struct MyStruct {
    let x: Bool
    let y: Bool
}

В Swift 4 мы теперь можем получить доступ к его свойствам с помощью интерфейса myStruct[keyPath: \MyStruct.x].

Мне нужен способ доступа ко всем ключевым путям, например:

extension MyStruct {

    static func getAllKeyPaths() -> [WritableKeyPath<MyStruct, Bool>] {
        return [
            \MyStruct.x,
            \MyStruct.y
        ]
    }

}

Но, очевидно, без необходимости вручную объявлять каждое свойство в массиве.

Как я могу это достичь?

Ответ 1

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ:

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

Ответ:

Я не знаю, актуален ли ваш вопрос сегодня, но задача была веселой :)

На самом деле это возможно с помощью API зеркального отображения.

В настоящее время API-интерфейс KeyPath не позволяет инициализировать новый KeyPath из строки, но он поддерживает словарь "синтаксический анализ".

Идея состоит в том, чтобы создать словарь, который будет описывать struct с использованием API зеркального отображения, а затем перебирать ключ для создания массива KeyPath.

Детская площадка Swift 4.2:

protocol KeyPathListable {
  // require empty init as the implementation use the mirroring API, which require
  // to be used on an instance. So we need to be able to create a new instance of the 
  // type.
  init()

  var _keyPathReadableFormat: [String: Any] { get }
  static var allKeyPaths: [KeyPath<Foo, Any?>] { get }
}

extension KeyPathListable {
  var _keyPathReadableFormat: [String: Any] {
    let mirror = Mirror(reflecting: self)
    var description: [String: Any] = [:]
    for case let (label?, value) in mirror.children {
      description[label] = value
    }
    return description
  }

  static var allKeyPaths: [KeyPath<Self, Any?>] {
    var keyPaths: [KeyPath<Self, Any?>] = []
    let instance = Self()
    for (key, _) in instance._keyPathReadableFormat {
      keyPaths.append(\Self._keyPathReadableFormat[key])
    }
    return keyPaths
  }
}

struct Foo: KeyPathListable {
  var x: Int
  var y: Int
}

extension Foo {
  // Custom init inside an extension to keep auto generated 'init(x:, y:)'
  init() {
    x = 0
    y = 0
  }
}

let xKey = Foo.allKeyPaths[0]
let yKey = Foo.allKeyPaths[1]

var foo = Foo(x: 10, y: 20)
let x = foo[keyPath: xKey]!
let y = foo[keyPath: yKey]!

print(x)
print(y)

Обратите внимание, что напечатанный вывод не всегда в одном и том же порядке (вероятно, из-за API зеркального отображения, но не уверен в этом).

Ответ 2

После внесения изменений в ответ рафаэля я спросил об этом на форумах Swift.

Возможно обсуждение здесь:

Получение KeyPath для участников автоматически с помощью зеркала

Кроме того, команда Swift for TensorFlow уже встроила ее в Swift для TensorFlow, которая может перейти на чистый Swift:

Динамическая итерация свойств с использованием ключевых путей