阅读 208

Key-Value Observing Programming Guide

Key-Value Observing Programming Guide

键值观察是一种机制, 它允许将其他对象的指定属性的更改通知给对象

重要 理解键值观察前需要明白键值编码

Registering for Key-Value Observing

必须执行以下步骤以使对象能够接收遵从KVO的属性的键值观察通知

  • 使用方法addObserver:forKeyPath:options:context:注册一个观察者对象
  • 实现方法observeValueForKeyPath:ofObject:change:context:在观察者内部接收更改通知消息
  • 当观察者不再接收属性改变消息时, 使用方法removeObserver:forKeyPath:移除观察者, 注意要在观察者内存释放前, 移除观察者

重要 并非所有类的所有属性都符合KVO可以按照KVO Compliance中所述的步骤确保自己的类符合KVO

Registering as an Observer

观察对象首先通过发送addObserver:forKeyPath:options:context:消息向观察对象注册自己, 将其自身作为观察者和要观察的属性的key path传递. 观察者还指定了一个options参数和一个上下文指针(a context pointer)来管理通知的各个方面.

Options

options参数(可按位或的常量)既会影响通知中提供的change字典的内容, 又会影响生成通知的方式.

可以通过指定NSKeyValueObservingOptionOld参数接收观察到的属性更改之前的值, 使用NSKeyValueObservingOptionNew接收属性更改之后的值, 如果旧值新值都要获取, 则使用按位或NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew

可以使用NSKeyValueObservingOptionInitial指示观察到的对象发送立即更改通知(在addObserver:forKeyPath:options:context:方法返回之前). 可以在观察者中可以使用这种一次性通知确定观察属性的初始化

可以通过NSKeyValueObservingOptionPrior参数, 在属性更改之前接收通知. change字典通过包含键NSKeyValueChangeNotificationIsPriorKey和NSNumber包装的YES值来表示更改前的通知.

当通知需要进行willChange…之类的监听的时候可以接收预更改通知

Context

addObserver:forKeyPath:options:context:方法中的上下文指针包含了任意数据, 这些数据将在相应的更改通知中传递给观察者. 可以指定NULL并完全依靠key path字符串来确定更改通知的来源, 但是这种方法可能会导致对象的父类由于不同的原因也观察到相同的key path而导致问题

一种更安全更可扩展的方法是使用上下文确保收到的通知是发给观察者的而不是超类的

类中唯一命名的静态变量的地址构成了良好的上下文. 在超类或子类中以类似方式选择的上下文不太可能重复. 可以为整个类选择一个上下文, 然后依靠通知消息中的key path字符串来确定更改的内容. 另外可以为每个观察到的key path创建一个不同的上下文, 从而完全无需进行字符串比较从而可以更高效地进行通知解析

创建上下文指针

static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext;
复制代码

示例 一个Person实例使用给定的上下文指针将自己注册为Account实例的balanceinterestRate属性的观察者

- (void)registerAsObserverForAccount:(Account*)account {
    [account addObserver:self
              forKeyPath:@"balance"
                 options:(NSKeyValueObservingOptionNew |
                          NSKeyValueObservingOptionOld)
                 context:PersonAccountBalanceContext];
 
    [account addObserver:self
              forKeyPath:@"interestRate"
                 options:(NSKeyValueObservingOptionNew |
                          NSKeyValueObservingOptionOld)
                  context:PersonAccountInterestRateContext];
}
复制代码

注意 addObserver:forKeyPath:options:context:方法不维护对观测对象、被观测对象或上下文的强引用. 应确保根据需要对观察和观察对象和上下文保持强引用

Receiving Notification of a Change

当一个对象被观察的属性的值发生了改变, 则观察者对象会接收到observeValueForKeyPath:ofObject:change:context:消息, 所有的观察者必须实现observeValueForKeyPath:ofObject:change:context:方法

change字典中NSKeyValueChangeKindKey对应的值提供有关发生的更改类型的信息, 如果观测对象的值已更改, 会返回NSKeyValueChangeSetting键入的值

取决于注册观察者时指定的Option NSKeyValueChangeOldKey, NSKeyValueChangeNewKey包含更改之前和之后的属性的值. 如果属性是对象则直接提供该值. 如果属性是标量或C结构体, 则该值将包装在NSValue对象中

