三十、KVC(常用方法和简单的自定义)

784 阅读7分钟

本文由快学吧个人写作,以任何形式转载请表明原文出处。

一、KVC的常用方法

1. 基本类型

比如NSInteger、int、float之类的,用KVC的时候要转成NSNumber或者NSString。

图片.png

图片.png

图片.png

2. 集合类型

不可变数组也可以当成可变数组用。

图片.png

图片.png

图片.png

3. 非对象类型

比如结构体之类的。就要转成NSValue,不然没办法存也没办法取。

图片.png

图片.png

图片.png

4. 字典操作

字典转模型,模型转字典

图片.png

图片.png

图片.png

5. 聚合操作符

@avg: 返回操作对象指定属性的平均值

@count: 返回操作对象指定属性的个数

@max: 返回操作对象指定属性的最大值

@min: 返回操作对象指定属性的最小值

@sum: 返回操作对象指定属性值之

(1). 小写和字符串长度

小写是 : lowercaseString。字符串长度是length

图片.png

(2). 聚合操作符举例

图片.png

图片.png

图片.png

(3). 取数组中所有相同的key的value

图片.png

(4). 数组嵌套

图片.png

图片.png

(5). 集合嵌套

图片.png

图片.png

6. 对象嵌套

一个对象是另一个对象的属性,利用keyPath路由来设值和取值。

图片.png

图片.png

二、自定义KVC

根据上一章setValue:ForKey:valueForKey:的总结来完成的思路,但是只针对了简单的基本类型和数组类型,没有处理集合和其他的。思路大概就是这样的思路,写的很简单。全都测试过了,绝对能正常的使用。

1. 创建NSObject分类

.h文件如下 :

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface NSObject (JDKVC)

// 自定义setValueForKey
- (void)jd_setValue:(nullable id)value forKey:(NSString *)key;

// 
- (nullable id)jd_valueForKey:(NSString *)key;

@end

NS_ASSUME_NONNULL_END

2. 自定义

.m文件如下 :

#import "NSObject+JDKVC.h"
#import <objc/runtime.h>

@implementation NSObject (JDKVC)

/**
 自定义响应方法,找的到名字是sName的方法就调用,并把参数value传进去,返回YES。
 找不到名字是sName的方法,就直接返回NO
 */
- (BOOL)jd_performSelecorWithName:(NSString *)sName value:(id)value
{
    SEL mSEL = NSSelectorFromString(sName);
    if ([self respondsToSelector:mSEL]) {
        
        //确认不会内存泄漏就不用写下面这些宏
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        // 响应了就调用sName的方法,并把参数value传进去
        [self performSelector:mSEL withObject:value];
#pragma clang diagnostic pop
        return YES;
    }
    return NO;
}

/**
 获取调用对象的成员变量,并将成员变量的名字的字符串放入到一个数组中
 */
- (NSMutableArray *)jd_getIvarsName
{
    // 初始容量是1,超过了直接*2扩容
    NSMutableArray *ivarsMArr = [NSMutableArray arrayWithCapacity:1];
    
    // 定义数量,用于循环遍历
    unsigned int count = 0;
    
    // 获取调用对象的类的成员变量列表ivarList,成员变量的数量存入count
    Ivar *ivars = class_copyIvarList([self class], &count);
    
    // 遍历成员变量类表,并将其名字变成字符串,将字符串加入到数组中
    for (int i = 0; i < count; i++) {
        Ivar ivar = ivars[i];
        const char *ivarNC = ivar_getName(ivar);
        NSString *ivarNStr = [NSString stringWithUTF8String:ivarNC];
        NSLog(@"%@的成员变量:%@",[self class],ivarNStr);
        [ivarsMArr addObject:ivarNStr];
    }
    
    //Ivar需要手动释放
    free(ivars);
    return ivarsMArr;
}

#pragma mark - 自定义setValueForKey

