swiftswift

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

2018-04-30  本文已影响174人  wiiale

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 的作用远远不止那么局限,它的潜力有待发掘!

文章Demo 汇总

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

上一篇 下一篇

猜你喜欢

热点阅读