iOS KVO

KVO的能力

KVO全稱 key value observing,用於監聽對象屬性的改變,能夠監聽多個屬性。git

使用方法

只須要複寫-addObserver:forKeyPath:options:context方法便可,若是監聽多個屬性,須要在方法中經過keyPath來判斷修改的是哪個屬性。在更復雜的業務場景下,使用 context 上下文以及其它輔助手段纔可以幫助咱們更加精準地肯定被觀測的對象。尤爲是處理那些繼承自同一個父類的子類,而且這些子類有相同的 keypath。github

// Foo.h
@interface Foo : NSObject

@property (nonatomic, copy) NSString *bar;

@end
複製代碼
// Foo.m
#import "Foo.h"
#import <objc/runtime.h>

@implementation Foo

- (void)setBar:(NSString *)bar
{
    NSLog(@"self->isa = %@", object_getClass(self));
    _bar = bar;
}

複製代碼
ViewController.m
#import "ViewController.h"
#import "Foo.h"
#import <objc/runtime.h>

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    Foo *foo = [[Foo alloc] init];
    NSLog(@"self->isa = %@", object_getClass(foo));
    [foo addObserver:self
          forKeyPath:@"bar"
             options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
             context:nil];
    NSLog(@"self->isa = %@", object_getClass(foo));
    NSLog(@"self->isa->superClass = %@", class_getSuperclass(object_getClass(foo)));
    foo.bar = @"祈求者Kael";
}


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"bar"]) {
        NSLog(@"change = %@", change[NSKeyValueChangeOldKey]);
        NSLog(@"change = %@", change[NSKeyValueChangeNewKey]);
    }
}

@end

複製代碼
2019-05-24 15:49:19.916170+0800 NSCodingDemo[1614:88021] self->isa = Foo
2019-05-24 15:49:19.916548+0800 NSCodingDemo[1614:88021] self->isa = NSKVONotifying_Foo
2019-05-24 15:49:19.916548+0800 NSCodingDemo[1614:88021] self->isa->superClass = Foo
2019-05-24 15:49:19.916667+0800 NSCodingDemo[1614:88021] self->isa = NSKVONotifying_Foo
2019-05-24 15:49:19.916799+0800 NSCodingDemo[1614:88021] change = <null>
2019-05-24 15:49:19.916898+0800 NSCodingDemo[1614:88021] change = 祈求者Kael
複製代碼

實現原理

衆所周知,KVO是經過iOS runtime的isa-swizzle來實現的。面試

從上面代碼的打印結果能夠看出,一旦使用 addObserver:forKeyPath:options:context:給實例foo添加觀察者以後,系統會建立一個新類NSKVONotifying_Foo,而且NSKVONotifying_Foo是繼承自Foo。而後把實例foo的isa指針從 Foo 變成了 NSKVONotifying_Foo,這樣這個本來的Foo類實際就變成了NSKVONotifying_Foo( 由於isa 指針告訴 Runtime 系統這個對象的類是什麼),最後,蘋果爸爸還重寫NSKVONotifying_Foo的class方法,使它返回父類Foo,形成這個類仍是Foo的錯覺,從而使上述一系列的騷操做不被察覺。app

系統重寫了NSKVONotifying_Foo類的setter方法,在調用父類的setter方法先後,插入了willChangeValueForKey:didChangeValueForKey:方法。ui

正式因爲上述,KVO 行爲是同步的,而且發生與所觀察的值發生變化的一樣的線程上。atom

手動觸發KVO

手動調用willChangeValueForKey:didChangeValueForKey:方法,便可在不改變屬性值的狀況下手動觸發KVO,willChangeValueForKey:用於記錄舊值,didChangeValueForKey用於記錄新值。
而且這兩個方法缺一不可。
例如,對於屬性bar,只有當新值與舊值不一樣時,才觸發觀察的代碼邏輯。spa

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
    if ([key isEqualToString:@"bar"]) {
        return NO;
    }
    return YES;
}

- (void)setBar:(NSString *)bar
{
    if ([_bar isEqualToString:bar]) {
        return;
    }
    [self willChangeValueForKey:@"bar"];
    _bar = bar;
    [self didChangeValueForKey:@"bar"];
}
複製代碼

參考

相關文章
相關標籤/搜索