“这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战”
一.前言
- KVO的一些细节
- 探索KVO原理
- 自定义KVO
一.KVO细节分析上
1.关于context的细节 官方说明如下图
注册

监听回调

删除

static void *PersonNameContext = &PersonNameContext;
@interface LGViewController ()
@property (nonatomic, strong) LGPerson  *person;
@end
@implementation LGViewController
- (void)viewDidLoad {
    [super viewDidLoad];
     self.person  = [LGPerson new];
    [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:PersonNameContext];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    self.person.name = @"test";
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
   if(context == PersonNameContext){
        NSLog(@"%@",change);
    }
}
- (void)dealloc{
 [self.person removeObserver:self forKeyPath:@"name" context:PersonNameContext];
}
复制代码打印结果

根据官方文档 如果我们用KVO没removeObserver的话 例如:进入一个VC里面有一个单例持有观察VC的属性 哪怕VC被释放 单例没释放继续观察 在次进入VC的时候 观察属性发生变化 之前已经被释放的VC会继续接收到消息 因为已经被释放 所以野指针 崩溃了
二.KVO细节分析下
1.手动观察属性
// 自动开关
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key{
    return NO;
}
- (void)setName:(NSString *)name
{
    [self willChangeValueForKey:@"name"];
    _name = name;
    [self didChangeValueForKey:@"name"];
}
复制代码2.监听一个属性 通过另外两个变量去控制
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
    
    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
    if ([key isEqualToString:@"downloadProgress"]) {
        NSArray *affectingKeys = @[@"totalData", @"writtenData"];
        keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
    }
    return keyPaths;
}
- (NSString *)downloadProgress{
    if (self.writtenData == 0) {
        self.writtenData = 10;
    }
    if (self.totalData == 0) {
        self.totalData = 100;
    }
    return [[NSString alloc] initWithFormat:@"%f",1.0f*self.writtenData/self.totalData];
}
//监听downloadProgress这个属性
[self.person addObserver:self forKeyPath:@"downloadProgress" options:(NSKeyValueObservingOptionNew) context:NULL];
//改变这两个属性
self.person.writtenData += 10;
self.person.totalData  += 1;
复制代码3.对可变数组的观察

  [self.person addObserver:self forKeyPath:@"dateArray" options:(NSKeyValueObservingOptionNew) context:PersonDataArrayContext];
  self.person.dateArray = [NSMutableArray arrayWithCapacity:1];
  
  
  [[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"1"];
  
  打印结果
  2021-08-15 11:16:20.182185+0800 001---KVO初探[39513:1527764] {
    kind = 1;
    new =     (
    );
  }
  2021-08-15 11:16:25.016788+0800 001---KVO初探[39513:1527764] {
     indexes = "<_NSCachedIndexSet: 0x6000010b84e0>[number of indexes: 1 (in    1 ranges), indexes: (0)]";
    kind = 2;
    new =     (
        1
     );
  }
  
  typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
    NSKeyValueChangeSetting = 1,
    NSKeyValueChangeInsertion = 2,
    NSKeyValueChangeRemoval = 3,
    NSKeyValueChangeReplacement = 4,
};
 kind=2的类型是insert
复制代码三.KVO原理上
1.kvo原理分析

- 只对属性观察 setter
- 中间类 – self.person->LGPerson isa发生了变化NSKVONotifying_LGPerson (LGPerson 子类)
- 有什么东西 – 方法 – 属性 setNickName–class–dealloc–_isKVOA
- 继承 – 重写 – 实实在在的 实现
- setter 子类– 父类改变- nickName传值
- willchange父类的- setter- didChange
- NSKVONotifying_LGPerson是否移除- +isa是否会回来 在移除观察的时候
//添加监听的时候确实动态生成了NSKVONotifying_LGPerson


#pragma mark - 遍历类以及子类
- (void)printClasses:(Class)cls{
    
    // 注册类的总数
    int count = objc_getClassList(NULL, 0);
    // 创建一个数组, 其中包含给定对象
    NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];
    // 获取所有已注册的类
    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]];
        }
    }
    free(classes);
    NSLog(@"classes = %@", mArray);
}
复制代码
四.KVO原理下
- 1.isa->LGPerson->NSKVONotifying_LGPerson–消失
- 1.1 动态生成 NSKVONotifying_LGPerson
- 1.2 LGPersonVSNSKVONotifying_LGPerson父子
- 1.3 NSKVONotifying_LGPerson有哪些方法
- 
`setNickName` //重写set方法 复制代码
- 
`class` 复制代码
- 
`dealloc` 复制代码
- 
`_isKVOA` 复制代码
- 1.4 isa指回来 //通过这个方法_isKVOA
- 1.5 NSKVONotifying_LGPerson是否销毁
-2.setter方KVO实例法(setter)/class
- 
2.1 成员 VS 属性 
- 
2.2 修改 LGPerson属性
#pragma mark - 遍历方法-ivar-property
- (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);
}
//遍历NSKVONotifying_LGPerson有哪些方法
[self printClassAllMethod:objc_getClass("NSKVONotifying_LGPerson")];
2021-08-15 12:20:25.681840+0800 002---KVO原理探讨[39748:1566211] setNickName:-0x7fff207bf03f
2021-08-15 12:20:25.682122+0800 002---KVO原理探讨[39748:1566211] class-0x7fff207bdb49
2021-08-15 12:20:25.682305+0800 002---KVO原理探讨[39748:1566211] dealloc-0x7fff207bd8f7
2021-08-15 12:20:25.682472+0800 002---KVO原理探讨[39748:1566211] _isKVOA-0x7fff207bd8ef
复制代码
remove前是NSKVONotifying_LGPerson remove后是LGPerson 说明isa指回来了
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END
    























![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)
