底层

iOS-OC底层18:KV0原理

2020-10-28  本文已影响0人  MonKey_Money

1.概念

KVO(Key-Value-Observer)也就是观察者模式(键值观察),是苹果提供的一套事件通知机制。允许对象监听另一个对象特定属性的改变,并在改变时接收到事件,一般继承自NSObject的对象都默认支持KVO

2.基本使用

2.1.实现基本的监听

//定义类
@interface PWPerson : NSObject

@property (nonatomic, copy) NSString *name;
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.view.backgroundColor = [UIColor whiteColor];
    self.person = [[PWPerson alloc] init];
    self.person.name = @"pw";
    [self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];   
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.person.name = [NSString stringWithFormat:@"%@++",self.person.name];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"%@",change);
}
-(void)dealloc {
    [self.person removeObserver:self forKeyPath:@"name"];
}

打印日志

{
    kind = 1;
    new = "pw++";
}
 {
    kind = 1;
    new = "pw++++";
}

需要注意的两点

1.addObserver:forKeyPath:options:context: 中context存在的意义
如果在一个UIViewController中,同一个类的两个对象监听同一个forKeyPath,我们在observeValueForKeyPath:ofObject:change:context的判断就会比较多,keyPath和object两者都要判断,如果我们在添加观察者时context值不为空,我们在这里就可以直接判断context了
2.removeObserver:forKeyPath:是必须的吗?
一般情况下不会有问题,如果观察的对象是一个单利,当被观察的KeyPath改变时可能会向一个野指针发送消息。

2.2.KVO自动触发改成手动触发

重写automaticallyNotifiesObserversForKey方法
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    if ([key isEqual:@"name"]) {
        return NO;
    }
    return YES;
}

当我们点击屏幕修改name属性时,没有收到信息,我们怎么改动才会收到信息呢
重写set方法

-(void)setName:(NSString *)name {
    [self willChangeValueForKey:@"name"];
    _name = [name copy];
    [self didChangeValueForKey:@"name"];
}

我们收到信息

{
    kind = 1;
    new = "pw++++++++";
}

2.3 嵌套层次

@interface PWPerson : NSObject
@property (nonatomic, copy) NSString *firstName;

@property (nonatomic, copy) NSString *secondName;

@property (nonatomic, copy) NSString *allName;
@end
@implementation PWPerson
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
    if ([key isEqualToString:@"allName"]) {
        NSArray *affectingkeys = @[@"firstName",@"secondName"];
        keyPaths = [keyPaths setByAddingObjectsFromArray:affectingkeys];
    }
    return keyPaths;
}
-(NSString *)allName {
    return [NSString stringWithFormat:@"%@-----%@",self.firstName,self.secondName];
}
@end
self.person.firstName = @"first";
    self.person.secondName = @"second";
    [self.person addObserver:self forKeyPath:@"allName" options:(NSKeyValueObservingOptionNew) context:NULL];

//触发更改
    self.person.firstName = [NSString stringWithFormat:@"%@++",self.person.firstName];
    self.person.secondName = [NSString stringWithFormat:@"%@++",self.person.secondName];

打印日志

 {
    kind = 1;
    new = "first++++++++-----second++++++++";
}

allName是有firstName和secondName合成而来的,firstName和secondName的更改决定了allName,所以我们增加了嵌套,firstName和secondName决定了allName的值

2.3 观察数组

我们在被观察类里添加可变数组

@property (nonatomic, strong) NSMutableArray *hobbyArray;
//观察对象的可变数组
    [self.person addObserver:self forKeyPath:@"hobbyArray" options:(NSKeyValueObservingOptionNew) context:NULL];

//向数组中添加数据
    [self.person.hobbyArray addObject:@"footBall"];

但是我们没有收到信息,这是为什么呢?

NSObject provides a basic implementation of automatic key-value change notification. Automatic key-value change notification informs observers of changes made using key-value compliant accessors, as well as the key-value coding methods. Automatic notification is also supported by the collection proxy objects returned by, for example, mutableArrayValueForKey:.
我们需要用mutableArrayValueForKey来修改集合的数据

    [[self.person mutableArrayValueForKey:@"hobbyArray"] addObject:@"footBall"];
