Swift学习之KeyPath

作者: 冷武橘 | 来源:发表于2023-05-18 10:11 被阅读0次

    一、什么是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))
        }
    }
    

    相关文章

      网友评论

        本文标题:Swift学习之KeyPath

        本文链接:https://www.haomeiwen.com/subject/zakisdtx.html