「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战」。

KVO

KVO,全称Key-Value observing,即键值观察,Apple官方文档,它可以将其他对象指定属性的更改通知给观察者,在iOS开发中,经常使用kvo监工行2.5亿存款不翼而飞听属性的变化,并做出响应(例如UI刷新等)。
测试测试你的自卑程度Demo地址

特点

  • 一对多
  • 只能监听对象属性的变化
  • 通过NSString查找,数组公式编写时不会查错补测试用例
  • 发送通apple id密码重置知由系统控制
  • 可以记录新旧值得变化

使用

一 注册观察者(addObse宫颈癌rver:forKeyPath:options:context)

  • self.person 被观察对象
  • observ数组公式er 响应对象
  • keyPath 观察的属性
  • options 定义观察选项,下面回详细讲
  • context 个人理指针式万用表解一个标记,用来区分在回调里区分通知数组c语言来源,不使用则NULL
static void *NameContext = &NameContext;
self.person = CQPerson.new;
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NameContext];
[self.person addObserver:self forKeyPath:@"nickName" options:NSKeyValueObservingOptionNew context:NULL];

二 在addObserver:forKeyPath:options:context函数监听观察通知回调

  • keyPath 属性
  • object 对象数组词
  • change 新旧值集合,和注册的options有关
  • context 可根据context直接区分,例如不同对象的同名属性,当然用obj配合keyPath也可以
// object
- (void)observeValueForKeyPath:(**NSString** *)keyPath ofObject:(id)object change:(**NSDictionary**<NSKeyValueChangeKey,id> *)change context:(void *)context {
  if (context == NameContext) {
  }
  NSLog(@"对象-%@,属性-%@",object, keyPath);
}

三 移除观测试工程师察者(removeObserver:forKeyPath:context)

  • 必须注册了才能移除,否则NS宫颈癌疫苗RangeException崩溃,可使用try
  • 注册和移除成对出现,如果不移除,释放后依然会有通知,可能导致野指针崩溃
  • 典型使用,在init指针或viewDidLoad中注指针数组册为观察者,在dealloc 中移除
[self.person removeObserver:self forKeyPath:@"name" context:NULL];
[self.person removeObserver:self forKeyPath:@"nickName" context:NULL];

自动触发和手动触发

  • 默认为自动触发,通过重写automaticallyNotifiesObserversForKey可关闭
+ (BOOL)automaticallyNotifiesObserversForKey:(**NSString** *)key {
  return NO;
}
  • 如果属性不可见或只读,通枸杞过公开函数间接调用私有set函数,可以触发
  • 如果属性不可见或只读,通过performSelector或IMP调指针数组用set函数,均可触发
  • 通过kvc修改属性名,可以触发
  • 在以上基础上,赋相同的值,也会触发,部分场景下,注意检查是否相同
// 通过公开函数间接调用私有set函数,可以监听到*
[self.person reloadName];
// 通过performSelector 调用私有set函数,可以监听到*
[self.person performSelector:@selector(setName:) withObject:@"1"];
// 通过IMP 调用私有set函数,可以监听到*
SEL sel = NSSelectorFromString(@"setName:");
IMP imp = [self.person methodForSelector:sel];
void(*func)(id, SEL, **NSString** *) = (void *)imp;
func(self.person, sel, @"1");
// 简写
((void(*)(id, SEL, **NSString** *))[self.person methodForSelector:sel])(self.person, sel, @"1");
// 通过kvc直接修改属性名,会触发KVO
[self.person setValue:@"1" forKey:@"nickName"];
  • 通过指针万用表怎么读数kvc直接修改私有成员变量,不会触发KVO
  • 通过公开函数间接修改私有成员变量,也不会触发KVO
  • 总之,对成员变量修改不会触发,只有属性的修改才能触发
// 通过kvc直接修改私有成员变量,不会触发KVO
[self.person setValue:@"100" forKey:@"_nickName"];
  • 关闭自动龚俊触发后,可手动触发
  • willChangeValueForKey 修改前触发
  • didChangeValueForKey 修改后触发
  • options为Old和New时两个函数必须全部调用才能触发
  • options为Prior时,只调用willChangeValueForKey可触发修改前的监听,必须两个都实现才能触发修改后的监听
- (void)setName:(**NSString** * _Nonnull)name {
  [self willChangeValueForKey:@"name"];
  _name = name;
  [self didChangeValueForKey:@"name"];
}

合并触发

数组指针如C属性依赖A和B,只要工行2.5亿存款不翼而飞AB有一个变C就会变,可以重写keyPath指针数组sFo测试你适合学心理学吗rValuesAffectingValueForKey函数

+ NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
  NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
  if ([key isEqualToString:@"mergeName"]) {
    NSArray *affectingKeys = @[@"name", @"nickName"];
    keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
  }
  return keyPaths;
}
  • 当然,在AB的set函数里调用C的set也是OK的

观察容器类

  • 对容google器类添加数据是不会调用set函数的,所以也触发不了kvo
  • 需要使用对应的函数,例如数组mutableArrayValueForKey
[self.person.testArr addObject:@"1"]; // 不会触发
[[self.person mutableArrayValueForKey:@"testArr"] addObject:@"1"]; // 可以触发

# NSKeyValueObservingOptio工行2.5亿存款不翼而飞ns

一共有四种,可同时使用,分别是

  • NSKeyValueOb数组长度serv数组c语言ingOptionNew:在回调chang指针数组e里提供更改后的新值(key-@”new”),值被修改后触发回调

  • NSKeyValueObservingOptioapple watchnOld:在回调change提供更改前的值(key-@”old”),值被修改后触发回调

  • NSKe工商银行yValueObservingOptionInitial:观察最初的值(Apple在注册观察服务时会调用一次触发方法)

  • NSKeyValueO数组指针bservingOptio指针和引用的区别nPrior:分别在值修改前后触发方法测试抑郁症的20道题(即一次修改有两次触发)在回调change里Apple有key@”notification数组词IsPrior”

  • 注意如果使用了手动触发

    • options为Old和New时willChangeValueForKey/didChangeValueFor测试你适合学心理学吗Key必须全部调用才能触发
    • options为Prior时,只调用willChangeV指针万用表的使用方法alueForKey可触发修改前的监听,必须两个都实现才能触发修改后的监听
  • 四种Option可同时使用

原理解析

  • 保证自动触发没有关闭
  • 注册前后打印isa指针的指向,可以发现注册后指向了派生类NSKVONotifying_CQPerson
(lldb) po object_getClassName(self.person)**
"CQPerson"
(lldb) po object_getClassName(self.person)**
"NSKVONotifying_CQPerson"
  • 继续探究测试用例中间类,发现中间类重写了观察属性的setter方法、class、dealloc、_isKVOA方法,隐藏对象真实类信息
  • 重写dealloc做了一些 KVO 内存释放
  • 在setter方法内部调用了Foundation 的 _NSS测试手机是否被监控etObjectValu数组去重方法eAndNotify 函数
      • a) 首先会调用 w数组c语言illChangeValueForKey
      • b) 然后给属性赋值
      • c) 最后调用 didChangeValueForKey
      • d) 最后调用 observer 的 observeValueForKeyPath 去告诉监听器属性值发生了改变 .app store
- (void)printClass:(Class)cls {
  // 注册类的总数*
  int count = objc_getClassList(NULL, 0);
  NSMutableArray *mArray = [**NSMutableArray array];
  // 获取所有已注册的类
  Class *classes = (Class *)malloc(sizeof(Class)*count);
  objc_getClassList(classes, count);
  for (int i = 0; i<count; i++) {
    if (cls == class_getSuperclass(classes[i])) {
      [mArray addObject:classes[i]];
      [self printClassAllMethod:cls];
    }
  }
  free(classes);
  NSLog(@"class = %@", mArray);
}
- (void)printClassAllMethod:(Class)cls{
  unsigned int count = 0;
  Method *methodList = class_copyMethodList(cls, &count);
  for (int i = 0; i<count; i++) {
    Method method = methodList[i];
    SEL sel = method_getName(method);
    IMP imp = class_getMethodImplementation(cls, sel);
    NSLog(@"%@-%p",NSStringFromSelector(sel),imp);
  }
  free(methodList);
}
  • 移除KVO观察者之后,实例对象isa指向由中间类更改为原有类
  • 中间类从创建后,就一测试用例存在内存中,不会被销毁

自定义KVO

  • 跟系统kvo流程基本一致,做一些优化处理,例如block回调
  • 大致步骤
    • 注册观察者以及响应
      • 1、验证set函数是否存在
      • 2、保存信息
      • 3、动态生成子类,重写classsetter方法
      • 4、在子类的set函数中向父类发消息,即自定义消息发送
      • 5、指针式万用表让观察者响应
    • 移除观察者
    • 1、更工行2.5亿存款不翼而飞isa指向为原有类
    • 2、重写子类的dealloc方法

参考facebookarchiv测试抑郁程度的问卷e/KVOControllerapple watch