什麼是KVO
- KVO是Key-Value Observing的首字母縮寫
- KVO是Object-C對觀察者設計模式的實現
- Apple使用了isa混寫(isa-swizzling)來實現KVO
KVO 提供一種機制,指定一個被觀察對象(例如 A 類),當對象某個屬性(例如 A 中的字符串 name)發生更改時,對象會獲得通知,並作出相應處理;【且不需要給被觀察的對象添加任何額外代碼,就能使用 KVO 機制】
用一張圖來描述一下KVO的實現機制
上圖可以看出,註冊一個對象的觀察者的時候,實際上是調用了系統的
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
這個方法,調用這個方法後觀察者觀察對象A中的某個屬性,然後系統會在運行時動態的創建一個NSKVONotifying_A的這麼樣一個類,原來的對象A的isa指針重新指向了NSKVONotifying_A這個類,把isa的指向進行修改就是isa混寫技術.NSKVONotifying_A是類A的子類,並重寫了其中的Setter方法,通過對Setter方法的重寫達到可以通知所有觀察者的目的.接下來,在XCode工程當中,來實際通過Setter方法的設置,KVO的監聽來感受一下KVO的實現.
創建兩個文件,MyObject和MyObserver.
- MyObject
@interface MyObject : NSObject
@property (nonatomic,assign) int value;
-(void)increase;
@end
@implementation MyObject
-(instancetype)init
{
self = [super init];
if (self) {
_value = 0;
}
return self;
}
-(void)increase
{
_value += 1;
}
@end
- MyObserver
@implementation MyObserver
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSNumber *valueNum = [change valueForKey:NSKeyValueChangeNewKey];
NSLog(@"value is %@",valueNum);
}
@end
然後在AppDelegate中進行KVO的監聽
- AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
MyObject *obj = [[MyObject alloc]init];
MyObserver *observer = [[MyObserver alloc]init];
//調用KVO方法監聽obj的value屬性的變化
[obj addObserver:observer forKeyPath:@"value" options:NSKeyValueObservingOptionNew context:NULL];
obj.value = 1;
return YES;
}
可以看到控制檯打印出了結果
說明監聽成功了.
在obj.value那裏打個斷點,看看MyObject是怎麼被改寫的.
不出所料,在監聽的屬性Value被改寫後,MyObject變成了NSKVONotifying_MyObject了.
爲什麼調用Setter方法就可以實現這種KVO的監聽呢.
重寫的Setter添加的方法
- -(void)willChangeValueForKey:(NSString *)key
- -(void)didChangeValueForKey:(NSString *)key
那麼在NSKVONotifying_MyObject中的Setter方法就變成了下面這樣
-(void)setValue:(id)obj
{
[self willChangeValueForKey:@"keyPath"];
//調用父類,也就是原類的實現
[super setValue:obj];
[self didChangeValueForKey:@"keyPath"];
}
接下來有兩個問題.
1.通過KVC設置Value能否生效
這個問題用代碼來驗證一下就可以了,如下所示
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
MyObject *obj = [[MyObject alloc]init];
MyObserver *observer = [[MyObserver alloc]init];
//調用KVO方法監聽obj的value屬性的變化
[obj addObserver:observer forKeyPath:@"value" options:NSKeyValueObservingOptionNew context:NULL];
obj.value = 1;
// 使用kvc來改變value的值
[obj setValue:@2 forKey:@"value"];
return YES;
}
結果控制檯打印如下:
說明使用KVC設置屬性的方式是可以出發KVO的,說明KVC設置屬性是觸發了Setter方法
2.使用成員變量賦值會出發KVO嗎
我們在AppDelegate調用obj的increase方法,發現控制檯只打印了value is 1,說明對成員變量賦值不會觸發KVO,但對increate方法進行以下操作就不一樣了.
不過如果我們把increase方法變成下面這樣,再運行試試
-(void)increase
{
[self willChangeValueForKey:@"value"];
_value += 1;
[self didChangeValueForKey:@"value"];
}
發現又觸發了KVO
所以根據上面的實驗總結出下面幾點
- 使用setter方法改變值KVO纔會生效
- 使用setValue:forKey:改變KVO纔會生效
- 成員變量直接修改需手動添加KVO纔會生效
KVC
KVC是Key-Value coding的縮寫,也就是鍵值編碼,和鍵值編碼相關的兩個方法就是下面這兩個
- -(id)valueForKey:(NSString *)key
這個可以調用某個實例的ValueForKey:方法,來獲取和key同名或相似名稱的實例變量的值 - -(void)setValue forKey:(NSString *)key
根據這個方法可以設置某一個對象和這個key同名或者相似名稱的實例變量的值.