iOS-KVC

本文章属于-mr_young_原创,转载请注明出处:

https://www.jianshu.com/p/23dce2ead05f

1.什么是KVC?

KVC:即key-value-coding,键值编码。

2.KVC应用场景

1> 通过键值路径可为对象的属性进行赋值,也可以设置对象的私有属性。

Person *person = [Person alloc] init];
[person setValue:@"young" forKey:@"name"];
// 如果对象的某一属性也是对象
[person setValue:@"doge" forKeyPath:@"dog.name"];

2> 通过键值路径获取对象某一属性的值,也可以获取私有属性。

NSString *pName = [person valueForKey:@"name"];
// 也可以通过kvc获取到dog的属性
NSString *dogName = [person valueForKeyPath:@"dog.name"];

3> 简单的字典-->模型

#import 

@class Dog;

@interface Person : NSObject

@property (nonatomic, assign) int p_id
@property (nonatomic, copy) NSString name;
@property (nonatomic, strong) Dog *dog;

+ (instancetype)personWithDict:(NSDictionary *)dict;
@end

#import "Person.h"
#import "Dog.h"

@implementation Person

+ (instancetype)personWithDict:(NSDictionary *)dict {
    // dict: @{@"id":@(10), @"name":@"young", @"dog":@{@"name":@"doge", @"age":@(3)}}
    Person *person = [Person alloc] init];
    [person setValuesForKeysWithDictionary:dict];
    return person;
}

// Person类中找不到key(此时找不到id)对应的属性时,调用此方法
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    if ([key isEqualToString:@"id"]) {
        self.p_id = [value intValue];
    } else {
        NSLog(@"Person找不到%@对应的属性", key);
        return nil;
    }
}

@end

4> 用KVC获取集合中的元素个数、最大值、最小值、平均值以及求和。⚠️注意:通过KVC来获取集合中元素的个数时应当使用@"@count"

#import 
#import "Person.h"
#import "Dog.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p1 = [[Person alloc] init];
        Dog *dog1 = [[Dog alloc] init];
        dog1.name = @"dog1";
        dog1.age = 3;
        p1.name = @"young";
        p1.dog = dog1;
        
        Person *p2 = [[Person alloc] init];
        Dog *dog2 = [[Dog alloc] init];
        dog2.name = @"dog2";
        dog2.age = 2;
        p2.name = @"wang";
        p2.dog = dog2;
        
        Person *p3 = [[Person alloc] init];
        Dog *dog3 = [[Dog alloc] init];
        dog3.name = @"dog3";
        dog3.age = 6;
        p3.name = @"zhang";
        p3.dog = dog3;
        
        NSArray *arr = [NSArray arrayWithObjects:p1, p2, p3, nil];
        NSLog(@"\narr.count:%@\nmaxAge:%@\nminAg:%@\navgAge:%@\nsumAge:%@",
            [arr valueForKey:@"@count"], [arr valueForKeyPath:@"@max.dog.age"], 
            [arr valueForKeyPath:@"@min.dog.age"], [arr valueForKeyPath:@"@avg.dog.age"], 
            [arr valueForKeyPath:@"@sum.dog.age"]);
    }
    return 0;
}
iOS-KVC_第1张图片
运行结果

3.手撸KVC之前

3.1 首先,我们要知道在OC中一个属性对应有四个成员变量,并且他们的优先级依次为:_key>_isKey>key>isKey

// Person.h
#import 
@interface Person : NSObject {
    NSString *_name;
    NSString *_isName;
    NSString *name;
    NSString *isName;
    
    /**
     NSString *_isName;
     NSString *name;
     NSString *isName;
     */
    
    /**
     NSString *name;
     NSString *isName;
     */
    
    /**
     NSString *isName;
     */
}
@end

// Person.m
#import "Person.h"
@implementation Person