如果观察到的属性是多关系, NSKeyValueChangeKindKey, 关系中的对象进行插入、删除或替换则会对应返回NSKeyValueChangeInsertion,NSKeyValueChangeRemoval,NSKeyValueChangeReplacement

change字典中NSKeyValueChangeIndexesKey表示NSIndexSet对象指定索引关系的更改

当观察者被注册的时候, 制定了Option参数为NSKeyValueObservingOptionNew, NSKeyValueObservingOptionOld, 则change字典的NSKeyValueChangeOldKey, NSKeyValueChangeNewKey则包含相关对象的值更改之前和之后的数组

示例 实现observeValueForKeyPath:ofObject:change:context:

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
 
    if (context == PersonAccountBalanceContext) {
        // Do something with the balance…
 
    } else if (context == PersonAccountInterestRateContext) {
        // Do something with the interest rate…
 
    } else {
        // Any unrecognized context must belong to super
        [super observeValueForKeyPath:keyPath
                             ofObject:object
                               change:change
                               context:context];
    }
}
复制代码

如果在注册观察者时指定了NULL上下文则可以将通知的key path与正在观察的key path进行比较以确定更改的内容. 如果对所有观察到的key path使用单个上下文, 则首先针对通知的上下文测试该上下文并查找匹配项, 使用key path字符串比较来确定特定更改的内容. 如果为每个key path提供了唯一的上下文(如上面代码所示), 则一系列简单的指针比较会同时告诉通知是否针对此观察者. 如果是则告知更改了哪些key path

在所有的情况中, 当观察者没法识别上下文时, 应该调用父类的observeValueForKeyPath:ofObject:change:context:, 父类可能注册了相应的观察者

注意 如果通知传播到类层次结构的顶部, NSObject将抛出NSInternalInconsistencyException异常, 这是一个编程错误, 子类未能使用为其注册的通知

Removing an Object as an Observer

移除观察者示例

- (void)unregisterAsObserverForAccount:(Account*)account {
    [account removeObserver:self
                 forKeyPath:@"balance"
                    context:PersonAccountBalanceContext];
 
    [account removeObserver:self
                 forKeyPath:@"interestRate"
                    context:PersonAccountInterestRateContext];
}

复制代码

移除观察者后, 观察者对象将不再接收observeValueForKeyPath:ofObject:change:context:消息

移除观察者时请记住以下几点

  • 如果尚未注册为观察者, 移除观察者则会导致NSRangeException异常. addObserver:forKeyPath:options:context:的调用要与removeObserver:forKeyPath:context:方法的调用要一直, 如果做不到, 则使用try/catch处理
  • 观察者在销毁的时候不会自动将自己移除观察. 被观察者会接着继续发送通知, 并且会无视观察者的状态. 如果给一个已释放的对象发送通知则会造成内存访问异常, 所以确保在内存释放之前移除观察者对象
  • 该协议无法询问对象是观察者还是被观察, 构造自身的代码以避免产生错误. 典型的模式就是在初始化的时候(如init,viewDidLoad)添加观察者, 在要销毁(通常在dealloc)的时候移除观察者. 确保正确配对和有序添加和删除观察者, 并确保观察者在从内存中释放之前被移除

KVO Compliance

为了满足键值观察规则, 请确保满足以下内容

  • 类的属性满足键值编码规则
  • KVO支持与KVC相同的数据类型, 支持Objective-C对象, 标量, 结构体
  • 类发出属性的KVO更改通知
  • 相关的key能被正确注册

有两种技术可确保发出更改通知. 自动支持由NSObject提供, 默认情况下可用于类的所有属性. 通常如果遵循标准的Cocoa编码和命名约定则可以使用自动更改通知 — 无需编写任何其他代码

手动更改通知提供对何时发出通知的额外控制, 并且需要额外的编码. 通过类方法automaticallyNotifiesObserversForKey:可以控制子类属性的自动通知

Automatic Change Notification

