一、什么是KeyPath
KeyPath:一个指定类型下的结果值的类型的路径
class KeyPath<Root, Value>
struct Student{
var name:String = ""
let nameKeyPath:KeyPath<Student, String> = \Student.name
}
- keyPath是对实例属性的一种引用,注意是对实例的属性的引用,而不是对属性值的引用。上面Student例子可以说是对实例Student属性name值的引用,而nameKeyPaths是对实例属性name的引用,它表示name是存储在Student的一个String而已,至于具体的值是多少,我们并不知道。
1、 keyPath不需要实例就能表示属性位置及类型
2、 keyPath解释了,哪个位置有什么类型的东西
- keyPath的组成: \ 类型名称 . 属性名称
二、KeyPath的使用
struct Student{
var name:String = ""
}
let s = Student(name: "张三")
let nameKeyPath: KeyPath<Student, String> = \Student.name
let name = s[keyPath: nameKeyPath]
print(name)
- 实例对象可以通过object[keyPath:path]获取属性值
上面的例子是最基本的使用,然而还看不出其强大之处,下面继续。
代替闭包
let s = Student(name: "张三")
let s1 = Student(name: "李四")
let array = [s,s1]
let names = array.map {$0.name}
上面map闭包获取所有的名字可以用keyPath代替:
let s = Student(name: "张三")
let s1 = Student(name: "李四")
let array = [s,s1]
let names = array.map {$0.name}
let namess = array.map(\.name)
- 字面表达.name, 虽然看起来是个省略类型的keyPath,Swift对这种结构尝试会自动转换成{
object in
object[keypath:keypath)
} - 字面表达\Student.name,是个很明确的keyPath类型,Swift就不会再去转换,map那里面需要一个闭包,所以array.map(\Student.name)就会报错
类似协议封装的效果
import UIKit
struct CellConfigurator<Model> {
let titleKeyPath: KeyPath<Model, String>
let descKeyPath: KeyPath<Model, String>
func configure(_ cell: UITableViewCell?, for model: Model) {
cell?.textLabel?.text = model[keyPath: titleKeyPath]
cell?.detailTextLabel?.text = model[keyPath: descKeyPath]
}
}
struct Student{
var title:String
var desc:String
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let s = Student(title: "111", desc:"22")
let cellConfigure = CellConfigurator(titleKeyPath: \Student.title, descKeyPath: \Student.desc)
cellConfigure.configure(UITableViewCell(), for: s)
}
}
三、KeyPath的类型
- 1、KeyPath<Root, Value>,Root可以是任何指定类型,Value属性指定类型,可读。
- 2、WritableKeyPath<Root, Value>,Root只可以是指定值类型,Value属性指定类型必须是var,可读可写
- 3、ReferenceWritableKeyPath<Root, Value>,Root只可以是指定引用类型,Value属性指定类型必须是var,可读可写
struct Cat{
var name:String
}
var cat = Cat(name: "hhh")
let nameKeyPath:WritableKeyPath = \Cat.name
print(cat[keyPath: nameKeyPath])
cat[keyPath: nameKeyPath] = "aaa"
print(cat.name)
class Cat{
var name:String = "111"
}
var cat = Cat()
let nameKeyPath:ReferenceWritableKeyPath = \Cat.name
print(cat[keyPath: nameKeyPath])
cat[keyPath: nameKeyPath] = "aaa"
print(cat.name)
Converting to functions
class ListViewController {
private var items = [Item]() { didSet { render() } }
func loadItems() {
loader.load { [weak self] items in
self?.items = items
}
}
}
上面的例子,我们通常为了避免循环引用[weak self]是必不可少的,这里我们就可以通过ReferenceWritableKeyPath来更加巧妙地解决这个问题;
func setter<Object: AnyObject, Value>(for object: Object, keyPath: ReferenceWritableKeyPath<Object, Value>) -> (Value) -> Void {
return { [weak object] value in
object?[keyPath: keyPath] = value
}
}
下面让我们去我们改写原来的代码
class ListViewController {
private var items = [Item]() { didSet { render() } }
func loadItems() {
loader.load(then: setter(for: self, keyPath: \.items))
}
}
网友评论