- (instancetype)init {
    self = [super init];
    if (self) {
        _name = @"_name";
        _isName = @"_isName";
        name = @"name";
        isName = @"isName";
        // 运行结果: KVC-Test[1799:310739] name: _name
        
        /**
        // _name = @"_name";
        _isName = @"_isName";
        name = @"name";
        isName = @"isName";
        // 运行结果: KVC-Test[1799:310739] name: _isName
        
        // _name = @"_name";
        // _isName = @"_isName";
        name = @"name";
        isName = @"isName";
        // 运行结果: KVC-Test[1799:310739] name: name
        
        // _name = @"_name";
        // _isName = @"_isName";
        // name = @"name";
        isName = @"isName";
        // 运行结果: KVC-Test[1799:310739] name: isName
        */
    }
    return self;
}

@end

// main.m
#import 
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        NSLog(@"name: %@", [person valueForKey:@"name"]);
    }
    return 0;
}
@end

3.2 通过valueForKey:@"key"获取对象某一属性时:

  1. 首先,调用getter方法,一个属性对应有3个getter方法,并且它们的优先级依次为:getKey>key>isKey
// 还是用3.1的例子,重写Person类的3个getter方法
// Person.m
#import "Person.h"
@implementation Person

- (instancetype)init {
    self = [super init];
    if (self) {
        _name = @"_name";
        _isName = @"_isName";
        name = @"name";
        isName = @"isName";
    }
    return self;
}

- (NSString *)getName {
    // 运行结果:KVC-Test[2115:403089] name: getter- getName
    return @"getter- getName";
}

- (NSString *)name {
    // 注释getName方法后,运行结果:KVC-Test[2115:403089] name: getter - name
    return @"getter - name";
}

- (NSString *)isName {
    // 注释getName和name方法后,运行结果:KVC-Test[2115:403089] name: getter - isName
    return @"getter - isName";
}
@end
  1. 如果没有getter方法,则会调用NSArry的两个对象方法 - (NSInteger)countOfKey- (id)objectInKeyAtIndex:(NSInteger)index,如果实现了这两个方法则会返回一个数组。
#import "Person.h"
@implementation Person

- (NSUInteger)countOfName {
    return 10;
}

- (id)objectInNameAtIndex:(NSUInteger)index {
    return [NSString stringWithFormat:@"name-%lu", (unsigned long)index];
}
// 打印结果为
//KVC-Test[2278:424945] name: (
//                             "name-0",
//                             "name-1",
//                             "name-2",
//                             "name-3",
//                             "name-4",
//                             "name-5",
//                             "name-6",
//                             "name-7",
//                             "name-8",
//                             "name-9"
//                             )
//Program ended with exit code: 0
@end
3.2.2运行结果
  1. 如果没有NSArray的两个对象方法,则会调用类方法+(BOOL)accessInstanceVariablesDirectly。返回NO,则不会查找任何成员变量,并且报错valueForUndefinedKey:]: this class is not key value coding-compliant for the key。因此,该类方法默认返回YES。
  1. 只有+ (BOOL)accessInstanceVariablesDirectly返回YES时,KVC才会查找Person类中的4个成员变量:_key_isKeykeyisKey(_name_isNamenameisName)。它们优先级依次为_key>_isKey>key>isKey, 当找到_key时,即返回_key的值,如果找不到_key再查找_isKey...如果最后找不到isKey的成员变量时,同样报错valueForUndefinedKey:]: this class is not key value coding-compliant for the key
  1. 如果类方法+ (BOOL)accessInstanceVariablesDirectly返回NO,或最后也没又找到isKey对应的成员变量时,系统还会给我们最后一次机会防止程序崩溃:就是实现- (id)valueForUndefinedKey:(NSString *)key方法,该方法return nil;时,会防止程序崩溃。

3.3 通过setValue:@"value" ForKey:@"key"设置对象某一属性时:

  1. 首先,调用setter方法。对于一个属性,对应有2个setter方法,并且它们的优先级依次为:setKey>setIsKey
// main.m
#import 
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        [person setValue:@"young" forKey:@"name"];
    }
    return 0;
}
@end

// Person.m
#import "Person.h"

@implementation Person
- (void)setName:(NSString *)name {
    // 运行结果:KVC-Test[2315:444214] setter name - young
    NSLog(@"setter name - %@", name);
}