NSObject提供了自动键值更改通知的基本实现. 自动键值更改通知观察者使用符合键值的访问器所做的更改, 及使用键值编码的方法. 自动通知也受集合代理对象的支持, 例如mutableArrayValueForKey:

导致发出 KVO 更改通知的方法调用示例

// Call the accessor method.
[account setName:@"Savings"];
 
// Use setValue:forKey:.
[account setValue:@"Savings" forKey:@"name"];
 
// Use a key path, where 'account' is a kvc-compliant property of 'document'.
[document setValue:@"Savings" forKeyPath:@"account.name"];
 
// Use mutableArrayValueForKey: to retrieve a relationship proxy object.
Transaction *newTransaction = <#Create a new transaction for the account#>;
NSMutableArray *transactions = [account mutableArrayValueForKey:@"transactions"];
[transactions addObject:newTransaction];
复制代码

Manual Change Notification

在某些情况下可能希望控制通知过程. 例如要尽量减少因应用程序特定原因而不需要的触发通知或将一些更改分组到单个通知中. 手动更改通知提供了执行此操作的方法

手动和自动通知不是相互排斥的. 除了已有自动通知外还可以自由发布手动通知. 更典型地是可能希望完全控制特定属性的通知. 这种情况下可以重写NSObject的方法automaticallyNotifiesObserversForKey:. 对于要不使用自动通知的属性, 子类automaticallyNotifiesObserversForKey:的实现中应该返回NO. 对于不认识(不需要观察的)的Key, 子类实现中应该调用父类的automaticallyNotifiesObserversForKey:

automaticallyNotifiesObserversForKey:示例

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
 
    BOOL automatic = NO;
    if ([theKey isEqualToString:@"balance"]) {
        automatic = NO;
    }
    else {
        automatic = [super automaticallyNotifiesObserversForKey:theKey];
    }
    return automatic;
}
复制代码

实现手动观察者通知, 在改变值之前调用willChangeValueForKey:, 值改变之后调用didChangeValueForKey:

实现手动通知的访问器方法示例

- (void)setBalance:(double)theBalance {
    [self willChangeValueForKey:@"balance"];
    _balance = theBalance;
    [self didChangeValueForKey:@"balance"];
}
复制代码

在提供通知之前测试更改值

- (void)setBalance:(double)theBalance {
    if (theBalance != _balance) {
        [self willChangeValueForKey:@"balance"];
        _balance = theBalance;
        [self didChangeValueForKey:@"balance"];
    }
}
复制代码

嵌套多个键的更改通知

- (void)setBalance:(double)theBalance {
    [self willChangeValueForKey:@"balance"];
    [self willChangeValueForKey:@"itemChanged"];
    _balance = theBalance;
    _itemChanged = _itemChanged+1;
    [self didChangeValueForKey:@"itemChanged"];
    [self didChangeValueForKey:@"balance"];
}
复制代码

在有序到对多关系的情况下, 不仅必须指定更改的键还必须指定更改的类型和所涉及的对象的索引.

尤其是NSKeyValueChange之类的更改类型. 如NSKeyValueChangeInsertion, NSKeyValueChangeRemoval, NSKeyValueChangeReplacement. 当对象为NSIndexSet对象传递时, 对象的索引会受影响

在对多关系中实现手动观察者通知

- (void)removeTransactionsAtIndexes:(NSIndexSet *)indexes {
    [self willChange:NSKeyValueChangeRemoval
        valuesAtIndexes:indexes forKey:@"transactions"];
 
    // Remove the transaction objects at the specified indexes.
 
    [self didChange:NSKeyValueChangeRemoval
        valuesAtIndexes:indexes forKey:@"transactions"];
}
复制代码

Registering Dependent Keys

在许多情况下一个属性的值取决于另一对象中一个或多个其他属性的值. 如果一个属性的值发生更改则派生属性的值也应标记为更改. 如何确保为这些从属属性发布键值观察通知取决于关系的基数

To-One Relationships

自动触发对一关系的通知, 需要重写keyPathsForValuesAffectingValueForKey:方法或者实现一个合适的方法, 该方法遵循它定义的用于注册依赖的键的模式

例如一个人的全名取决于名字和姓氏. 返回全名的方法可以编写如下

