Swift是没有KVO模式的
在iOS开发中,运用oc的runtime的动态分发机制,通过key来监听value的值,达到实现KVO监听的效果。然而在Swift中是没有KVO模式的(换句话说是不能直接使用KVO模式),使用的条件必须是继承自NSObject,属性前加上dynamic来开启运行时,或者是在class前面加@objcMembers,所有的属性都会开启runtime,允许监听属性的变化。
class Car: NSObject {
dynamic var carName: String
init(carName: String) {
self.carName = carName;
}
}
// ************或者加@objcMembers
@objcMembers
class Car: NSObject {
var carName: String
init(carName: String) {
self.carName = carName;
}
}
复制代码
然后你就可以愉快的像使用oc一样来玩耍KVO了,具体实现过程就不演示了,毕竟你们都是老司机了。
KVO的实现原理
基于oc的runtime的动态分发机制,属性对象被监听时,会在运行时创建一个派生类,并重写了被观察属性keyPath的setter方法,setter方法随后负责通知观察对象属性的改变状况。
OC中的KVO及其KVO的基础知识可参见: 深入runtime探究KVO
Swift中属性观察器
Swift有两个属性观察者willSet和didSet,类似于触发器。用来监视属性的除初始化之外的属性值变化,当属性值发生改变时可以对此作出响应。
有如下特点:
- 不仅可以在属性值改变后触发didSet,也可以在属性值改变前触发willSet
- 给属性添加观察者必须要声明清楚属性类型,否则编译器报错
- willSet可以带一个newName的参数,没有的话,该参数默认命名为newValue
- didSet可以带一个oldName的参数,表示旧的属性,不带的话默认命名为oldValue
- 属性初始化时,willSet和didSet不会调用。只有在初始化上下文之外,当设置属性值时才会调用
- 即使是设置的值和原来值相同,willSet和didSet也会被调用
var firstName: String = "Fist" {
willSet { //新值设置之前被调用,在此可以进行条件筛选,过滤数据
print("willSet的新值是\(newValue)")
}
didSet { //新值设置之后立即调用,在此可以进行条件筛选,过滤数据,可以直接绑定数据到UI上面
print("didSet的旧值是\(oldValue) --- 新值是 \(firstName)")
self.nameLabel.text = firstName
}
}
复制代码
用得更多的是在didSet中跟UI绑定,或者条件逻辑过滤。
Swift实现KVO值监听
利用设计模式中的观察者模式,观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。 观察者模式适用在一个被观察者(数据源)要通知多个观察者的场景。
Swift中实现KVO,还是用到了属性观察者didSet,直接贴代码:
public class Observable<Type> {
// MARK: - Callback
fileprivate class Callback {
fileprivate weak var observer: AnyObject?
fileprivate let options: [ObservableOptions]
fileprivate let closure: (Type, ObservableOptions) -> Void
fileprivate init(
observer: AnyObject,
options: [ObservableOptions],
closure: @escaping (Type, ObservableOptions) -> Void) {
self.observer = observer
self.options = options
self.closure = closure
}
}
// MARK: - Properties 利用Swift 的didSet 特性把值回调给callback
public var value: Type {
didSet {
removeNilObserverCallbacks()
notifyCallbacks(value: oldValue, option: .old)
notifyCallbacks(value: value, option: .new)
}
}
private func removeNilObserverCallbacks() {
callbacks = callbacks.filter { $0.observer != nil }
}
// MARK: 回调给callback 实现闭包回调
private func notifyCallbacks(value: Type, option: ObservableOptions) {
let callbacksToNotify = callbacks.filter { $0.options.contains(option) }
callbacksToNotify.forEach { $0.closure(value, option) }
}
// MARK: - Object Lifecycle
public init(_ value: Type) {
self.value = value
}
// MARK: - Managing Observers
private var callbacks: [Callback] = []
/// 添加观察者
///
/// - Parameters:
/// - observer: 观察者
/// - removeIfExists: 如果观察者存在需要移除
/// - options: 被观察者
/// - closure: 回调
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 }
}
}
// MARK: - ObservableOptions
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
}
}
复制代码
以上代码出自 设计模式(Swift) - 3.观察者模式、建造者模式 这一篇文章中,欲知详情请移步查看。
使用列子:
class ViewController: UIViewController {
// 用来管理观察者
public class Observer {}
var observer: Observer? // 当observer置为nil的时候,可观察对象会自动释放.
let user = User(name: "yihai", info: ["name" : "Lewis"],person: Person(name: "lewis", age: 18))
override func viewDidLoad() {
super.viewDidLoad()
observer = Observer()
user.name.addObserver(observer!, options: [.new]) { name, change in
print("name:\(name), change:\(change)")
}
user.info.addObserver(observer!, options: [.new]) { info, change in
print("info:\(info), change:\(change)")
}
user.person.addObserver(observer!, options: [.new]) { person, change in
print("person:\(person), change:\(change)")
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
user.name.value = "Amel"
user.info.value = ["info":"lewis info"]
user.person.value = Person(name: "swift", age: 19)
user.person.value.name = "我们不一样"
user.info.value["car"] = "宝马"
user.info.value.removeAll()
}
}
public class User {
// 被观察的属性需要是Observable类型
public let name: Observable<String>
public var info: Observable<Dictionary<String, Any>>
public var person: Observable<Person>
public init(name: String,info: Dictionary<String, Any>,person: Person) {
self.name = Observable(name)
self.info = Observable(info)
self.person = Observable(person)
}
}
public struct Person {
var name: String
var age: Int
public init(name: String, age: Int) {
self.name = name
self.age = age
}
}
}
复制代码
注意
- 可以监听任意类型的对象
- Dictionary的增删改支持,struct的改变某个内部属性值也可以
- 有可能当observer置不为nil的时候,可观察对象收不到监听回调
那么对于数组Array的增删改是否一样也能实现呢?这个问题留给大神们亲自求索,我太菜了,哈哈。
第三方框架实现值监听
更方便,更简洁,更优雅
/* // RxSwift 实现方式 需要导入如下几个框架
import RxSwift
import RxCocoa
import RxGesture
import NSObject_Rx
*/
let variable = Variable(Int())
let variableDisposeBag = DisposeBag()
variable.asObservable().subscribe { (number) in
print("\(String(describing: number.element))")
}.disposed(by: variableDisposeBag)
variable.value = 100
复制代码
本人很喜欢RxSwift框架,后续会爬一爬它的内部实现原理。
最后
写得不是很好,请担待并提出您的宝贵意见。 希望我的文章能成为你的盛宴,也渴望你的建议能成为我的大餐。 如有错误请留言指正,对文章感兴趣可以关注作者不定期更新。