OC之自定义KVC

332 阅读6分钟

和谐学习!不急不躁!!我是你们的老朋友小青龙~

承接文章:OC之Method_Swizling一些坑点、KVC原理分析

前言

上篇文章,我们对KVC的原理进行了分析,本文我将和大家一起自定义实现一个独属于我们自己的KVC。我仿佛已经听到了你们的呐喊:赶紧嘚,别墨迹~

正文

炒冷饭:

// Setter原理:
1. 依次查找`set<Key>:``_set<Key>`,找到了就调用方法;找不到就进入步骤22、先确定AccessInstanceVariables返回YES,然后依次查找`_<key>`, `_is<Key>`, `<key>`, 或`is<Key>`,找到了就用输入值设置变量。找不到就进入步骤3;
(比如找到了查找`_<key>`,那么后面的`_is<Key>`等就不需要找了)。

3、调用`setValue:forUndefinedKey:`并引发`异常`// Getter原理:
1、查找 get<Key>, <key>, is<Key>, or _<key>,找到了就进入步骤5;找不到就进入步骤22、在实例方法中搜索:countOf<Key>和objectIn<Key>AtIndex和<key>AtIndexes:,
   countOf<Key>必须实现,另外两个找到了其中一个,就创建集合代理对象,找不到就进入步骤33、改为搜索countOf<Key>、Enumeratorf<Key>和memberOf<Key>:,3个方法都存在才行,否则就进入步骤44、当确定AccessInstanceVariables方法返回YES(默认也是YES),
   顺序搜索名为 _<key>, _is<Key>, <key>, 或is<Key>的实例变量。
   如果找到,直接获取实例变量的值并继续执行步骤5。否则,继续执行步骤65、如果检索到的属性值是对象指针,只需返回结果。
   如果该值是NSNumber支持的标量类型,请将其存储在NSNumber实例中并返回该值。
   如果结果是NSNumber不支持的标量类型,请转换为NSValue对象并返回该对象。

6、如果都找不到,调用valueForUndefinedKey:并抛出异常。

参考以上原理,我们给NSObject添加一个NSObject+SSJKVC分类

// NSObject+SSJKVC.h

@interface NSObject (SSJKVC)
- (void)ssj_setValue:(nullable id)value forKey:(NSString *)key;
@end

NSObject+SSJKVC.m实现部分,我们可以根据前面原理整体得到如下伪代码:

// NSObject+SSJKVC.m

#import "NSObject+SSJKVC.h"
#import <objc/runtime.h>
@implementation NSObject (SSJKVC)
#pragma mark -- Setter
- (void)ssj_setValue:(nullable id)value forKey:(NSString *)key{
    /***
     -> key 非空判断
     
     步骤1、 依次查找`set<Key>:` 或 `_set<Key>`,找到了就调用方法;找不到就进入步骤2;

     步骤2、先确定AccessInstanceVariables返回YES,
         然后从实例变量列表依次查找`_<key>`, `_is<Key>`, `<key>`, 
         或`is<Key>`,找到了就用输入值设置变量。找不到就进入步骤3;
     (比如找到了`_<key>`,那么后面的`_is<Key>`等就不需要找了)。

     步骤3、抛出异常`。
     
     */
}

#pragma mark -- getter
- (nullable id)valueForKey:(NSString *)key{
    /***
     -> key 非空判断
     步骤1、查找 get<Key>, <key>, is<Key>, or _<key>,找到了就进入步骤5;找不到就进入步骤2;

     步骤2、在实例中搜索:countOf<Key>和objectIn<Key>AtIndex和<Key>AtIndexes:,
        countOf<Key>必须实现,另外两个找到了其中一个,就创建集合代理对象,找不到就进入步骤3;

     步骤3、判断能否给实例变量赋值,不能就抛出异常,能就进入步骤4;

     步骤4、
        顺序搜索名为 _<key>, _is<Key>, <key>, 或is<Key>的实例变量。
         取出对应实例变量的值并返回,否则返回空字符串
     */
}

ssj_setValue具体实现:

