Swift|Swift4 - KVC与KVO

KVC和KVO是我们开发中常用的功能,现在来看一下在Swift4中的变化
KVC
在Swift4的时候,Struct也支持KVC,我们不在使用setValue: forKeypath的方式,而是使用新的语法特性,下面看一下例子,参考这里:

struct Person { var name: String }struct Book { var title: String var authors: [Person] var primaryAuthor: Person { return authors.first! } }let abelson = Person(name: "Haeold Abelson") let sussman = Person(name: "Garald Jay Sussman") var book = Book(title: "tructure and Interpretation of Computer Programs", authors: [abelson, sussman])//get //1 keyPath以\开始,然后开始组合结构体和属性 let title = book[keyPath: \Book.title] print(title) //tructure and Interpretation of Computer Programs//2 keypath可以进入多层深入搜索查找,也可以对计算属性进行操作 let name = book[keyPath: \Book.primaryAuthor.name] print(name) //Haeold Abelson//set book[keyPath: \Book.title] = "KVC" print(book[keyPath: \Book.title]) // KVC

对象的路径操作

//先获取一个路径 let authorKeyPath = \Book.primaryAuthor //拼接子路径 let nameKeyPath = authorKeyPath.appending(path: \.name) let newName = book[keyPath: nameKeyPath] print(newName) //Haeold Abelson


KVO

@objcMembers class Food: NSObject { dynamic var string: String override init() { string = "hotdog" super.init() } }let food = Food() let observation = food.observe(\.string) { (foo, changed) in print("new food string: \(foo.string)") } food.string = "not hotdog" // new food string: not hotdog

上面代码很简单,创建了一个Food类,拥有一个string属性,但是需要注意几件事情: 1)用KVO依然需要是NSObject类或子类,Swift4中swift类不再自动被推测为继承于NSObject,所以当我们在编写swift的代码时,需要为类添加@objcMembers关键字
2)注意到属性string,我们使用了dynamic关键字,主要是告诉观察者在值发生改变之后触发闭包,如果没有该关键字,那么无法观察到值的改变
【Swift|Swift4 - KVC与KVO】3)使用新语法特性\.string监听属性变化
4)最开心的事就是我们不在需要手动去除观察者,以前都需要在deinit()中去除观察者


当然,我们也可以为属性添加@objc,那么类就不在需要@objcMembers关键字


