面试中常见问题总结(二)

非正式协议和正式协议

非正式协议:凡是给NSObject或者其子类添加的类别都是非正式协议
正式协议:使用 @protocol 方式声明的方法列表,有 @require 和 @optional 两种类型

KVC 键值编码

之所以可以使用就是因为给 NObject 增加了 NSKeyValueCoding 非正式协议,NSObject 实现了协议,OC中几乎所有的对象都支持KVC,获取值的方法是不通过属性的setter、getter 方法,而是通过属性名称的key间接访问了实例变量,可以访问私有变量

1
2
3
4
 // 官方注释
/* Send -setObject:forKey: to the receiver, unless the value is nil, in which case send -removeObjectForKey:.
*/
- (void)setValue:(nullable ObjectType)value forKey:(NSString *)key;

如果为nil, 就会自动过滤掉, 此方法常用在字典中过滤空值

// forKeyPath: 用于符合属性,能够通过层层访问内部属性,相当于调用属性点语法, 拥有forKey 的所有功能

1
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
  • 实现原理
    • 利用 setValue: forKeyPath: 赋值
    1. 底层实现还是使用 runtime 给对象发消息,先去查找属性是否有setter方法,存在setter 就会通过 setter 方法复制,
    2. 如果没有setter方法,接着查找下是否划线的成员变量,如果有直接给赋值
    3. 如果没有下划线的成员变量,查找是否存在和key值相同的属性,如果有就直接给属性赋值
    4. 如果没有和key值相同的属性,就会执行 setValue: forUndefinedKey: 抛出一个异常程序奔溃,这也是字典转模型中需要实现这个方法的原因

KVO 键值监听

利用一个key值来找到某个属性并监听某个属性的值的变化发送给观察者,可以理解为简单的观察者模式

  • KVO 使用

    1. 为目标对象的属性添加观察者

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      - (void)testKVO {
      Person *p = [[Person alloc] init];
      self.person = p;
      p.name = @"哈哈";

      [p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];

      p.name = @"我不是原来的我!你能监听到我 ???";
      p.name = @"看我七十二变";
      }
2. 实现监听的方法

1
2
3
4
5
#pragma mark - kvo 回调方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {

NSLog(@"KVO监听到的对象是 = %@, 值 = %@ , change = %@", [object class], keyPath, change);
}
3. 移除观察者
1
2
3
4
5
- (void)dealloc {

[self removeObserver:self forKeyPath:@"name"];
NSLog(@"-----控制器销毁-----");
}
  • KVO原理

    类的属性被添加了观察者后,OC底层就会通过Runtime 动态的创建一个这个类的派生类,当前类内部的isa 指针指向了这个派生类,在派生类中重写基类被观察属性的 setter 方法,调用前后会调用 willChangeValueForKey, setValueForKey, didChangeValue 方法, 从而达到监听属性值的方法