KVO的全稱是key-value Observng,也叫作「鍵值監聽」,一般用來監聽某個對象的某個屬性值的變化。下面使用一個簡單的例子來回顧一下KVO的用法。面試
@interface XLPerson : NSObject
@property(nonatomic, copy)NSString *name;
@property(nonatomic, assign)int age;
@end
複製代碼
@implementation ViewController
- (void)viewDidLoad{
[super viewDidLoad];
self.person = [[XLPerson alloc] init];
self.person.age = 10;
[self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"\n keyPath:%@, \n object:%@, \n change:%@", keyPath, object, change);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.person.age = 20;
}
@end
複製代碼
2019-11-13 14:52:49.960452+0800 TestFont[52476:1429894]
keyPath:age,
object:<XLPerson: 0x6000039bec00>,
change:{
kind = 1;
new = 20;
old = 10;
}
複製代碼
結合以前對NSObject底層的學習咱們知道,實例對象的isa指針指向它的類對象,那麼上文例子中的person對象的isa指針應該指向它的類對象XLPerson,爲了作對比,咱們增長一個person2對象:bash
- (void)viewDidLoad{
[super viewDidLoad];
self.person = [[XLPerson alloc] init];
self.person.age = 10;
self.person2 = [[XLPerson alloc] init];
self.person2.age = 30;
[self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"\n keyPath:%@, \n object:%@, \n change:%@", keyPath, object, change);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.person.age = 20;
}
@end
複製代碼
在touchesBegan方法中添加斷點,而後咱們使用LLDB命令來對代碼進行調試框架
(lldb) p self.person->isa
(Class) $1 = NSKVONotifying_XLPerson
(lldb) p self.person2->isa
(Class) $2 = XLPerson
(lldb)
複製代碼
這時候會發現添加了Observer後的person對象的isa指針不是指向XLPerson,而是指向一個新的類對象NSKVONotifying_XLPerson,而person2對象因爲沒有添加Observer,因此它的isa指針指向的是類對象XLPerson。函數
因爲咱們並無建立過NSKVONotifying_XLPerson類,因此NSKVONotifying_XLPerson是在運行時動態生成的一個新的類,新類生成以後,又將person的isa指針指向了新的類對象。學習
爲了瞭解NSKVONotifying_XLPerson的內部構造,咱們自定義一個方法來打印Class的方法列表和superClass測試
- (void)descriptionOfClass:(Class)cls{
NSLog(@"------------ %@ -----------", NSStringFromClass(cls));
NSLog(@"%@ superClass ----> %@", NSStringFromClass(cls),NSStringFromClass(class_getSuperclass(cls)));
unsigned int count;
Method *methondList = class_copyMethodList(cls, &count);
for (int i = 0; i < count; i++) {
Method method = methondList[i];
NSString *methodName = NSStringFromSelector(method_getName(method));
NSLog(@"%@ ----> %@", NSStringFromClass(cls), methodName);
}
free(methondList);
}
複製代碼
修改示例中的代碼,打印出person和person1的方法列表和superClassui
- (void)viewDidLoad{
[super viewDidLoad];
self.person = [[XLPerson alloc] init];
self.person.age = 10;
self.person2 = [[XLPerson alloc] init];
self.person2.age = 20;
[self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
[self descriptionOfClass:object_getClass(self.person)];
NSLog(@"\n");
[self descriptionOfClass:object_getClass(self.person2)];
}
複製代碼
注意:因爲對象的實例方法都存放在類對象的methonList中,因此此處咱們須要經過object_getClass方法拿到person和person1對象的類對象,而後經過遍歷類對象的方法列表打印出具體的方法名稱。 object_getClass方法若是傳遞過去一個示例對象,那麼會返回對應的類對象,若是傳遞過去一個類對象,會返回對應的元類對象。編碼
運行程序,獲得如下運行結果atom
從圖中能夠看出,person對象因爲加了KVO監聽,因此它的類對象變成了NSKVONotifying_XLPerson,而NSKVONotifying_XLPerson對象的superClass是XLPerson,說明NSKVONotifying_XLPerson是XLPerson的子類。spa
在NSKVONotifying_XLPerson方法列表中主要有4個方法,setAge:、class、dealloc和_isKVOA,下面咱們就來一一分析這四個方法。
- (void)setAge:(int)age{
//調用Foundationf框架中的_NSSetIntValueAndNotify方法
[self _NSSetIntValueAndNotify];
}
- (void)_NSSetIntValueAndNotify{
//將要修改age的值
[self willChangeValueForKey:@"age"];
//調用父類的setAge方法去修改age的值
[super setAge:age];
//完成修改age的值,而且執行observeValueForKeyPath方法
[self didChangeValueForKey:@"age"];
}
複製代碼
- (Class)class{
return [XLPerson class];
}
複製代碼
上文介紹了NSKVONotifying_XLPerson對象中的幾個主要的方法,如今咱們就來還原一下NSKVONotifying_XLPerson對象完整的內部結構。
首先,NSKVONotifying_XLPerson是Class類型的對象,因此它內部確定擁有isa指針和superClass指針,由此能夠獲得NSKVONotifying_XLPerson的結構以下:
結合isa指針的指向能夠獲得如下結構:
由此也能夠獲得NSKVONotifying_XLPerson的僞代碼以下
@interface NSKVONotifying_XLPerson : XLPerson
@end
@implementation NSKVONotifying_XLPerson
- (void)setAge:(int)age{
//調用Foundationf框架中的_NSSetIntValueAndNotify方法
[self _NSSetIntValueAndNotify];
}
- (void)_NSSetIntValueAndNotify{
//將要修改age的值
[self willChangeValueForKey:@"age"];
//調用父類的setAge方法去修改age的值
[super setAge:age];
//完成修改age的值,而且執行observeValueForKeyPath方法
[self didChangeValueForKey:@"age"];
}
- (void)didChangeValueForKey:(NSString *)key{
//觸發observeValueForKeyPath方法
[self observeValueForKeyPath:@"age" ofObject:self change:nil context:nil];
}
- (void)dealloc{
//釋放操做
}
- (Class)class{
return [XLPerson class];
}
- (BOOL)_isKVOA{
return YES;
}
@end
複製代碼
KVC,俗稱「鍵值編碼」,全稱是「Key Value Coding」,它是一種能夠直接經過字符串的名稱(Key)來訪問類屬性的機制,而不是經過調用Setter或者Getter方法來進行訪問。
KVC的經常使用方法以下
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (nullable id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
- (nullable id)valueForKeyPath:(NSString *)keyPath;
複製代碼
KVC有兩種賦值和取值方法,下面咱們經過一個簡單的例子來了解一下。首先建立XLPerson類和XLStudent類
@interface XLStudent : NSObject
@property(nonatomic, assign)int num;
@end
@interface XLPerson : NSObject
@property(nonatomic, strong)XLStudent *student;
@property(nonatomic, copy)NSString *name;
@property(nonatomic, assign)int age;
@end
複製代碼
而後經過KVC來設置XLPerson和XLStudent的屬性的值,以下
- (void)viewDidLoad{
[super viewDidLoad];
XLPerson *person = [[XLPerson alloc] init];
[person setValue:[[XLStudent alloc] init] forKey:@"student"];
[person setValue:@10 forKey:@"age"];
[person setValue:@"張三" forKey:@"name"];
[person setValue:@20 forKeyPath:@"student.num"];
NSNumber *age = [person valueForKey:@"age"];
NSString *name = [person valueForKey:@"name"];
NSNumber *num = [person valueForKeyPath:@"student.num"];
NSLog(@"%@, %@, %@",age,name,num);
}
複製代碼
最後獲得結果age=10、name=張三、num=20,因而可知,經過KVC確實能夠修改對象中的屬性。
使用KVC除了能夠修改屬性,也能夠修改爲員變量的值,在XLPerson中增長以下成員變量
@interface XLPerson : NSObject{
int _height;
int _weight;
}
@end
複製代碼
而後使用KVC進行賦值
XLPerson *person = [[XLPerson alloc] init];
[person setValue:@30 forKey:@"_height"];
[person setValue:@40 forKeyPath:@"_weight"];
NSLog(@"%@, %@",person->_height,person->_weight);
複製代碼
最後能夠發現KVC確實也能修改爲員變量的值。
同時,經過上面的代碼咱們能夠看出兩種賦值和取值方法的區別。
[[person valueForKey:@"student"] valueForKey:@"num"];
複製代碼
其實經過setValue:forkey方法給對象的屬性賦值,主要通過如下幾個流程
對應流程圖以下:
經過valueForKey:方法取值,流程以下:
流程圖以下:
經過對KVO的探索,咱們知道,給對象的某個屬性添加KVO監聽,實際上是動態建立了一個此類的子類,而後將對象的isa指針指向新生成的類,最後經過重寫屬性的setter方法來添加監聽。那麼若是使用KVO來對屬性或者成員變量進行賦值,會觸發KVO監聽嗎?咱們經過一個簡單的例子來測試一下
仍是使用上文的XLPerson對象
- (void)viewDidLoad{
[super viewDidLoad];
self.person = [[XLPerson alloc] init];
[self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
[self.person addObserver:self forKeyPath:@"_height" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"\n keyPath:%@, \n object:%@, \n change:%@", keyPath, object, change);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self.person setValue:@10 forKey:@"age"];
[self.person setValue:@20 forKeyPath:@"_height"];
}
複製代碼
運行代碼,點擊屏幕能夠看到以下打印信息
經過KVC不論是設置屬性的值仍是成員變量的值,都會觸發KVO監聽,說明在KVC內部確實會在給屬性或成員變量賦值的時候,會經過相似調用didChangeValueForKey方法來觸發KVO監聽。
在修改變量先後手動調用willChangeValueForKey:和didChangeValueForKey:方法
[self willChangeValueForKey:name];
_name = @"xxx";
[self didChangeValueForKey:name];
複製代碼
直接修改爲員變量的值不會觸發KVO,由於沒有觸發setter方法。
會觸發KVO。
參考上文流程圖
參考上文流程圖
以上內容純屬我的理解,若是有什麼不對的地方歡迎留言指正。
一塊兒學習,一塊兒進步~~~