class Child: NSObject { let name: String // KVO-enabled properties must be @objc dynamic @objc dynamic var age: Intinit(name: String, age: Int) { self.name = name self.age = age super.init() }func celebrateBirthday() { age += 1 } }

使用方法是一样,接下来使用带有options参数的方法:
//Set up KVO let mia = Child(name: "Mia", age: 5) let observation = mia.observe(\.age, options: [.initial, .old]) { (child, change) in if let oldValue = https://www.it610.com/article/change.oldValue { print("\(child.name)’s age changed from \(oldValue) to \(child.age)") //Mia’s age changed from 5 to 6 } else { print("\(child.name)’s age is now \(child.age)") //Mia’s age is now 5 } } //Trigger KVO (see output in the console) mia.celebrateBirthday()//Deiniting or invalidating the observation token ends the observation observation.invalidate()//This doesn't trigger the KVO handler anymore mia.celebrateBirthday()

options参数,我们设置了两个值,.initial,.old,表示获取最开始的值,和变化前的值。KVO的options一共有4种:


public struct NSKeyValueObservingOptions : OptionSet { public init(rawValue: UInt) public static var new: NSKeyValueObservingOptions { get } //变化前的值 public static var old: NSKeyValueObservingOptions { get } //变化后的值 public static var initial: NSKeyValueObservingOptions { get } // 初始值 public static var prior: NSKeyValueObservingOptions { get } // notification变化前後的标准 }



监听WebKit加载进度

class ViewController: UIViewController { var webView: WKWebView! var urlPath: String = "https://www.baidu.com/" var progressView: UIProgressView! var observer: NSKeyValueObservation! override func viewDidLoad() { super.viewDidLoad() setupWebView() }func setupWebView() { webView = WKWebView(frame: view.frame) view.addSubview(webView)progressView = UIProgressView(frame: CGRect(x: 0, y: 43, width: view.bounds.width, height: 1.0)) navigationController?.navigationBar.addSubview(progressView)observer = webView.observe(\.estimatedProgress, options: .new) { [weak self] (_, changed) in if let new = changed.newValue { self?.changeProgress(Float(new)) } } if let url = URL(string: urlPath) { webView.load(URLRequest(url: url)) } }func changeProgress(_ progress: Float) { progressView.isHidden = progress == 1 progressView.setProgress(progress, animated: true) } }



Swift|Swift4 - KVC与KVO
文章图片



自定义观察者
1、声明一个泛型的观察者类,并拥有一个嵌套回调类
//1、 自己封装观察者 public class Observable { //2、 内嵌回调类 fileprivate class Callback { fileprivate weak var observer: AnyObject? fileprivate let options: [ObservableOptions] fileprivate let closure: (Type, ObservableOptions) -> Voidinit(observer: AnyObject, options: [ObservableOptions], closure: @escaping(Type, ObservableOptions) -> Void) { self.observer = observer self.options = options self.closure = closure } } }

Callback中关联观察者(observer)、可选项(options)、回调闭包(closure),并且观察者是弱引用可以为任意对象。
2、声明可选项结构体
public struct ObservableOptions: OptionSet { public static let initial = ObservableOptions(rawValue: 1 << 0) public static let old = ObservableOptions(rawValue: 1 << 1) public static let new = ObservableOptions(rawValue: 1 << 2)public var rawValue: Int public init(rawValue: Int) { self.rawValue = https://www.it610.com/article/rawValue } }

可以看到非常简单,遵守OptionSet协议,并拥有initial、old、new3个值,跟系统本身KVO中的NSKeyValueObservableOptions有点类似。
3、在Observable中声明泛型的value属性
// 无论什么时候值发生改变,发送通知 public var value: Type { didSet { // notification } }public init(_ value: Type) { self.value = https://www.it610.com/article/value }

4、在Observable中增加添加观察者、去除观察者等方法
// 回调 private var callbacks: [Callback] = []// 添加观察者 public func addObserver(_ observer: AnyObject, removeIfExists: Bool = true, options: [ObservableOptions] = [.new], closure: @escaping (Type, ObservableOptions) -> Void) { if removeIfExists { removeObserver(observer) } let callback = Callback(observer: observer, options: options, closure: closure) callbacks.append(callback)if options.contains(.initial) { closure(value, .initial) } }// 去除观察者 public func removeObserver(_ observer: AnyObject) { callbacks = callbacks.filter { $0.observer !== observer } }// 去除观察者为nil的回调 private func removeNilObserverCallbacks() { callbacks = callbacks.filter { $0.observer != nil } }

5、监听属性改变,添加回调方法
public var value: Type { didSet { // 去除为nil的通知,防止当观察者被释放掉之后然后使用所造成的crash removeNilObserverCallbacks() // 回调旧值和新值 notifyCallbacks(value: oldValue, options: .old) notifyCallbacks(value: value, options: .new) } }private func notifyCallbacks(value: Type, options: ObservableOptions) { let callbacksToNotify = callbacks.filter { $0.options.contains(options) } callbacksToNotify.forEach { $0.closure(value, options) } }

到这里我们自己封装的观察者已经完成,简单使用一下
1、声明User和Observer类
public class User { public let name: Observable public init(name: String) { self.name = Observable(name) } }public class Observer {}

2、简单使用
let myUser = User(name: "Jack")// 可观察对象 var observer: Observer? = Observer() // 观察者,观察者可以是NSObject的任意实例或者任意类 // 对name进行观察 myUser.name.addObserver(observer!, options: [.initial, .new]) { (name, change) in print("user is name is: \(name)") } // 设置value来更新其值 myUser.name.value = "https://www.it610.com/article/hello jack" // 去除观察者,那么后面其值再次改变将不受影响,因为内部的实现告诉我们其相关回调已经被去除 observer = nil // 监听无效 myUser.name.value = "https://www.it610.com/article/poor jack"



参考:
Key Value Observation in iOS 11
Smart KeyPaths: Better Key-Value Coding for Swift
What's new in swift4







    推荐阅读