- (void)jd_setValue:(nullable id)value forKey:(NSString *)key
{
    // 传入的key不能是空的
    if (!key || key.length == 0) {
        NSLog(@"传的key是空的");
        return;
    }
    
    // 第1步:
    // 先找3个setter方法:set<Key>  _set<Key>  setIs<Key>
    // 1.1 因为Key是共用的,且首字母皆要大写,所以先设置一个公共的Key,让它的首字母大写,
    //     用NSString自带的capitalizedString就可以,这个就是让字符串首字母大写
    NSString *big_key = [key stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[key substringToIndex:1].capitalizedString];
    
    // 1.2 把三个setter方法的名字先用字符串保存起来,方便使用
    NSString *setKey = [NSString stringWithFormat:@"set%@:",big_key];
    NSString *_setKey = [NSString stringWithFormat:@"_set%@:",big_key];
    NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",big_key];
    
    // 1.3 判断是否能找到3个setter方法中的任何一个,找到哪个就调用哪个
    if ([self jd_performSelecorWithName:setKey value:value]) {
        NSLog(@"找到%@方法了",setKey);
        // 找到了就不用向下继续了,已经给变量赋值了,直接return
        return;
    }
    else if ([self jd_performSelecorWithName:_setKey value:value])
    {
        NSLog(@"找到%@方法了",_setKey);
        return;
    }
    else if ([self jd_performSelecorWithName:setIsKey value:value])
    {
        NSLog(@"找到%@方法了",setIsKey);
        return;
    }
    
    // 第2步:
    // 找不到3个setter方法,查看accessInstanceVariablesDirectly方法是否返回的是YES
    if (![self.class accessInstanceVariablesDirectly]) {
        @throw [NSException exceptionWithName:@"JD_CANT_FIND_SETTER" reason:[NSString stringWithFormat:@"%@can't find three setter methods",self] userInfo:nil];
    }
    
    // 第3步 :
    // accessInstanceVariablesDirectly为YES会走到这里,间接访问成员变量,进行赋值。
    // 3.1 先要把调用者的成员变量拿到,放到一个数组中,然后判断成员变量的名字是否有如下格式,按顺序:
    // _<Key>   _is<Key>   <Key>  <isKey>
    NSMutableArray *ivarArr = [self jd_getIvarsName];
    
    // 3.2 把名字格式先定义好,方便用
    NSString *_key = [NSString stringWithFormat:@"_%@",key];
    NSString *_iskey = [NSString stringWithFormat:@"_is%@",big_key];
    NSString *isKey = [NSString stringWithFormat:@"is%@",big_key];
    
    // 3.3 判断成员变量列表中是否有这四种格式的key
    //     哪个先有,哪个就被赋值
    if ([ivarArr containsObject:_key]) {
        NSLog(@"是%@",_key);
        // 拿到对应的成员变量
        Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
        // 给对应的成员变量赋值
        object_setIvar(self, ivar, value);
        // 赋值到了就return,不用管其他的
        return;
    }
    else if ([ivarArr containsObject:_iskey])
    {
        NSLog(@"是%@",_iskey);
        Ivar ivar = class_getInstanceVariable([self class], _iskey.UTF8String);
        object_setIvar(self, ivar, value);
        return;
    }
    else if ([ivarArr containsObject:key])
    {
        NSLog(@"是%@",key);
        Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
        object_setIvar(self, ivar, value);
        return;
    }
    else if ([ivarArr containsObject:isKey])
    {
        NSLog(@"是%@",isKey);
        Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
        object_setIvar(self, ivar, value);
        return;
    }
    
    [self jd_setValue:value ForUndefinedKey:key];
    
}

- (void)jd_setValue:(id)value ForUndefinedKey:(NSString *)key
{
    
    // 如果实现了系统的setValue:forUndefinedKey:,就不崩溃
    NSString *undefineMN = @"setValue:forUndefinedKey:";
    IMP undefineIMP = class_getMethodImplementation([self class], NSSelectorFromString(undefineMN));
    if (undefineIMP) {
        NSLog(@"调用系统的");
        [self jd_performSelecorWithName:undefineMN value:value];
    }
    else{
        // 如果没实现,就抛出异常
        @throw [NSException exceptionWithName:@"JD_CANT_FIND_IVARS" reason:[NSString stringWithFormat:@"%@ can find four ivars",self] userInfo:nil];
    }
}

#pragma mark - 自定义valueForKey