- (void)ssj_setValue:(nullable id)value forKey:(NSString *)key{
    /***
     -> key 非空判断
     
     步骤1、 依次查找`set<Key>:` 或 `_set<Key>`,找到了就调用方法;找不到就进入步骤2;

     步骤2、先确定AccessInstanceVariables返回YES,然后从实例变量列表依次查找`_<key>`, `_is<Key>`, `<key>`, 或`is<Key>`,找到了就用输入值设置变量。找不到就进入步骤3;
     (比如找到了`_<key>`,那么后面的`_is<Key>`等就不需要找了)。

     步骤3、抛出异常`。
     
     */
    NSLog(@"欢迎使用NSObject+SSJKVC");
    if (key) {
        NSString *bigDealWithKey = [self smallToBigWithFirstCharFrom:key];//首字母大写
        // ***************************** 步骤1  ****************************
        NSString *selNameOne = [NSString stringWithFormat:@"set%@:",bigDealWithKey];
        NSString *selNameTwo = [NSString stringWithFormat:@"_set%@:",bigDealWithKey];
        BOOL realizationSelOne= [self respondsToSelector:NSSelectorFromString(selNameOne)];
        BOOL realizationSelTwo= [self respondsToSelector:NSSelectorFromString(selNameTwo)];
        
        /// 去掉调用performSelector产生的警告
            #pragma clang diagnostic push
            #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        
        /// 查找`set<Key>:`
        if (realizationSelOne) {
            [self performSelector:NSSelectorFromString(selNameOne) withObject:value];
            /// 找到这个方法,后面的不需要执行
            return;
        }
        /// 查找`_set<Key>`
        if (realizationSelTwo) {
            [self performSelector:NSSelectorFromString(selNameTwo) withObject:value];
            /// 找到这个方法,后面的不需要执行
            return;
        }
        
        // ***************************** 步骤2  ****************************
        /// 先确定AccessInstanceVariables返回YES
        if ([self.class accessInstanceVariablesDirectly]) {
            //依次查找`_<key>`, `_is<Key>`, `<key>`或`is<Key>`
            NSString *_key = [@"_" stringByAppendingString:key];
            NSString *_isKey = [@"_is" stringByAppendingString:bigDealWithKey];
            NSString *isKey = [@"is" stringByAppendingString:bigDealWithKey];
            
            NSMutableArray *ar = [self getIvarListName];//得到所有实例变量
            if ([ar containsObject:_key]) {//_key与ar匹配
                [self ssj_changeIvar:_key value:value]; //给实例变量赋值
                return;
            }
            if ([ar containsObject:_isKey]) {
                [self ssj_changeIvar:_isKey value:value];
                return;
            }
            if ([ar containsObject:key]) {
                [self ssj_changeIvar:key value:value];
                return;
            }
            if ([ar containsObject:isKey]) {
                [self ssj_changeIvar:isKey value:value];
                return;
            }
        }
    }
    // ***************************** 步骤3  ****************************
    /// 抛出异常
    [self throwSetterException];
}

重写valueForKey

