Foundation02 - KVC

104 阅读3分钟

设置值的流程

setValue:forKey: setValue:forKeyPath: (keypath 支持 "A.B.C")

  1. setKey: _setKey:, 存在这些方法即调用方法, 否则到2

  2. +accessInstanceVariablesDirectly, 返回true则到3, 否则到4

  3. 根据_key, _isKey, key, isKey的顺序开始查找成员变量, 如果存在成员变量则正常赋值, 否则到4

  4. setValue:forUndefinedKey:,默认实现会抛出异常NSUndefinedKeyException

取值的流程

valueForKey: valueForKeyPath:

  1. getKey key isKey _getKey _key, 存在这些方法即调用方法并返回, 否则到2

  2. +accessInstanceVariablesDirectly, 返回true则到3, 否则到4

  3. 根据_key, _isKey, key, isKey的顺序开始查找成员变量, 如果存在成员变量则取出值并返回, 否则到4

  4. valueForUndefinedKey:,默认实现会抛出异常

KVC是否会触发KVO

利用KVC会触发KVO, 即使该成员变量没有set方法. 在setValue:forKey:中会调用willChangeValueForKey:,didChangeValueForKey:

其他方法

  1. setNilValueForKey: 默认实现会抛出异常NSInvalidArgumentException 当key对应的成员变量的值是基础类型时, 但是传入的value是nil时, 会调用setNilValueForKey:

  2. - (BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError *__autoreleasing  _Nullable *)outError可以用来验证value是否可用, 可以在这个方法中修改ioValue 或者 outError

用途

Mantle 字典转模型

DEMO

//
//  ViewController.m
//  TestKVC
//
//  Created by 王昱 on 2021/9/28.
//

#import "ViewController.h"
@interface WYPerson: NSObject
{
    @public
    int _age;
}
@end

@implementation WYPerson

- (void)willChangeValueForKey:(NSString *)key {

    NSLog(@"willChangeValueForKey - begin");
    [super willChangeValueForKey:key];
    NSLog(@"willChangeValueForKey - end");
}

- (void)didChangeValueForKey:(NSString *)key {

    NSLog(@"didChangeValueForKey - begin");
    [super didChangeValueForKey:key];
    NSLog(@"didChangeValueForKey - end");
}

- (void)setNilValueForKey:(NSString *)key {
    NSLog(@"setNilValueForKey - %@", key);
}

- (BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError *__autoreleasing  _Nullable *)outError {
    if([inKey isEqualToString:@"age"]){
        if([(*ioValue) isKindOfClass:[NSNumber class]]) {
            if([(NSNumber *)(*ioValue) intValue] < 0) {
                *ioValue = @20;
            }
            return YES;
        } else {
            *outError = [NSError errorWithDomain:@"type exception" code:0 userInfo:nil];
            return NO;
        }
    }
    return YES;
}

@end

@interface ViewController ()

@property (nonatomic, strong) WYPerson *person;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    self.person = [WYPerson new];
    [self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld  context:nil];
    /*
    2021-09-28 16:58:06.203026+0800 TestKVC[41063:6884574] willChangeValueForKey - begin
    2021-09-28 16:58:06.203196+0800 TestKVC[41063:6884574] willChangeValueForKey - end
    2021-09-28 16:58:06.203432+0800 TestKVC[41063:6884574] didChangeValueForKey - begin
    2021-09-28 16:58:06.203810+0800 TestKVC[41063:6884574]  keypath:  age
     object: <WYPerson: 0x600000590360>
     change: {
        kind = 1;
        new = 10;
        old = 0;
    }
     context: (null)
    2021-09-28 16:58:06.203898+0800 TestKVC[41063:6884574] didChangeValueForKey - end
     */
    [self.person setValue:@10 forKey:@"age"];

    NSLog(@"setValue:forKey: - end");

    /**
     2021-09-28 17:08:29.784955+0800 TestKVC[41659:6898908] willChangeValueForKey - begin
     2021-09-28 17:08:29.785021+0800 TestKVC[41659:6898908] willChangeValueForKey - end
     2021-09-28 17:08:29.785095+0800 TestKVC[41659:6898908] setNilValueForKey - age
     2021-09-28 17:08:29.785162+0800 TestKVC[41659:6898908] didChangeValueForKey - begin
     2021-09-28 17:08:29.785281+0800 TestKVC[41659:6898908]  keypath:  age
      object: <WYPerson: 0x600000330610>
      change: {
         kind = 1;
         new = 10;
         old = 10;
     }
      context: (null)
     2021-09-28 17:08:29.785354+0800 TestKVC[41659:6898908] didChangeValueForKey - end
    */

    [self.person setValue:nil forKey:@"age"];


    //   2021-09-28 17:19:55.815113+0800 TestKVC[42335:6915549] Error Domain=type exception Code=0 "(null)"
//     id number = @"";
    /*
    2021-09-28 17:21:29.732385+0800 TestKVC[42445:6918021] willChangeValueForKey - begin
    2021-09-28 17:21:29.732459+0800 TestKVC[42445:6918021] willChangeValueForKey - end
    2021-09-28 17:21:29.739948+0800 TestKVC[42445:6918021] didChangeValueForKey - begin
    2021-09-28 17:21:29.740109+0800 TestKVC[42445:6918021]  keypath:  age
     object: <WYPerson: 0x600001744160>
     change: {
        kind = 1;
        new = 20;
        old = 10;
    }
     context: (null)
    2021-09-28 17:21:29.740192+0800 TestKVC[42445:6918021] didChangeValueForKey - end
     */
//    id number = @(-5);
    /**
     2021-09-28 17:22:00.405500+0800 TestKVC[42484:6919089] willChangeValueForKey - begin
     2021-09-28 17:22:00.405570+0800 TestKVC[42484:6919089] willChangeValueForKey - end
     2021-09-28 17:22:00.413856+0800 TestKVC[42484:6919089] didChangeValueForKey - begin
     2021-09-28 17:22:00.414028+0800 TestKVC[42484:6919089]  keypath:  age
      object: <WYPerson: 0x600002b801a0>
      change: {
         kind = 1;
         new = 10;
         old = 10;
     }
      context: (null)
     2021-09-28 17:22:00.414109+0800 TestKVC[42484:6919089] didChangeValueForKey - end
     */
    id number = @10;
    NSError *error = nil;

    if([self.person validateValue:&number forKey:@"age" error:&error]) {
        [self.person setValue:number forKey:@"age"];
    } else {

        NSLog(@"%@", error);
    }
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@" keypath:  %@ \n object: %@ \n change: %@ \n context: %@", keyPath, object, change, context);
}

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

@end