KVO/KVC

什麼是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同名或者相似名稱的實例變量的值.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章