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关键字,主要是告诉观察者在值发生改变之后触发闭包,如果没有该关键字,那么无法观察到值的改变

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)
    }
}


Swift4 - KVC与KVO_第1张图片


自定义观察者

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




你可能感兴趣的:(Swift,OC,设计模式)