- (NSString *)fullName {
    return [NSString stringWithFormat:@"%@ %@",firstName, lastName];
}
复制代码

如果firstName, lastName发生更改了则必须通知观察fullName的观察者fullName属性发生了更改, 一个解决办法就是重写keyPathsForValuesAffectingValueForKey:, 表明一个PersonfullName属性依赖于firstNamelastName

keyPathsForValuesAffectingValueForKey:实现示例

+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
 
    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
 
    if ([key isEqualToString:@"fullName"]) {
        NSArray *affectingKeys = @[@"lastName", @"firstName"];
        keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
    }
    return keyPaths;
}
复制代码

还可以通过实现遵循命名约定keyPathsForValuesAffecting<Key>的类方法实现相同的结果. 其中 <Key>是依赖于值的属性的名称(第一个字母大写)

实现keyPathsForValuesAffecting<Key>命名约定的方法示例

+ (NSSet *)keyPathsForValuesAffectingFullName {
    return [NSSet setWithObjects:@"lastName", @"firstName", nil];
}
复制代码

给已有类的分类添加属性时, 不能进行keyPathsForValuesAffectingValueForKey:的重写, 因为不支持覆盖重写分类中的方法. 在这种情况下, 请实现匹配的keyPathsForValuesAffecting<Key>类方法以利用此机制

注意 通过实现keyPathsForValuesAffectingValueForKey:无法建立对多关系的依存关系. 相反必须观察对多集合中每个对象的适当属性, 并通过自己更新相关键来响应其值的更改. 下一节显示了应对这种情况的策略

To-Many Relationships

  1. keyPathsForValuesAffectingValueForKey:方法不支持对多关系的key-paths. 例如假设有一个Department部门对象,该Department对象与Employee雇员具有一对多关系雇员, 而Employee具有salary属性, 可能希望Department对象具有totalSalary属性, 该属性取决于关系中所有Employees的薪水. 不能使用keyPathsForValuesAffectingTotalSalary并将返回的employee.salary作为键来执行此操

有两种解决方案

可以使用键值观察将父级(在此示例中为Department)注册为所有子级(在此示例中为Employees)的相关属性的观察者. 注意添加和移除观察者的对应.

在方法observeValueForKeyPath:ofObject:change:context:更新相关值以响应更改

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    
    // 观察的keyPath应该为Employee的salary 
    // 子级属性更改更新父级属性totalSalary 手动触发KVO
    if (context == totalSalaryContext) { 
        [self updateTotalSalary];
    } else if (context == FatherTotalSalaryContext) {
        // deal with totalSalary change
    }
    else
    // deal with other observations and/or invoke super...
}
 
- (void)updateTotalSalary {
    [self setTotalSalary:[self valueForKeyPath:@"employees.@sum.salary"]];
}
 
- (void)setTotalSalary:(NSNumber *)newTotalSalary {
 
    if (totalSalary != newTotalSalary) {
        [self willChangeValueForKey:@"totalSalary"];
        _totalSalary = newTotalSalary;
        [self didChangeValueForKey:@"totalSalary"];
    }
}
 
- (NSNumber *)totalSalary {
    return _totalSalary;
}

复制代码
  1. 如果使用的是Core Data则可以在应用程序的通知中心将父级注册为其托管对象上下文的观察者。父级应以类似于观察键值的方式响应子级发布的相关变更通知

Key-Value Observing Implementation Details

使用称为isa-swizzling的技术实现自动键值观察. isa指向一个对象所属的类, 这个类维护了一张派发表(dispatch table), 派发表本质上包含指向该类实现方法的指针以及其他数据

当观察者注册为对象的属性时, 所观察对象的isa指针将被修改, 指向中间类而不是真实类. 因此isa指针的值不一定反映实例的实际类

绝不应依赖isa指针来确定类成员身份. 相反应使用类方法来确定对象实例的类

gnustep-base-1.25.0 KVO中的实现

gnustep-base现在查看代码

GSKVOPathInfo

/* An instance of thsi records the observations for a key path and the
 * recursion state of the process of sending notifications.
 */
 // 此实例记录关键路径的观测值和发送通知过程的递归状态