- (void)setIsName:(NSString *)name {
    // 注释setName方法后,运行结果:KVC-Test[2315:444214] setter isName - young
    NSLog(@"setter isName - %@", name);
}
@end
  1. 如果这个类没有实现setter方法,则系统就会调用类方法:+ (BOOL)accessInstanceVariablesDirectly默认返回YES; 如果该类方法返回值为NO,便如方法名所言,不能访问实例的所有成员变量。并且报错:setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name.'
  1. 当然,如果+ (BOOL)accessInstanceVariablesDirectly``+ (BOOL)accessInstanceVariablesDirectly类方法返回NO时,系统还给我们最后一次防止报错的机会:实现- (void)setValue:(id)value forUndefinedKey:(NSString *)key方法。
#import "Person.h"

@implementation Person
+ (BOOL)accessInstanceVariablesDirectly {
    return NO;
}

- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    // 运行结果:KVC-Test[2391:471126] Person类中没有name属性
    NSLog(@"Person类中没有%@属性", key);
}
@end

3.4 将nil值通过KVC设置对象某一属性时,该属性不能为基本数据类型,否则报错:

iOS-KVC_第2张图片
将nil赋值给int类型的age属性
如果实现了 - (void)setNilValueForKey:(NSString *)key方法,便可防止此异常的发生。

4.手撸KVC

// NSObject+MyKVX.h
#import 

@interface NSObject (MyKVC)
- (id)my_valueForKey:(NSString *)key;
- (void)my_setValue:(id)value forKey:(NSString *)key;
@end

// NSObject+MyKVX.m
#import "NSObject+MyKVC.h"
#import 

@implementation NSObject (MyKVC)

- (id)my_valueForKey:(NSString *)key {
    // key值需要合法
    if (key == nil || key.length == 0) {
        NSLog(@"key is nil!!!");
        return nil;
    }
    
    // step 1: 调用getter方法: getKey、key、getIsKey
    NSString *getKey = [NSString stringWithFormat:@"get%@", key.capitalizedString];
    // 调用getKey方法
    if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
        return [self performSelector:NSSelectorFromString(getKey)];
    }
    // 调用key方法
    if ([self respondsToSelector:NSSelectorFromString(key)]) {
        return [self performSelector:NSSelectorFromString(key)];
    }
    NSString *getIsKey = [NSString stringWithFormat:@"getIs%@", key.capitalizedString];
    if ([self respondsToSelector:NSSelectorFromString(getIsKey)]) {
        return [self performSelector:NSSelectorFromString(getIsKey)];
    }
    
    // step 2: 没有getter方法,调用countOfKey和objectInKeyAtIndex:(NSInteger)index方法
    NSString *countOfKey = [NSString stringWithFormat:@"countOf%@", key.capitalizedString];
    // 注意⚠️:objectInKeyAtIndex:(NSInteger)index有参数,方法名要包含冒号
    NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:", key.capitalizedString];
    NSUInteger keyCount = 0;
    if ([self respondsToSelector:NSSelectorFromString(countOfKey)] &&
        [self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) {
        keyCount = (NSUInteger)[self performSelector:NSSelectorFromString(countOfKey)];
        NSMutableArray *keyValues = [NSMutableArray array];
        for (int i = 0; i < keyCount; i++) {
            [keyValues addObject:[self performSelector:NSSelectorFromString(objectInKeyAtIndex) withObject:@(i)]];
        }
        return keyValues;
    }
    
    // step 3: 没有实现countOfKey和objectInKeyAtIndex方法
    // 并且 accessInstanceVariablesDirectly类方法返回NO
    if (![self.class accessInstanceVariablesDirectly]) { // 抛出异常
        NSException *exception = [NSException exceptionWithName:@"MyKVC Exception" reason:@"你不让我访问成员变量,我能怎么办!" userInfo:nil];
        @throw exception;
    }
    
    // 获取类中的所有成员变量
    unsigned int ivarCount;
    Ivar *ivars = class_copyIvarList(self.class, &ivarCount); // 拷贝一个类的成员变量列表到堆区域
    
    // step 4: 根据成员变量的优先级将获取成员变量的值
    Ivar _keyIvar = nil;
    Ivar _isKeyIvar = nil;
    Ivar keyIvar = nil;
    Ivar isKeyIvar = nil;
    for (int i = 0; i < ivarCount; i++) {
        Ivar ivar = ivars[i];
        NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
        if ([name isEqualToString:[NSString stringWithFormat:@"_%@", key]]) {
            _keyIvar = ivar;
        } else if ([name isEqualToString:[NSString stringWithFormat:@"_is%@", key.capitalizedString]]) {
            _isKeyIvar = ivar;
        } else if ([name isEqualToString:[NSString stringWithFormat:@"%@", key]]) {
            keyIvar = ivar;
        } else if ([name isEqualToString:[NSString stringWithFormat:@"is%@", key.capitalizedString]]) {
            isKeyIvar = ivar;
        }
    }
    id obj = nil;
    if (_keyIvar) {
        obj = object_getIvar(self, _keyIvar);
        free(ivars);
        return obj;
    } else if (_isKeyIvar) {
        obj = object_getIvar(self, _isKeyIvar);
        free(ivars);
        return obj;
    } else if (keyIvar) {
        obj = object_getIvar(self, keyIvar);
        free(ivars);
        return obj;
    } else if (isKeyIvar) {
        obj = object_getIvar(self, isKeyIvar);
        free(ivars);
        return obj;
    }

    // step 5: 调用valueForUndefinedKey方法
    free(ivars);
    return [self valueForUndefinedKey:key];
}

