map(keyPath) 其中 keyPath 是一个变量
map(keyPath) where keyPath is a variable
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)
Cannot convert value of type 'WritableKeyPath<(Int, Int), Int>' to
expected argument type '((Int, Int)) throws -> T'.
Generic parameter 'T' could not be inferred.
Array.map
需要一个带有签名 (Element) throws -> T
.
的闭包
在 Swift 5.2 中,键路径被允许作为 functions/closures 传入(这里是一个 evolution proposal),但是 只能作为文字 (至少,根据提案,它说“暂时”,所以也许这个限制会被取消)。
为了克服这个问题,您可以在 Sequence
上创建一个接受关键路径的扩展:
extension Sequence {
func map<T>(_ keyPath: KeyPath<Element, T>) -> [T] {
return map { [=10=][keyPath: keyPath] }
}
}
(记入:https://www.swiftbysundell.com/articles/the-power-of-key-paths-in-swift/)
那你就可以随心所欲了:
let keyPath = \(Int, Int).0
arr.map(keyPath)
evolution proposal 展示了如何使用运算符来完成它,但您也可以使用相同的 []
或 ()
语法,无论是否为 partially-applied,因为下标和函数不需要参数。
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 {
{ [=12=][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[[=12=]] }
}
/// Convert a `KeyPath` to a get/set accessor pair.
subscript(root: Root) -> Computed<Value> {
.init(
get: self[root],
set: { root[keyPath: self] = [=12=] }
)
}
}
/// 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
}
}
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)
Cannot convert value of type 'WritableKeyPath<(Int, Int), Int>' to expected argument type '((Int, Int)) throws -> T'.
Generic parameter 'T' could not be inferred.
Array.map
需要一个带有签名 (Element) throws -> T
.
在 Swift 5.2 中,键路径被允许作为 functions/closures 传入(这里是一个 evolution proposal),但是 只能作为文字 (至少,根据提案,它说“暂时”,所以也许这个限制会被取消)。
为了克服这个问题,您可以在 Sequence
上创建一个接受关键路径的扩展:
extension Sequence {
func map<T>(_ keyPath: KeyPath<Element, T>) -> [T] {
return map { [=10=][keyPath: keyPath] }
}
}
(记入:https://www.swiftbysundell.com/articles/the-power-of-key-paths-in-swift/)
那你就可以随心所欲了:
let keyPath = \(Int, Int).0
arr.map(keyPath)
evolution proposal 展示了如何使用运算符来完成它,但您也可以使用相同的 []
或 ()
语法,无论是否为 partially-applied,因为下标和函数不需要参数。
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 {
{ [=12=][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[[=12=]] }
}
/// Convert a `KeyPath` to a get/set accessor pair.
subscript(root: Root) -> Computed<Value> {
.init(
get: self[root],
set: { root[keyPath: self] = [=12=] }
)
}
}
/// 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
}
}