bản đồ (keyPath) trong đó keyPath là một biến

Sep 06 2020
let arr = [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]
arr.map(\.0) // [1, 2, 3, 4, 5]

Hoạt động tuyệt vời. Nhưng đoạn mã dưới đây không biên dịch:

let keyPath = \(Int, Int).0
arr.map(keyPath)

Không thể chuyển đổi giá trị kiểu 'WordsKeyPath <(Int, Int), Int>' thành kiểu đối số mong đợi '((Int, Int)) ném -> T'.
Không thể suy ra tham số chung 'T'.

Trả lời

4 NewDev Sep 06 2020 at 20:41

Array.mapmong đợi một sự đóng cửa với chữ ký (Element) throws -> T.

Trong Swift 5.2, các đường dẫn chính được phép chuyển vào dưới dạng hàm / đóng (đây là một đề xuất tiến hóa ), nhưng chỉ dưới dạng chữ (ít nhất, theo đề xuất, nó nói "bây giờ", vì vậy có lẽ hạn chế này sẽ được dỡ bỏ ).

Để khắc phục điều này, bạn có thể tạo một tiện ích mở rộng trên Sequenceđó chấp nhận một đường dẫn chính:

extension Sequence {
   func map<T>(_ keyPath: KeyPath<Element, T>) -> [T] {
      return map { $0[keyPath: keyPath] }
   }
}

(tín dụng để: https://www.swiftbysundell.com/articles/the-power-of-key-paths-in-swift/)

Sau đó, bạn có thể làm những gì bạn muốn:

let keyPath = \(Int, Int).0
arr.map(keyPath)
Jessy Sep 06 2020 at 22:24

Đề xuất phát triển đã chỉ ra cách thực hiện điều đó với một toán tử, nhưng bạn cũng có thể sử dụng cùng một []hoặc ()cú pháp cho dù nó được áp dụng một phần hay không, bởi vì các chỉ số và hàm không cần đối số.

let oneTo5 = 1...5
let keyPath = \(Int, Int).0
XCTAssert(
  zip(oneTo5, oneTo5).map(keyPath[]).elementsEqual(oneTo5)
)
let keyPath = \Double.isZero
XCTAssertFalse(keyPath[1.0]())
public extension KeyPath {
  /// Convert a `KeyPath` to a partially-applied get accessor.
  subscript() -> (Root) -> Value {
    { $0[keyPath: self] } } /// Convert a `KeyPath` to a get accessor. subscript(root: Root) -> () -> Value { { root[keyPath: self] } } } public extension ReferenceWritableKeyPath { /// Convert a `KeyPath` to a partially-applied get/set accessor pair. subscript() -> (Root) -> Computed<Value> { { self[$0] }
  }

  /// Convert a `KeyPath` to a get/set accessor pair.
  subscript(root: Root) -> Computed<Value> {
    .init(
      get: self[root],
      set: { root[keyPath: self] = $0 }
    )
  }
}


/// A workaround for limitations of Swift's computed properties.
///
/// Limitations of Swift's computed property accessors:
/// 1. They are not mutable.
/// 2. They cannot be referenced as closures.
@propertyWrapper public struct Computed<Value> {
  public typealias Get = () -> Value
  public typealias Set = (Value) -> Void

  public init(
    get: @escaping Get,
    set: @escaping Set
  ) {
    self.get = get
    self.set = set
  }

  public var get: Get
  public var set: Set

  public var wrappedValue: Value {
    get { get() }
    set { set(newValue) }
  }

  public var projectedValue: Self {
    get { self }
    set { self = newValue }
  }
}

//MARK:- public
public extension Computed {
  init(
    wrappedValue: Value,
    get: @escaping Get = {
      fatalError("`get` must be assigned before accessing `wrappedValue`.")
    },
    set: @escaping Set
  ) {
    self.init(get: get, set: set)
    self.wrappedValue = wrappedValue
  }
}