KVCKVO
KVC
KVC意思是键值编码,是一种可以通过键名来访问对象属性的机制,也可以对属性进行赋值,包括私有属性,由于KVC的定义是对OC中的NSObject的扩展进行实现的,所以如果要使用KVC机制,那么这个类需要继承NSObject,同时属性需添加@objc关键字。如图:
class Person:NSObject{
@objc var name:String{
set{
_name = newValue
print("name被赋值了")
}
get{
print("name被访问了")
return _name
}
}
@objc var age:Int
init(age: Int) {
self.age = age
}
var _name = ""
}
在上面的示例中,name和age变量的用了@objc修饰,所以可以通过KVC机制对这两个变量进行读写,而_name没有@objc关键字修饰,则不能通过KVC机制进行读写。
使用KVC机制进行读写访问:
let p = Person(age: 20)
p.setValue("Ally", forKey: "name")//使用KVC进行赋值操作
let pName = p.value(forKey: "name") as! String//使用KVC进行访问操作
print(pName)
在使用KVC对计算属性(上面示例中的name属性)进行读写操作时,会分别调用属性的getter和setter方法。
执行结果如图:
补充:
计算属性:在Swift中,计算属性不直接存储值,而是提供一个 getter 和一个可选的 setter 来间接获取和设置其他属性的值。计算属性可以不需要初始化,因为计算属性并不会占用内存。
KVC机制和.语法的区别:
我们知道,对类的属性进行读写时,可以创建对象,然后通过.语法对属性进行读写操作,那么两者的区别是什么呢?
对于KVC机制,它是在运行时才会检查错误,比如访问某个不存在的属性安全性较低,而点语法在编译时会进行类型检查,同时.语法不能访问或修改私有成员变量,而KVC机制可以。
KVC赋值和取值的原理:
赋值:
- 优先查找set<Key>方法或_set<Key>的第一个访问器。找到则直接完成。
- 如果没有找到访问器,则会检查类方法
accessInstanceVariablesDirectly
是否为YES,如果是YES则会查找
名称为_< key >、_is< key >、< key >或is< key >的实例变量,。如果找到,则直接完成。 - 如果没有访问器或实例变量时,调用
setValue:forUndefinedKey:
。这将在默认情况下引发异常。
取值:
首先查找get<Key>、<Key>、is<Key>或_<Key>的方法,找到则直接调用。
如果都没找到,KVC则会查找countOf<Key>,objectIn<Key>AtIndex或<Key>AtIndexes格式的方法。如果countOf<Key>方法和另外两个方法中的一个被找到,那么就会返回一个可以响应NSArray所有方法的代理集合(它是NSKeyValueArray,是NSArray的子类),调用这个代理集合的方法,或者说给这个代理集合发送属于NSArray的方法,就会以countOf<Key>,objectIn<Key>AtIndex或<Key>AtIndexes这几个方法组合的形式调用。还有一个可选的get<Key>:range:方法。所以你想重新定义KVC的一些功能,你可以添加这些方法,需要注意的是你的方法名要符合KVC的标准命名方法,包括方法签名。
如果上面的方法没有找到,那么会同时查找countOf<Key>,enumeratorOf<Key>,memberOf<Key>格式的方法。如果这三个方法都找到,那么就返回一个可以响应NSSet所的方法的代理集合,和上面一样,给这个代理集合发NSSet的消息,就会以countOf<Key>,enumeratorOf<Key>,memberOf<Key>组合的形式调用。
如果还没有找到,再检查类方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默认行为),那么和先前的设值一样,会按_<key>,_is<Key>,<key>,is<Key>的顺序搜索成员变量名。如果重写了类方法+ (BOOL)accessInstanceVariablesDirectly返回NO的话,那么会直接调用valueForUndefinedKey:
还没有找到的话,调用valueForUndefinedKey:
KVC在实际应用中可以字典转模型,如:
let p = Person(age: 20)
let dict = ["name":"BBB","age":30,"_name":"_name"] as [String : Any]
p.setValuesForKeys(dict)
print(p.name)
print(p.age)
print(p._name)
运行结果:
KVO
KVO意思是键值观察,是基于KVC以及动态派发技术实现的,它是观察者模式的一种衍生,其思想是:当被观察对象的某个属性发生变化时,另一个类可以通过观察获取通知,并做出处理。
KVO是对NSObject的扩展来实现的,当使用KVO时,需要继承NSObject,并且观察属性需要添加@objc dynamic标识,如:
class Person:NSObject{
@objc dynamic var name:String{
set{
_name = newValue
print("name被赋值了")
}
get{
print("name被访问了")
return _name
}
}
@objc var age:Int
init(age: Int) {
self.age = age
}
@objc var _name = ""
deinit {
removeObserver(self, forKeyPath: "name")
}
}
注意:observer对资源消耗大,建议在类销毁的时候移除观察者,如上示例中的析构deinit()中.
接着我们定义一个Model(B类),负责监听A类中的属性name,如:
class Model:NSObject{
let p2 = Person(age: 30)
override init(){
super.init()
p2.addObserver(self as! NSObject, forKeyPath: "name", options: .new, context: nil)
}
//响应回调事件的重写,需要继承NSObject
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
print(22)
if p2.name != ""{
print("p2`s name is changed,is \(p2.name)")
}
}
}
注意:你需要继承NSObject类,从而对响应回调事件进行重写。
接着我们创建实例,修改p2的name值:
let model = Model()
model.p2.name = "good"
当p2.name值被修改后,会调用响应事件,运行结果如下:
KVO在iOS实际开发中的举例:
import UIKit
import Foundation
class Persons:NSObject {
@objc dynamic var name: String?
init(name: String? = nil) {
self.name = name
}
}
class ViewController: UIViewController {
//实例化被观察对象
var person = Persons(name: "Aliy")
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .red
//添加观察者
self.person.addObserver(self, forKeyPath: "name", options: .new, context: nil)
//3s后执行被观察对象值的改变
self.perform(#selector(ChangeName), with: nil, afterDelay: 3.0)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if person.name != "" {
print("The new person is \(person.name!)")
self.view.backgroundColor = . green
}
}
}
extension ViewController {
@objc func ChangeName(){
person.name = "John"
}
}
这里注意:ViewController不需要再去继承NSObject类,因为它已经继承了UIViewController,而Swift 中的 UIViewController
继承自 UIResponder
,而 UIResponder
继承自 NSObject
类。