- (nullable id)jd_valueForKey:(NSString *)key
{
    
    // 传入的key不可以是空的
    if (!key || key.length == 0) {
        NSLog(@"key不能是空的");
        return nil;
    }
    
    // 第1步 :
    // 查找getter的四个方法 : get<Key>  <Key>  is<Key>  _<Key>
    // 拿到首字母大写的key
    NSString *big_key = [key stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[key substringToIndex:1].capitalizedString];
    
    NSString *getKey = [NSString stringWithFormat:@"get%@",big_key];
    NSString *isKey = [NSString stringWithFormat:@"is%@",big_key];
    NSString *_Key = [NSString stringWithFormat:@"_%@",key];
    
    SEL getKeySEL = NSSelectorFromString(getKey);
    SEL keySEL = NSSelectorFromString(key);
    SEL isKeySEL = NSSelectorFromString(isKey);
    SEL _KeySEL = NSSelectorFromString(_Key);
    
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    if ([self respondsToSelector:getKeySEL])
    {
        return [self performSelector:getKeySEL];
    }
    else if ([self respondsToSelector:keySEL])
    {
        return [self performSelector:keySEL];
    }
    else if ([self respondsToSelector:isKeySEL])
    {
        return [self performSelector:isKeySEL];
    }
    else if ([self respondsToSelector:_KeySEL])
    {
        return [self performSelector:_KeySEL];
    }
    
    // 第2步 :
    // 数组的处理
    NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",big_key];
    NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",big_key];
    
    SEL countOfKeySEL = NSSelectorFromString(countOfKey);
    SEL objectInKeyAtIndexSEL = NSSelectorFromString(objectInKeyAtIndex);
    
    // countOfKeySEL是必须实现的,否则另外两个关于数组的方法没用
    // 不实现就直接走到下个流程
    if ([self respondsToSelector:countOfKeySEL]) {
        // objectInKeyAtIndex对应的是objectAtIndex
        // 这是更常使用的,所以这里只判断是否实现了这个,对另一个
        // 数组的方法,暂时不做处理,因为不常用
        if ([self respondsToSelector:objectInKeyAtIndexSEL]) {
            // 定义一个数组,存储遍历出来的数组元素
            NSMutableArray *mArr = [NSMutableArray arrayWithCapacity:1];
            // 拿到数组元素的数量
            int num = (int)[self performSelector:countOfKeySEL];
            for (int j = 0; j < num; j++) {
                // 这就是数组里面的每个元素
                id tem = [self performSelector:objectInKeyAtIndexSEL withObject:[NSString stringWithFormat:@"%d",j]];
                [mArr addObject:tem];
            }
            return mArr;
        }
    }
#pragma clang diagnostic pop
    // accessInstanceVariablesDirectly如果不是YES
    if (![self.class accessInstanceVariablesDirectly]) {
        @throw [NSException exceptionWithName:@"JD_CANT_FIND_GETTER" reason:[NSString stringWithFormat:@"%@can't find four getter methods",self] userInfo:nil];
    }

    // 第3步 :
    // 直接从成员变量中拿值,_<Key>  _is<Key>  <Key>  is<Key>
    NSMutableArray *ivarMutArr = [self jd_getIvarsName];
    NSString *_isKey = [NSString stringWithFormat:@"_is%@",big_key];
    
    if ([ivarMutArr containsObject:_Key]) {
        Ivar ivar = class_getInstanceVariable([self class], _Key.UTF8String);
        return object_getIvar(self, ivar);
    }
    else if ([ivarMutArr containsObject:_isKey])
    {
        Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
        return object_getIvar(self, ivar);
    }
    else if ([ivarMutArr containsObject:key])
    {
        Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
        return object_getIvar(self, ivar);
    }
    else if ([ivarMutArr containsObject:isKey])
    {
        Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
        return object_getIvar(self, ivar);
    }
    
    // 没有设置报错,直接返回了一个固定的字符串,报错和上面的赋值一样
    return  @"啥也没有";
}

@end

三、KVC的一些异常监听

1. 设置空值

当给某个成员变量设置的value是nil的时候,可以通过setNilValueForKey:方法来监听处理。

不过setNilValueForKey:只能监听到NSNumber或者NSValue类型的空值设定。

如下图,JDMan.h和JDMan.m :

图片.png

VC中用KVC设置JDMan的所有成员变量都为nil :

图片.png

运行结果 :

图片.png

setNilValueForKey:只能监听类型为NSNumber或者NSValue的变量设置为nil。

2. UndefinedKey

在JDMan.m中重写它们的实现。可以保证设置的key是未知的时候不崩溃。

图片.png