@interface	GSKVOPathInfo : NSObject {
@public
  unsigned              recursion;
  unsigned              allOptions;
  NSMutableArray        *observations;
  NSMutableDictionary   *change;
}
复制代码

GSKVOObservation

/* An instance of this records all the information for a single observation.
 */
 // 此实例记录单个观测值的所有信息
@interface	GSKVOObservation : NSObject {
@public
  NSObject      *observer;      // Not retained (zeroing weak pointer)
  void          *context;
  int           options;
}
@end
复制代码

GSKVOReplacement

/*
 * This holds information about a subclass replacing a class which is
 * being observed.
 */
 // 这包含有关子类的信息,该子类替换了被观察的类
@interface	GSKVOReplacement : NSObject
{
  Class         original;       /* The original class */
  Class         replacement;    /* The replacement class */
  NSMutableSet  *keys;          /* The observed setter keys */
}
复制代码

NSObject (NSKeyValueObserverRegistration)分类的 addObserver

- (void) addObserver: (NSObject*)anObserver
	  forKeyPath: (NSString*)aPath
	     options: (NSKeyValueObservingOptions)options
	     context: (void*)aContext {
  GSKVOInfo             *info;
  GSKVOReplacement      *r;
  NSKeyValueObservationForwarder *forwarder;
  NSRange               dot;

  setup();
  [kvoLock lock];

  // 产生新的子类
  r = replacementForClass([self class]);

  /*
   * Get the existing observation information, creating it (and changing
   * the receiver to start key-value-observing by switching its class)
   * if necessary.
   */
  info = (GSKVOInfo*)[self observationInfo];
  if (info == nil) {
      info = [[GSKVOInfo alloc] initWithInstance: self];
      [self setObservationInfo: info];

      // 第一次添加观察的时候替换被观察对象的的类, 将`isa`指向新的子类
      object_setClass(self, [r replacement]);
    }

  /*
   * Now add the observer.
   */
  dot = [aPath rangeOfString:@"."];
  if (dot.location != NSNotFound) {
      forwarder = [[NSKeyValueObservationForwarder alloc]
        initWithKeyPath: aPath
	       ofObject: self
	     withTarget: anObserver
		context: aContext];
      [info addObserver: anObserver
             forKeyPath: aPath
                options: options
                context: forwarder];
  } else {
      [r overrideSetterFor: aPath];
      [info addObserver: anObserver
             forKeyPath: aPath
                options: options
                context: aContext];
    }

  [kvoLock unlock];
}
复制代码

GSKVOInfo类的addObserver:...

- (void) addObserver: (NSObject*)anObserver
	  forKeyPath: (NSString*)aPath
	     options: (NSKeyValueObservingOptions)options
	     context: (void*)aContext {
  GSKVOPathInfo         *pathInfo;
  GSKVOObservation      *observation;
  unsigned              count;

  // 不响应observeValueForKeyPath:ofObject:change:context:直接返回
  // anObserver可能不是继承自NSObject
  if ([anObserver respondsToSelector:
    @selector(observeValueForKeyPath:ofObject:change:context:)] == NO) {
      return;
   }
  [iLock lock];
  pathInfo = (GSKVOPathInfo*)NSMapGet(paths, (void*)aPath);
  if (pathInfo == nil) {
      pathInfo = [GSKVOPathInfo new];
      // use immutable object for map key
      aPath = [aPath copy];
      NSMapInsert(paths, (void*)aPath, (void*)pathInfo);
      [pathInfo release];
      [aPath release];
    }

  observation = nil;
  pathInfo->allOptions = 0;
  count = [pathInfo->observations count];
  while (count-- > 0) {
      GSKVOObservation      *o;
      o = [pathInfo->observations objectAtIndex: count];
      if (o->observer == anObserver)  {
          o->context = aContext;
          o->options = options;
          observation = o;
        }
      pathInfo->allOptions |= o->options;
    }
  if (observation == nil) {
      observation = [GSKVOObservation new];
      GSAssignZeroingWeakPointer((void**)&observation->observer,
	(void*)anObserver);
      observation->context = aContext;
      observation->options = options;
      [pathInfo->observations addObject: observation];
      [observation release];
      pathInfo->allOptions |= options;
    }

  // NSKeyValueObservingOptionInitial
  if (options & NSKeyValueObservingOptionInitial) {
      /* If the NSKeyValueObservingOptionInitial option is set,
       * we must send an immediate notification containing the
       * existing value in the NSKeyValueChangeNewKey
       */
      [pathInfo->change setObject: [NSNumber numberWithInt: 1]
                           forKey:  NSKeyValueChangeKindKey];
      if (options & NSKeyValueObservingOptionNew) {
          id    value;
          value = [instance valueForKeyPath: aPath];
          if (value == nil) {
              value = null;
          }
          [pathInfo->change setObject: value
                               forKey: NSKeyValueChangeNewKey];
        }
      // Option为NSKeyValueObservingOptionInitial, 就会通知一次
      [anObserver observeValueForKeyPath: aPath
                                ofObject: instance
                                  change: pathInfo->change
                                 context: aContext];
    }
  [iLock unlock];
}

