iOS下KVO的使用以及一些实现细节

      KVO的是Key Value Observe的缩写,中文是键值观察。这是一个典型的观察者模式,观察者在键值改变时会得到通知。iOS中有个Notification的机制,也可以获得通知,但这个机制需要有个Center,相比之下KVO更加简洁而直接。
      KVO的使用也很简单,就是简单的3步。
      1.注册需要观察的对象的属性addObserver:forKeyPath:options:context:
      2.实现observeValueForKeyPath:ofObject:change:context:方法,这个方法当观察的属性变化时会自动调用
      3.取消注册观察removeObserver:forKeyPath:context:

      不多说了,上代码:
@interface myPerson : NSObject
{
    NSString *_name;
    int      _age;
    int      _height;
    int      _weight;
}
@end

@interface testViewController : UIViewController
@property (nonatomic, retain) myPerson *testPerson;

- (IBAction)onBtnTest:(id)sender;
@end

- (void)testKVO
{
    testPerson = [[myPerson alloc] init];
    
    [testPerson addObserver:self forKeyPath:@"height" options:NSKeyValueObservingOptionNew context:nil];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"height"]) {
        NSLog(@"Height is changed! new=%@", [change valueForKey:NSKeyValueChangeNewKey]);
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

- (IBAction)onBtnTest:(id)sender {
    int h = [[testPerson valueForKey:@"height"] intValue];    
    [testPerson setValue:[NSNumber numberWithInt:h+1] forKey:@"height"];
    NSLog(@"person height=%@", [testPerson valueForKey:@"height"]);
}

- (void)dealloc
{
    [testPerson removeObserver:self forKeyPath:@"height" context:nil];
    [super dealloc];
}
      第一段代码声明了myPerson类,里面有个_height的属性。在testViewController有一个testPerson的对象指针。
      在testKVO这个方法里面,我们注册了testPerson这个对象height属性的观察,这样当testPerson的height属性变化时,会得到通知。在这个方法中还通过NSKeyValueObservingOptionNew这个参数要求把新值在dictionary中传递过来。
      重写了observeValueForKeyPath:ofObject:change:context:方法,这个方法里的change这个NSDictionary对象包含了相应的值。
      需要强调的是KVO的回调要被调用,属性必须是通过KVC的方法来修改的,如果是调用类的其他方法来修改属性,这个观察者是不会得到通知的。

       因为Cocoa是严格遵循MVC模式的,所以KVO在观察Modal的数据变化时很有用。那么KVO是怎么实现的呢,苹果官方文档上说的比较简单:“Automatic key-value observing is implemented using a technique called isa-swizzling.”
       “When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance.”
        就是说在运行时会生成一个派生类,在这个派生类中重写基类中任何被观察属性的 setter 方法,用来欺骗系统顶替原先的类。

        继续观察一下代码:
@interface myPerson : NSObject
{
    NSString *_name;
}

@property (nonatomic)int height;
@property (nonatomic)int weight;
@property (nonatomic)int age;
@end

@implementation myPerson
@synthesize height, weight, age;
@end

#import "objc/runtime.h"
static NSArray * ClassMethodNames(Class c)
{
    NSMutableArray * array = [NSMutableArray array];
    
    unsigned int methodCount = 0;
    Method * methodList = class_copyMethodList(c, &methodCount);
    unsigned int i;
    for(i = 0; i < methodCount; i++) {
        [array addObject: NSStringFromSelector(method_getName(methodList[i]))];
    }
    
    free(methodList);
    
    return array;
}

static void PrintDescription(NSString * name, id obj)
{
    NSString * str = [NSString stringWithFormat:
                      @"\n\t%@: %@\n\tNSObject class %s\n\tlibobjc class %s\n\timplements methods <%@>",
                      name,
                      obj,
                      class_getName([obj class]),
                      class_getName(obj->isa),
                      [ClassMethodNames(obj->isa) componentsJoinedByString:@", "]];
    NSLog(@"%@", str);
}

- (void)testKVOImplementation
{
    myPerson * anything = [[myPerson alloc] init];
    myPerson * hObserver = [[myPerson alloc] init];
    myPerson * wObserver = [[myPerson alloc] init];
    myPerson * hwObserver = [[myPerson alloc] init];
    myPerson * normal = [[myPerson alloc] init];
    
    [hObserver addObserver:anything forKeyPath:@"height" options:0 context:NULL];
    [wObserver addObserver:anything forKeyPath:@"weight" options:0 context:NULL];
    
    [hwObserver addObserver:anything forKeyPath:@"height" options:0 context:NULL];
    [hwObserver addObserver:anything forKeyPath:@"weight" options:0 context:NULL];
    
    PrintDescription(@"normal", normal);
    PrintDescription(@"hObserver", hObserver);
    PrintDescription(@"wObserver", wObserver);
    PrintDescription(@"hwOBserver", hwObserver);
    
    NSLog(@"\n\tUsing NSObject methods, normal setHeight: is %p, overridden setHeight: is %p\n",
          [normal methodForSelector:@selector(setHeight:)],
          [hObserver methodForSelector:@selector(setHeight:)]);
    NSLog(@"\n\tUsing libobjc functions, normal setHeight: is %p, overridden setHeight: is %p\n",
          method_getImplementation(class_getInstanceMethod(object_getClass(normal),
                                                           @selector(setHeight:))),
          method_getImplementation(class_getInstanceMethod(object_getClass(hObserver),
                                                           @selector(setHeight:))));
}
        略微改写了一下myPerson,age/height/weight两个属性增加了getter/setter方法,然后运用runtime的方法,打印相应的内容,运行的log如下:
2013-11-02 20:36:22.391 test[2438:c07] 
normal: <myPerson: 0x886b840>
NSObject class myPerson
libobjc class myPerson
implements methods <weight, setWeight:, age, setAge:, height, setHeight:>
2013-11-02 20:36:22.393 test[2438:c07] 
hObserver: <myPerson: 0x886b7e0>
NSObject class myPerson
libobjc class NSKVONotifying_myPerson
implements methods <setWeight:, setHeight:, class, dealloc, _isKVOA>
2013-11-02 20:36:22.393 test[2438:c07] 
wObserver: <myPerson: 0x886b800>
NSObject class myPerson
libobjc class NSKVONotifying_myPerson
implements methods <setWeight:, setHeight:, class, dealloc, _isKVOA>
2013-11-02 20:36:22.393 test[2438:c07] 
hwOBserver: <myPerson: 0x886b820>
NSObject class myPerson
libobjc class NSKVONotifying_myPerson
implements methods <setWeight:, setHeight:, class, dealloc, _isKVOA>
2013-11-02 20:36:22.394 test[2438:c07] 
Using NSObject methods, normal setHeight: is 0x37e0, overridden setHeight: is 0x37e0
2013-11-02 20:36:22.394 test[2438:c07] 
Using libobjc functions, normal setHeight: is 0x37e0, overridden setHeight: is 0xb859e0
  
        从log信息可以清楚的看到派生了一个NSKVONotifying_XXX的类,这个派生类集合了每个KVO观察者的信息,所以这个派生类可以全局公用。
        另外,观察原来类的方法和派生类的方法,每个被观察的属性都重写了,比如:setWeight:方法和setHeight:方法,没被观察的属性都没有重新生成,比如:height:方法、weight:方法、age:方法和setAge:方法。
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页