- (nullable id)valueForKey:(NSString *)key{
    /***
     -> key 非空判断
     步骤1、查找 get<Key>, <key>, is<Key>, or _<key>,找到了就进入步骤5;找不到就进入步骤2;

     步骤2、在实例中搜索:countOf<Key>和objectIn<Key>AtIndex和<Key>AtIndexes:,
        countOf<Key>必须实现,另外两个找到了其中一个,就创建集合代理对象,找不到就进入步骤3;

     步骤3、判断能否给实例变量赋值,不能就抛出异常,能就进入步骤4;

     步骤4、
        顺序搜索名为 _<key>, _is<Key>, <key>, 或is<Key>的实例变量。
         取出对应实例变量的值并返回,否则返回空字符串
     */
    if (key) {
        NSString *bigDealWithKey = [self smallToBigWithFirstCharFrom:key];//首字母大写
        
        // ***************************** 步骤1  ****************************
        NSString *getKey = [NSString stringWithFormat:@"get%@",bigDealWithKey];
        NSString *_key = [NSString stringWithFormat:@"_%@",key];
        BOOL realization_getKey= [self respondsToSelector:NSSelectorFromString(getKey)];
        BOOL realization_key= [self respondsToSelector:NSSelectorFromString(key)];
        BOOL realization__key= [self respondsToSelector:NSSelectorFromString(_key)];
        
        /// 去掉调用performSelector产生的警告
            #pragma clang diagnostic push
            #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        
        /// 查找`set<Key>:`
        if (realization_getKey) {
            return [self performSelector:NSSelectorFromString(getKey) withObject:key];
        }
        if (realization_key) {
            return [self performSelector:NSSelectorFromString(key) withObject:key];
        }
        if (realization__key) {
            return [self performSelector:NSSelectorFromString(_key) withObject:key];
        }
        
        // ***************************** 步骤2  ****************************
        /// countOf<Key>和objectIn<Key>AtIndex:和<key>AtIndexes:
        NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",bigDealWithKey];
        NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",bigDealWithKey];
        NSString *keyAtIndexes = [NSString stringWithFormat:@"%@AtIndexes:",key];
        BOOL realization_countOfkey = [self respondsToSelector:NSSelectorFromString(countOfKey)];
        BOOL realization_objectInKeyAtIndex = [self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)];
        BOOL realization_keyAtIndexes = [self respondsToSelector:NSSelectorFromString(keyAtIndexes)];
        // countOf<Key>必须执行,objectIn<Key>AtIndex:和<key>AtIndexes: 二选一
        if (realization_countOfkey) {
            if (realization_objectInKeyAtIndex) {
                return [self performSelector:NSSelectorFromString(objectInKeyAtIndex)];
            }else{
                if (realization_keyAtIndexes) {
                    return [self performSelector:NSSelectorFromString(keyAtIndexes)];
                }
            }
        }
        
        // ***************************** 步骤3  ****************************
        if (![self.class accessInstanceVariablesDirectly] ) {
            @throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
        }
        
        // ***************************** 步骤4  ****************************
        /// _<key>, _is<Key>, <key>, 或is<Key>的实例变量。
        NSMutableArray *ar = [self getIvarListName];//得到所有实例变量
        NSString *isKey = [NSString stringWithFormat:@"is%@",bigDealWithKey];
        NSString *_isKey = [NSString stringWithFormat:@"_is%@",bigDealWithKey];
        
        if ([ar containsObject:_key]) {//_key与ar匹配
            NSLog(@"ssj_IvarValue---%@",[self ssj_IvarValue:_key]);
            return [self ssj_IvarValue:_key]; //给实例变量赋值
        }else if ([ar containsObject:_isKey]) {
            NSLog(@"ssj_IvarValue---%@",[self ssj_IvarValue:_isKey]);
            return [self ssj_IvarValue:_isKey];
        }else if ([ar containsObject:key]) {
            NSLog(@"ssj_IvarValue---%@",[self ssj_IvarValue:key]);
            return [self ssj_IvarValue:key];
        }else if ([ar containsObject:isKey]) {
            NSLog(@"ssj_IvarValue---%@",[self ssj_IvarValue:isKey]);
            return [self ssj_IvarValue:isKey];
        }
    }
    return @"";
}

其它公共方法

- (void)throwSetterException{
    NSLog(@"setter 抛出异常");
    @throw [NSException exceptionWithName:@"SSJ_UnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key name.****",self,NSStringFromSelector(_cmd)] userInfo:nil];
}

/// 将参数string第一个字母转化为大写
/// @param string 需要处理的字符
/// return 返回处理好的字符串
- (NSString *)smallToBigWithFirstCharFrom:(NSString *)string{
    if (string) {
        if (string.length == 1) {//如果长度为1
            return string.uppercaseString;
        }else if (string.length > 1) {
            // 获取第一个转化为大写字母,然后拼接剩下的内容,然后return
            NSString *firstChar = [string substringToIndex:1];
            NSString *atferDeal = firstChar.uppercaseString;
            NSString *endStr = [string substringFromIndex:1];
            return [NSString stringWithFormat:@"%@%@",atferDeal,endStr];
        }else{
            return @"";
        }
    }
    return nil;
}

/// 给实例变量赋值
- (void)ssj_changeIvar:(NSString *)key value:(NSString *)value{
    //  获取相应的 ivar
   Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
    //  对相应的 ivar 设置值
   object_setIvar(self , ivar, value);
}

/// 获取实例变量的值
- (id)ssj_IvarValue:(NSString *)key{
    //  获取相应的 ivar
   Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
    //  对相应的 ivar 设置值
    return object_getIvar(self, ivar);
}

/// 获取所有实例变量,返回数组
- (NSMutableArray *)getIvarListName{
    
    NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([self class], &count);
    for (int i = 0; i<count; i++) {
        Ivar ivar = ivars[i];
        const char *ivarNameChar = ivar_getName(ivar);
        NSString *ivarName = [NSString stringWithUTF8String:ivarNameChar];
//        NSLog(@"ivarName == %@",ivarName);
        [mArray addObject:ivarName];
    }
    free(ivars);
    return mArray;
}

测试

// SSJPerson
@interface SSJPerson : NSObject{
    @public
    NSString *boddy;
    NSString *_boddy;
    NSString *isBoddy;
    NSString *_isBoddy;
}
@end

@implementation SSJPerson
@end

运行:

image.png

代码

百度网盘
链接:pan.baidu.com/s/1W0XleXhK…
密码:82ur

废话连篇

每一次的探索都是一次成长。
只有通过不断的学习,才能让我们变得越来越优秀。
欢迎大家在评论区留言~