面试宝点

KVO+KVC底层原理

2020-08-03  本文已影响0人  鼬殿

KVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变


未使用KVO监听的对象

KVO的实现

@interface Person : NSObject
@property (assign, nonatomic) int age;
@end

@implementation Person
@end

@interface ViewController ()
@property (strong, nonatomic) Person *person;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.person = [[Person alloc] init];
    self.person.age = 10;
    // 给person对象添加KVO监听
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [self.person addObserver:self forKeyPath:@"age" options:options context:nil];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    self.person.age = 20;
}

- (void)dealloc {
    [self.person removeObserver:self forKeyPath:@"age"];
}

// 当监听对象的属性值发生改变时,就会调用
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}

KVO的底层实现

在上面的代码基础上添加验证打印

#import <objc/runtime.h>
NSLog(@"person添加KVO监听之前,self.person实例变量的isa指针指向 - %@类对象",object_getClass(self.person));
// 给person对象添加KVO监听
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person addObserver:self forKeyPath:@"age" options:options context:nil];
NSLog(@"person添加KVO监听之后setAge方法内存地址 - %p",[self.person methodForSelector:@selector(setAge:)]);
person添加KVO监听之前,self.person实例变量的isa指针指向 - Person类对象
person添加KVO监听之后,self.person实例变量的isa指针指向- NSKVONotifying_Person类对象
NSLog(@"person添加KVO监听之前setAge方法内存地址 - %p",[self.person methodForSelector:@selector(setAge:)]);
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person addObserver:self forKeyPath:@"age" options:options context:nil];
NSLog(@"person添加KVO监听之后setAge方法内存地址 - %p",[self.person methodForSelector:@selector(setAge:)]);
person添加KVO监听之前setAge方法内存地址 - 0x102105cdc
person添加KVO监听之后setAge方法内存地址 - 0x18b412834
(lldb) p (IMP)0x102105cdc
(IMP) $0 = 0x0000000102105cdc (Interview01`-[Person setAge:] at ViewController.m:13)
(lldb) p (IMP)0x18b412834
(IMP) $1 = 0x000000018b412834 (Foundation`_NSSetIntValueAndNotify)
#import <objc/runtime.h>
[self printMethodNamesOfClass:object_getClass(self.person)];
 // 给person对象添加KVO监听
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person addObserver:self forKeyPath:@"age" options:options context:nil];
[self printMethodNamesOfClass:object_getClass(self.person)];

- (void)printMethodNamesOfClass:(Class)cls
{
    unsigned int count;
    // 获得方法数组
    Method *methodList = class_copyMethodList(cls, &count);
    // 存储方法名
    NSMutableString *methodNames = [NSMutableString string];
    // 遍历所有的方法
    for (int i = 0; i < count; i++) {
        // 获得方法
        Method method = methodList[I];
        // 获得方法名
        NSString *methodName = NSStringFromSelector(method_getName(method));
        // 拼接方法名
        [methodNames appendString:methodName];
        [methodNames appendString:@", "];
    }
    // 释放
    free(methodList);
    // 打印方法名
    NSLog(@"%@ %@", cls, methodNames);
}
Person age, setAge:
NSKVONotifying_Person setAge:, class, dealloc, _isKVOA

可以看到NSKVONotifying_Person中实现了上述方法,由以上结论我们可以得出KVO底层实现的伪代码大致如下:

@interface NSKVONotifying_Person : Person
@end

@implementation NSKVONotifying_Person

- (void)setAge:(int)age
{
    _NSSetIntValueAndNotify();
}

// 伪代码
void _NSSetIntValueAndNotify()
{
    [self willChangeValueForKey:@"age"];
    [super setAge:age];
    [self didChangeValueForKey:@"age"];
}

- (void)didChangeValueForKey:(NSString *)key
{
    // 通知监听器,某某属性值发生了改变
    [oberser observeValueForKeyPath:key ofObject:self change:nil context:nil];
}

// 屏幕内部实现,隐藏了NSKVONotifying_MJPerson类的存在
- (Class)class
{
    return [Person class];
}

- (void)dealloc
{
    // 收尾工作
}

- (BOOL)_isKVOA
{
    return YES;
}

@end

图册如下:


willChangeValueForKey:和didChangeValueForKey的调用我们可以在Person类验证

面试题

iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)

如何手动触发KVO?
手动调用willChangeValueForKey:和didChangeValueForKey:

直接修改成员变量会触发KVO么?
不会触发KVO(没有触发set方法)

KVC

KVC的全称是Key-Value Coding,俗称“键值编码”,可以通过一个key来访问某个属性
常见的API有

- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key; 

setValue:forKey:的原理

accessInstanceVariablesDirectly方法的默认返回值是YES

@interface Person : NSObject
{
    @public
    int age;
    int isAge;
    int _isAge;
    int _age;
}
@end

@implementation Person

- (void)setAge:(int)age
{
    NSLog(@"setAge: - %d", age);
}

- (void)_setAge:(int)age
{
    NSLog(@"_setAge: - %d", age);
}

// 默认的返回值就是YES
+ (BOOL)accessInstanceVariablesDirectly
{
    return YES;
}

@end
Person *person = [[MJPerson alloc] init];
[person setValue:@10 forKey:@"age"];

valueForKey:的原理

@interface Person : NSObject
{
    @public
    int age;
    int isAge;
    int _isAge;
    int _age;
}
@end

@implementation Person

- (int)getAge
{
    return 11;
}

- (int)age
{
    return 12;
}

- (int)isAge
{
    return 13;
}

- (int)_age
{
    return 14;
}

// 默认的返回值就是YES
+ (BOOL)accessInstanceVariablesDirectly
{
    return YES;
}

@end
Person *person = [[Person alloc] init];
person->_age = 10;
person->_isAge = 11;
person->age = 12;
person->isAge = 13;
NSLog(@"%@", [person valueForKey:@"age"]);

面试题

通过KVC修改属性会触发KVO么?
会触发KVO,setValue:forKey:方法内部会调用
willChangeValueForKey:
didChangeValueForKey:

- (void)willChangeValueForKey:(NSString *)key
{
    [super willChangeValueForKey:key];
}

- (void)didChangeValueForKey:(NSString *)key
{
    [super didChangeValueForKey:key];
}
上一篇下一篇

猜你喜欢

热点阅读