//打印日志
{
    indexes = "<_NSCachedIndexSet: 0x600000c02920>[number of indexes: 1 (in 1 ranges), indexes: (3)]";
    kind = 2;
    new =     (
        footBall
    );
}

我们看到 kind = 2,在前面我们看到kind = 1,kind到底是何方神圣呢?

typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
    NSKeyValueChangeSetting = 1,
    NSKeyValueChangeInsertion = 2,
    NSKeyValueChangeRemoval = 3,
    NSKeyValueChangeReplacement = 4,
};

我们触发让kind等于3或者等于4呢?NSKeyValueChangeRemoval我们看到removal应该是移除,NSKeyValueChangeReplacement应该是更换

2.3.1从数组中移除元素

移除元素之前,我们先确定数组中有元素所以代码如下

    self.person.hobbyArray = [@[@"footBall",@"basketBall",@"pingpngBall"] mutableCopy];
//触发观察
    [[self.person mutableArrayValueForKey:@"hobbyArray"] removeLastObject];
打印日志
{
    indexes = "<_NSCachedIndexSet: 0x600000c30ba0>[number of indexes: 1 (in 1 ranges), indexes: (2)]";
    kind = 3;
}

我们心心念念的kind = 3出现了。kind = 4呢?

    [[self.person mutableArrayValueForKey:@"hobbyArray"] replaceObjectAtIndex:0 withObject:@"billiards"];
//打印日志
{
    indexes = "<_NSCachedIndexSet: 0x6000032d8e40>[number of indexes: 1 (in 1 ranges), indexes: (0)]";
    kind = 4;
    new =     (
        billiards
    );
}

3.KVO原理

3.1.KVO观察的是属性还是成员变量

我们在PWPerson中添加一个公开的成员变量

@interface PWPerson : NSObject
{
    @public
    NSString *name;
}

@property (nonatomic, copy) NSString *name;

@end
    [self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
    self.person.name = @"pw";
    self.person->name = @"name";

打印日志

{
    kind = 1;
    new = pw;
}

KVO观察的是属性

3.1.谣传KVO会生成中间类

   NSLog(@"----%@",NSStringFromClass(object_getClass(self.person)));
    [self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
    NSLog(@"----%@",NSStringFromClass(object_getClass(self.person)));

打印日志

----PWPerson
----NSKVONotifying_PWPerson

3.1.1查看NSKVONotifying_PWPerson的父类

    NSString *className = @"NSKVONotifying_PWPerson";
    Class  newClass = NSClassFromString(className);
    if (newClass) {
        
        while (newClass) {
            NSLog(@"%@",NSStringFromClass(newClass));
            newClass = class_getSuperclass(newClass);
        }
    }

打印日志

NSKVONotifying_PWPerson
PWPerson
NSObject

3.1.1查看NSKVONotifying_PWPerson的方法列表

    NSString *className = @"NSKVONotifying_PWPerson";
    Class  newClass = NSClassFromString(className);
   unsigned  int count;
    Method *methods = class_copyMethodList(newClass, &count);
    for (int i = 0; i< count; i++) {
        Method method = methods[i];
        SEL sel= method_getName(method);
        NSLog(@"%@",NSStringFromSelector(sel));
        
    }

打印日志

setName:
class
dealloc
 _isKVOA

我们对PWPerson对象进行[self.person class],结果是PWPerson,重写了class方法
setName方法的目的可能就是向观察者发送消息,当值发生变化是发送消息
_isKVOA可能标记这个类是kvo生成的中间类。
dealloc:对观察者释放。

4.KVO简单的自定义

1:动态生成子类:NSKVONotifiy_A

1:防止实例变量的影响,我添加断言异常排除
2:动态子类的过程: A:生成类 B:添加Class方法 C:注册
3.同时也判断类的存在性做了处理

2.动态给子类添加Setter方法

3.消息转发给父类(runtime消息转发)

具体实现可以参看KVOController

上一篇 下一篇

猜你喜欢

热点阅读