Swift 进阶笔记-业务化 Tips(4)-键路径 KeyPath

KeyPath

Swift 4.0 后添加了类型安全的 KeyPath 语法,KeyPath 是一个能对类型指向属性但未调用的引用的非常酷的特性。并且能让 struct 也支持 KVC。

struct User {
    let name: String
    var age: Int
}
let nameKeyPath = \User.name
let batman = User(name: "Bruce Wayne", age: 1)

batman[keyPath: nameKeyPath] // "Bruce Wayne"

KVO

KVO 是 iOS 开发中非常常见的一种观察者模式,这里不再赘述基本用法以及原理。但在使用 Swift 开发中,却没有OC中使用地那么频繁。在今年六月的 WWDC,Swift 为 KVO 做了一些 API 上的调整,使得 KVO 变得更加好用。

let observation = batman.observe(\.name, options: .new) { _, change in 
    ...
}
/// 销毁
observation.invalidate()

在 Swift 4.0 以前,KeyPath 需要开发者手动设定字符串。如今使用 KeyPath 也让 KVO 变得更加简单安全更加优雅可读,但依然只有 NSObject 才能支持 KVO。

利用绑定的数据异步更新

首先为了使用 KVO,必须让涉及到的数据用 @objc dynamic 标示。

import Foundation

class User: NSObject {
    
    // KVO-enabled properties must be @objc dynamic
    @objc dynamic var name: String
    
    init(name: String, age: Int) {
        self.name = name
    }
}

extension User {
    override var description: String {
        return "name: \(name)"
    }
}

这里为了方便展示,自定义了 description,Swift 中类默认继承了 CustomStringConvertible,重写父类的 description 即可;结构体则需要引入 CustomStringConvertible 协议才能自定义 description。

接下来处理 KVO,将其封装为输入输出的形式。

/// 这段截取自 《Swift 进阶》
extension NSObjectProtocol where Self: NSObject {
    func observe<A, Other>(_ keyPath: KeyPath<Self, A>, writeTo other: Other, _ otherKeyPath: ReferenceWritableKeyPath<Other, A>)
        -> NSKeyValueObservation where A: Equatable, Other: NSObjectProtocol
    {
        return observe(keyPath, options: .new) { _, change in
            guard let newValue = change.newValue, other[keyPath: otherKeyPath] != newValue else {
                return
            }
            other[keyPath: otherKeyPath] = newValue
        }
    }
}

这里输入两个 KeyPath,为前者添加 KVO,更新内容后后者也同样更新。
和书中不同的是,我们只需要从数据更新到 UI,所以不使用双向绑定,双向绑定输入两个变量 KeyPath,返回两个 observation,有兴趣的可以根据上段方法自己实现,这里未使用就不放出来了)

接着实现我们的 ViewController:

import UIKit

class ViewController: UIViewController {

    var user = User(name: "Gua Li")
    
    /// Just Display current user props
    @IBOutlet weak var displayLabel: UILabel!
    
    var observation: NSKeyValueObservation?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 绑定
        observation = user.observe(\User.name, writeTo: displayLabel, \UILabel.text!)
        
        // 请求最新数据
        getUserName { [weak self] in self?.user.name = $0 }
    }
    
    deinit {
        // 解绑
        observation?.invalidate()
    }
    
    /// 假装自己是个网络请求
    func getUserName(completionHandler: ((String) -> Void)?) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            completionHandler?("Peter Parker")
        }
    }
}

viewDidLoad中 进行绑定,并做一个网络请求(假)(捂脸)来获取用户 name。

效果展示

KVO.gif

最终网络请求完成以后(2秒后)

getUserName { [weak self] in self?.user.name = $0 }

可以看到,仅仅改变了User的name值,displayLabel就已经更新。当然实际项目中一般不用使用 KVO 去更新网络数据,只是为了简单描述现在 KVO 是多么的简单易用。

Demo:
https://github.com/wiiale/AdvancedSwiftThinker/tree/master/T04-KeyPath-KVO

KeyPath 的作用远远不止那么局限

objc.io 中就有一篇文章利用 KeyPath 封装了子视图相对父视图布局拓展。

addSubview(childView, constraints:[
    equal(\.centerYAnchor, \.bottomAnchor),
    equal(\.leftAnchor, constant: 10), 
    equal(\.rightAnchor, constant: -10),
    equal(\.heightAnchor, constant: 100)
])

虽然上述代码使用的优雅程度在 CartographySnapKit 前稍显逊色,在实际开发场景中看约束功能也并不完善,但用于熟练与理解想必是极好的,这也足以说明 KeyPath 的潜力有待发掘。

文章Demo 汇总

本册文集中以“提出简单需求->简单实现需求片段”为流程,内容只针对该知识点实现的业务实例进行熟悉,业务也必定存在比文章方法更好的实现方式,文章旨在分析知识点并加深理解。文集不普及基本知识,不包含《Swift 进阶》书籍的详细内容。深入学习Swift请大家支持正版书籍(ObjC 中国)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,566评论 8 265
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,202评论 4 61
  • 吃过晚饭后,有车子过来接我们,于是,我便有足够的时间去准备行李。 我爬上楼,打开自己的衣柜,不慢不紧地收拾着衣物,...
    一个堕落的大学生阅读 132评论 0 0
  • 七月火伞高张 我是沉默 懒于动弹的鱼 蚂蚁游走在热锅边沿 炉火噼啪 苍蝇的尸体 是孩子肆意点上的逗号 横七竖八 河...
    黄梅梅阅读 277评论 7 10
  • 小时候家里条件不好,我的衣服大多都是别人赠予的或者拣哥哥的衣服穿,五岁的时候看到表姐穿的漂亮的公主裙来我家,我直勾...
    有风就好阅读 989评论 0 0