Swift KeyPath
swift是一门类型安全的编程语言,不像OC、Ruby等可以在运行时检查甚至改变某一种类型或者实现,swift在编译的时候,就明确一个变量的类型,但是有些时候,我们需要一些动态的特性,例如,我们不需要在乎某个对象的类型,只在乎其某些属性的类型,或者根本不需要在乎对象本身,只需要在乎其类型。swift为我们提供了这种机制KeyPath。
几种常用的KeyPath:
public class KeyPath<Root, Value> : PartialKeyPath<Root> {
}
public class WritableKeyPath<Root, Value> : KeyPath<Root, Value> {
}
public class ReferenceWritableKeyPath<Root, Value> : WritableKeyPath<Root, Value> {
}
KeyPath
是一种只读的,WritableKeyPath
是可读可写的,ReferenceWritableKeyPath
只能作用在引用类型(class
)的可读可写的KeyPath。
使用KeyPath
例如我们有一个结构体:
struct Color: CustomStringConvertible {
var red: UInt8
var green: UInt8
var blue: UInt8
var alpha: UInt8
var value: Int32 {
return Int32(red) << 3 | Int32(green) << 2 | Int32(blue) << 1 | Int32(alpha)
}
}
之前获取属性的方式,例如获取Color
的value
属性:
let color = Color.init(red: 255, green: 255, blue: 255, alpha: 255)
print(color.value)
使用KeyPath的方式如下:
print(color[keyPath: \.value])
// or
let keyPath: KeyPath<Color, Int32> = \.value
print(color[keyPath: keyPath])
通过KeyPath获取某一个对象的属性可以使用objectName[keyPath: \.propertyName]
的方式,也可以通过let keyPath: KeyPath<Color, Int32> = \.value
先创建KeyPath。
如果对象和属性是可变的话,可以使用WritableKeyPath
来改变属性值:
var mutColor = Color(red: 1, green: 1, blue: 1, alpha: 1)
mutColor[keyPath: \.red] = 255
let mutKeyPath: WritableKeyPath<Color, UInt8> = \.blue
mutColor[keyPath: mutKeyPath] = 255
用途
假设我们需要展示一个对象的所有数据,可以考虑使用KeyPath,传统的做法,例如展示一个Person的详细信息:
struct Person {
var name: String
var address: String
var postCode: String
var age: Int
}
之前的做法是:
func showPerson(p: Person) {
print(p.name)
print(p.address)
print(p.postCode)
print(p.age)
}
let tom = Person(...)
showPerson(p: tom)
但是这么做虽然没什么问题,只是不容易扩展,如果需要展示一辆汽车或者学生信息的话,就需要创建更多的方法。
可以使用KeyPath来构建一个新方法,该方法接受可变数量的KeyPath<Element, String>作为参数:
func showDetail<Element>(object: Element, keys: KeyPath<Element, String> ...) {
for one in keys {
print(object[keyPath: one])
}
}
let p = Person.init(...)
showDetail(object: p, keys: \.name, \.postCode, \.address)
如果说某些数据不是String类型的话,可以通过为对象添加一个extension 来将某些属性转换成String
extension Person {
var ageStr: String { return "\(self.age)" }
}
showDetail(object: p, keys: \.name, \.postCode, \.address, \.ageStr)
还有就是我们平常会用到排序,如果给你一组数据,并且需要按照数据的某个属性进行排序的话,也可以使用KeyPath来做通用方法:
extension Array {
func sort<Value: Comparable>(ascendingKey: KeyPath<Element, Value>) -> Array<Element> {
sorted { l, r -> Bool in
return l[keyPath: ascendingKey] < r[keyPath: ascendingKey]
}
}
func sort<Value: Comparable>(descendingKey: KeyPath<Element, Value>) -> Array<Element> {
sorted { l, r -> Bool in
return l[keyPath: descendingKey] > r[keyPath: descendingKey]
}
}
}
某些时候,需要把一个数组转换成字典,字典的Key是数据的某个属性:
extension Array {
func map<Key: Hashable>(mapKey: KeyPath<Element, Key>) -> [Key: Element] {
var result: [Key: Element] = [:]
forEach { element in
result[element[keyPath: mapKey]] = element
}
return result
}
}