【问题标题】:map(keyPath) where keyPath is a variablemap(keyPath) 其中 keyPath 是一个变量
【发布时间】:2020-09-06 13:20:52
【问题描述】:
let arr = [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]
arr.map(\.0) // [1, 2, 3, 4, 5]

效果很好。但以下代码无法编译:

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

无法将 'WritableKeyPath' 类型的值转换为 预期的参数类型 '((Int, Int)) 抛出 -> T'。
无法推断通用参数“T”。

【问题讨论】:

    标签: swift swift5 keypaths


    【解决方案1】:

    Array.map 期望带有签名 (Element) throws -> T 的闭包。

    在 Swift 5.2 中,允许将键路径作为函数/闭包传入(这里是 evolution proposal),但只能作为文字(至少,根据提案,它说“暂时”,所以也许这个限制会被取消)。

    要克服这个问题,您可以在 Sequence 上创建一个接受密钥路径的扩展:

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

    (感谢:https://www.swiftbysundell.com/articles/the-power-of-key-paths-in-swift/

    然后你就可以做你想做的事了:

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

    【讨论】:

    • 同样根据同样的进化提议,你可以写let transform: ((Int, Int)) -&gt; Int = \.0然后arr.map(transform)
    【解决方案2】:

    进化提案展示了如何使用运算符进行操作,但您也可以使用相同的 []() 语法,无论是否部分应用,因为下标和函数不需要参数。

    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
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-11-02
      • 2017-11-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-31
      • 1970-01-01
      相关资源
      最近更新 更多