OC底层原理20-KVO分析

这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战

一.前言

  • KVO的一些细节
  • 探索KVO原理
  • 自定义KVO

KVO官方文档链接

一.KVO细节分析上

1.关于context的细节 官方说明如下图
注册
Xnip2021-08-15_09-14-38.jpg
监听回调

Xnip2021-08-15_09-15-10.jpg
删除

Xnip2021-08-15_09-15-59.jpg

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];
}

复制代码

打印结果

Xnip2021-08-15_09-47-45.jpg

根据官方文档 如果我们用KVOremoveObserver的话 例如:进入一个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.对可变数组的观察
Xnip2021-08-15_11-14-29.jpg

  [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原理分析

Xnip2021-08-15_11-22-01.jpg

  1. 只对属性观察 setter
  2. 中间类 – self.person -> LGPerson isa 发生了变化 NSKVONotifying_LGPerson (LGPerson 子类)
  3. 有什么东西 – 方法 – 属性 setNickNameclassdealloc_isKVOA
  4. 继承 – 重写 – 实实在在的 实现
  5. setter 子类 – 父类改变 nickName 传值
  6. willchange 父类的setter didChange
  7. NSKVONotifying_LGPerson 是否移除 +isa 是否会回来 在移除观察的时候

//添加监听的时候确实动态生成了NSKVONotifying_LGPerson
Xnip2021-08-15_11-30-59.jpg

Xnip2021-08-15_11-34-33.jpg

#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);
}
复制代码

Xnip2021-08-15_11-39-11.jpg

四.KVO原理下

  • 1.isa->LGPerson->NSKVONotifying_LGPerson消失
  • 1.1 动态生成 NSKVONotifying_LGPerson
  • 1.2 LGPerson VS NSKVONotifying_LGPerson 父子
  • 1.3 NSKVONotifying_LGPerson有哪些方法
  •   `setNickName` //重写set方法
    复制代码
  •   `class`
    复制代码
  •   `dealloc`
    复制代码
  •   `_isKVOA`
    复制代码
  • 1.4 isa指回来 //通过这个方法_isKVOA
  • 1.5 NSKVONotifying_LGPerson是否销毁

-2.setterKVO实例法(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
复制代码

Xnip2021-08-15_12-28-36.jpg
remove前是NSKVONotifying_LGPerson remove后是LGPerson 说明isa指回来了

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享