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
@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关键字,主要是告诉观察者在值发生改变之后触发闭包,如果没有该关键字,那么无法观察到值的改变
3)使用新语法特性\.string监听属性变化
4)最开心的事就是我们不在需要手动去除观察者,以前都需要在deinit()中去除观察者
当然,我们也可以为属性添加@objc,那么类就不在需要@objcMembers关键字
class Child: NSObject {
let name: String
// KVO-enabled properties must be @objc dynamic
@objc dynamic var age: Int
init(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 = 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)
}
}
自定义观察者
1、声明一个泛型的观察者类,并拥有一个嵌套回调类
//1、 自己封装观察者
public class Observable {
//2、 内嵌回调类
fileprivate class Callback {
fileprivate weak var observer: AnyObject?
fileprivate let options: [ObservableOptions]
fileprivate let closure: (Type, ObservableOptions) -> Void
init(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 = rawValue
}
}
可以看到非常简单,遵守OptionSet协议,并拥有initial、old、new3个值,跟系统本身KVO中的NSKeyValueObservableOptions有点类似。
3、在Observable中声明泛型的value属性
// 无论什么时候值发生改变,发送通知
public var value: Type {
didSet {
// notification
}
}
public init(_ value: Type) {
self.value = 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 = "hello jack"
// 去除观察者,那么后面其值再次改变将不受影响,因为内部的实现告诉我们其相关回调已经被去除
observer = nil
// 监听无效
myUser.name.value = "poor jack"
参考:
Key Value Observation in iOS 11
Smart KeyPaths: Better Key-Value Coding for Swift
What's new in swift4