复制代码

observeValueForKeyPath...

- (void) observeValueForKeyPath: (NSString*)aPath
		       ofObject: (id)anObject
			 change: (NSDictionary*)aChange
		        context: (void*)aContext
{
  // 默认实现抛出异常
  [NSException raise: NSInvalidArgumentException
              format: @"-%@ cannot be sent to %@ ..."
              @" create an instance overriding this",
              NSStringFromSelector(_cmd), NSStringFromClass([self class])];
  return;
}
复制代码

removeObserver

- (void) removeObserver: (NSObject*)anObserver forKeyPath: (NSString*)aPath {
  GSKVOPathInfo	*pathInfo;

  [iLock lock];
  pathInfo = (GSKVOPathInfo*)NSMapGet(paths, (void*)aPath);
  if (pathInfo != nil) {
      unsigned  count = [pathInfo->observations count];
      pathInfo->allOptions = 0;
      while (count-- > 0) {
          GSKVOObservation      *o;
          o = [pathInfo->observations objectAtIndex: count];
          if (o->observer == anObserver || o->observer == nil) {
              [pathInfo->observations removeObjectAtIndex: count];
              if ([pathInfo->observations count] == 0) {
                  NSMapRemove(paths, (void*)aPath);
                }
            }
          else {
              pathInfo->allOptions |= o->options;
            }
	}
    }
  [iLock unlock];
}
复制代码

实现KVO的关键

setValue:forKey:

- (void) setValue:(id)anObject forKey:(NSString*)aKey {
  Class		c = [self class];
  void		(*imp)(id,SEL,id,id);

  imp = (void (*)(id,SEL,id,id))[c instanceMethodForSelector: _cmd];

  // 实现核心
  if ([[self class] automaticallyNotifiesObserversForKey: aKey]) {
      [self willChangeValueForKey: aKey];
      imp(self,_cmd,anObject,aKey); // 调用原生的setValue:forKey:方法
      [self didChangeValueForKey: aKey];
    }
  else {
      imp(self,_cmd,anObject,aKey);
    }
}
复制代码

willChangeValueForKey:

- (void) willChangeValueForKey: (NSString*)aKey {
  GSKVOPathInfo *pathInfo;
  GSKVOInfo     *info;

  info = (GSKVOInfo *)[self observationInfo];
  if (info == nil) {
      return;
  }

  pathInfo = [info lockReturningPathInfoForKey: aKey];
  if (pathInfo != nil) {
      if (pathInfo->recursion++ == 0) {
          id old = [pathInfo->change objectForKey: NSKeyValueChangeNewKey];

          if (old != nil) {
              /* We have set a value for this key already, so the value
               * we set must now be the old value and we don't need to
               * refetch it.
               */
              [pathInfo->change setObject: old
                                   forKey: NSKeyValueChangeOldKey];
              [pathInfo->change removeObjectForKey: NSKeyValueChangeNewKey];
            }
          else if (pathInfo->allOptions & NSKeyValueObservingOptionOld) {
              /* We don't have an old value set, so we must fetch the
               * existing value because at least one observation wants it.
               */
              old = [self valueForKey:aKey];
              if (old == nil) {
                  old = null;
                }
              [pathInfo->change setObject: old
                                   forKey: NSKeyValueChangeOldKey];
            }
          [pathInfo->change setValue:
            [NSNumber numberWithInt: NSKeyValueChangeSetting]
            forKey: NSKeyValueChangeKindKey];

          // 发通知调用观察者的
          // observeValueForKeyPath:ofObject:change:context:方法
          [pathInfo notifyForKey:aKey ofInstance:[info instance] prior:YES];
        }
      [info unlock];
    }

  [self willChangeValueForDependentsOfKey:aKey];
}
复制代码