- (void)my_setValue:(id)value forKey:(NSString *)key {
    // key值需要合法
    if (key == nil || key.length == 0) {
        NSLog(@"key is nil!!!");
        return;
    }
    
    // 第一步:调用setter方法:setKey、setIsKey; 注意⚠️:setter方法有参数,所以方法名后有冒号
    // capitalizedString方法:字符串首字母大写
    NSString *setKey = [NSString stringWithFormat:@"set%@:", key.capitalizedString];
    // 调用setKey方法
    if ([self respondsToSelector:NSSelectorFromString(setKey)]) {
        [self performSelector:NSSelectorFromString(setKey) withObject:value];
        return;
    }
    // 调用setIsKey
    NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:", key.capitalizedString];
    if ([self respondsToSelector:NSSelectorFromString(setIsKey)]) {
        [self performSelector:NSSelectorFromString(setIsKey) withObject:value];
    }
    
    // 第二步:没有setter方法 并且 accessInstanceVariablesDirectly类方法返回NO
    if (![self.class accessInstanceVariablesDirectly]) { // 抛出异常
        NSException *exception = [NSException exceptionWithName:@"MyKVC Exception" reason:@"你不让我访问成员变量,我能怎么办!" userInfo:nil];
        @throw exception;
        return;
    }
    
    // 获取类中的所有成员变量
    unsigned int ivarCount;
    Ivar *ivars = class_copyIvarList(self.class, &ivarCount); // 拷贝一个类的成员变量列表到堆区域
    
    // 第三步:根据成员变量的优先级将value赋值给成员变量
    Ivar _keyIvar = nil;
    Ivar _isKeyIvar = nil;
    Ivar keyIvar = nil;
    Ivar isKeyIvar = nil;
    for (int i = 0; i < ivarCount; i++) {
        Ivar ivar = ivars[i];
        NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
        if ([name isEqualToString:[NSString stringWithFormat:@"_%@", key]]) {
            _keyIvar = ivar;
        } else if ([name isEqualToString:[NSString stringWithFormat:@"_is%@", key.capitalizedString]]) {
            _isKeyIvar = ivar;
        } else if ([name isEqualToString:[NSString stringWithFormat:@"%@", key]]) {
            keyIvar = ivar;
        } else if ([name isEqualToString:[NSString stringWithFormat:@"is%@", key.capitalizedString]]) {
            isKeyIvar = ivar;
        }
    }
    if (_keyIvar) {
        object_setIvar(self, _keyIvar, value);
        free(ivars);
        return;
    } else if (_isKeyIvar) {
        object_setIvar(self, _isKeyIvar, value);
        free(ivars);
        return;
    } else if (keyIvar) {
        object_setIvar(self, keyIvar, value);
        free(ivars);
        return;
    } else if (isKeyIvar) {
        object_setIvar(self, isKeyIvar, value);
        free(ivars);
        return;
    }
    // 异常处理方法
    [self setValue:value forUndefinedKey:key];
    free(ivars);
}

@end

你可能感兴趣的:(iOS-KVC)