didChangeValueForKey:

- (void)didChangeValueForKey:(NSString*)aKey {
  GSKVOPathInfo *pathInfo;
  GSKVOInfo	*info;

  info = (GSKVOInfo *)[self observationInfo];
  if (info == nil) {
      return;
  }

  pathInfo = [info lockReturningPathInfoForKey: aKey];
  if (pathInfo != nil) {
      if (pathInfo->recursion == 1) {
          id  value = [self valueForKey: aKey];

          if (value == nil) {
              value = null;
           }
          [pathInfo->change setValue:value
                              forKey:NSKeyValueChangeNewKey];
          [pathInfo->change setValue:
            [NSNumber numberWithInt:NSKeyValueChangeSetting]
            forKey:NSKeyValueChangeKindKey];

           // 发通知调用观察者的
           // observeValueForKeyPath:ofObject:change:context:方法
          [pathInfo notifyForKey:aKey ofInstance:[info instance] prior:NO];
        }
      if (pathInfo->recursion > 0) {
          pathInfo->recursion--;
        }
      [info unlock];
    }

  [self didChangeValueForDependentsOfKey: aKey];
}
复制代码

notifyForKey:ofInstance:prior:

- (void)notifyForKey:(NSString *)aKey ofInstance:(id)instance prior:(BOOL)f {
  unsigned      count;
  id            oldValue;
  id            newValue;

  if (f == YES) {
      if ((allOptions & NSKeyValueObservingOptionPrior) == 0) {
          return;   // Nothing to do.
        }
      [change setObject: [NSNumber numberWithBool: YES]
                 forKey: NSKeyValueChangeNotificationIsPriorKey];
    }
  else {
      [change removeObjectForKey: NSKeyValueChangeNotificationIsPriorKey];
  }

  oldValue = [[change objectForKey: NSKeyValueChangeOldKey] retain];
  if (oldValue == nil) {
      oldValue = null;
  }
  newValue = [[change objectForKey: NSKeyValueChangeNewKey] retain];
  if (newValue == nil) {
      newValue = null;
  }

  /* Retain self so that we won't be deallocated during the
   * notification process.
   */
  [self retain];
  count = [observations count];
  while (count-- > 0) {
      GSKVOObservation  *o = [observations objectAtIndex: count];

      if (f == YES) {
          if ((o->options & NSKeyValueObservingOptionPrior) == 0) {
              continue;
            }
      } else {
          if (o->options & NSKeyValueObservingOptionNew) {
              [change setObject: newValue
                         forKey: NSKeyValueChangeNewKey];
           }
      }

      if (o->options & NSKeyValueObservingOptionOld) {
          [change setObject: oldValue
                     forKey: NSKeyValueChangeOldKey];
      }

      // 回到观察者的调用
      [o->observer observeValueForKeyPath: aKey
                                 ofObject: instance
                                   change: change
                                  context: o->context];
    }

  [change setObject: oldValue forKey: NSKeyValueChangeOldKey];
  [oldValue release];
  [change setObject: newValue forKey: NSKeyValueChangeNewKey];
  [newValue release];
  [self release];
}
复制代码

总结

KVO的流程

  1. Class A某个实例对象注册为 Class B实例对象某个属性attr的观察者
    • [instanceB addObserve:instanceA forKeyPath:attr....]
  2. instanceB在添加观察者的时候, 会创建一个继承自Class B的子类NSKVONotifying_B, 然后将instanceB的isa指针指向NSKVONotifying_B
  3. 然后重写NSKVONotifying_B attr属性的setter方法, 以达到键值观察的目的

理解如有错误 望指正 转载请说明出处

文章分类
iOS
文章标签