美文网首页
Runtime梳理(四)KVC原理

Runtime梳理(四)KVC原理

作者: 飞奔的小鲨鱼 | 来源:发表于2018-11-20 18:05 被阅读0次

    首先,引用官方文档的一个例子,说明一下runtime和kvc之间的联系:

    @interface BankAccount : NSObject
    @property (nonatomic) NSNumber* currentBalance;              // An attribute
    @property (nonatomic) Person* owner;                         // A to-one relation
    @property (nonatomic) NSArray< Transaction* >* transactions; // A to-many relation
    @end
    
    [myAccount setValue:@(100.0) forKey:@"currentBalance"];
    

    In fact, you can set all the properties of the myAccount object with the same method, using different key parameters. Because the parameter is a string, it can be a variable that is manipulated at run-time.

    可以通过设置不同的参数,用相同的方法为myAccount所有的属性赋值。因为参数是字符串,但是在运行时可以成为操作的变量。

    这里对KVC不作过多的介绍,引用文档中的一段话

    Key-value coding is a mechanism enabled by the NSKeyValueCoding informal protocol that objects adopt to provide indirect access to their properties. When an object is key-value coding compliant, its properties are addressable via string parameters through a concise, uniform messaging interface. This indirect access mechanism supplements the direct access afforded by instance variables and their associated accessor methods.

    在我们的实际应用中,下面的这4个方法是我们经常用到的:

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

    但是有没有想过KVC是这么通过key来完成赋值的呢?其实在文档中已经给出了我们答案:

    1. Look for the first accessor named set<Key>: or _set<Key>, in that order. If found, invoke it with the input value (or unwrapped value, as needed) and finish.
    2. If no simple accessor is found, and if the class method accessInstanceVariablesDirectly returns YES, look for an instance variable with a name like _<key>, _is<Key>, <key>, or is<Key>, in that order. If found, set the variable directly with the input value (or unwrapped value) and finish.
    3. Upon finding no accessor or instance variable, invoke setValue:forUndefinedKey:. This raises an exception by default, but a subclass of NSObject may provide key-specific behavior.

    看下面的图可能会有更加直观的认识:

    1.png

    实际开发中的应用:
    1.动态的设值和取值

    2.修改系统控价的隐藏属性
    ex:修改UITextField占位文字的颜色和字体大小

     [self setValue:[UIColor grayColor] forKeyPath:@"_placeholderLabel.textColor"];
     [self setValue:[UIFont systemFontOfSize:15] forKeyPath:@"_placeholderLabel.font"];
    

    3.字典转模型的使用
    在转模型之前先来一个生成模型属性的方法:

    - (void)propertyWithDict:(NSDictionary *)dict{
        if (![dict isKindOfClass:[NSDictionary class]]) {
            NSLog(@"param type is not NSDictionary!");
            return;
        }
        NSMutableString * mString = [NSMutableString string];
        [dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull value, BOOL * _Nonnull stop) {
            NSMutableString * property = [NSMutableString stringWithFormat:@"%@",@""];
            if ([value isKindOfClass:NSClassFromString(@"__NSCFString")] ||
                [value isKindOfClass:NSClassFromString(@"NSTaggedPointerString")]) {
                [property appendString:[NSString stringWithFormat:@"@property (nonatomic, copy)   NSString *%@;",key]];
            }
            else if ([value isKindOfClass:NSClassFromString(@"__NSCFNumber")]){
                NSScanner * scan = [NSScanner scannerWithString:[NSString stringWithFormat:@"%@",value]];
                int val1;
                float val2;
                if ([scan scanInt:&val1] && [scan isAtEnd]) {
                    [property appendString:[NSString stringWithFormat:@"@property (nonatomic, assign) NSInteger %@;",key]];
                }
                if ([scan scanFloat:&val2] && [scan isAtEnd]) {
                    [property appendString:[NSString stringWithFormat:@"@property (nonatomic, assign) float %@;",key]];
                }
            }
            else if ([value isKindOfClass:NSClassFromString(@"__NSCFArray")]){
                [property appendString:[NSString stringWithFormat:@"@property (nonatomic, strong) NSArray *%@;",key]];
            }
            else if ([value isKindOfClass:NSClassFromString(@"__NSCFDictionary")]){
                [property appendString:[NSString stringWithFormat:@"@property (nonatomic, strong) NSDictionary *%@;",key]];
            }
            else if ([value isKindOfClass:NSClassFromString(@"__NSCFBoolean")]){
                [property appendString:[NSString stringWithFormat:@"@property (nonatomic, assign) BOOL %@;",key]];
            }
            else{
                [property appendString:[NSString stringWithFormat:@"%@ --- %@",key,value]];
            }
            [mString appendFormat:@"\n%@\n",property];
            
        }];
        NSLog(@"------生成模型的字符串 mString ----- %@",mString);
    }
    
    

    这样我们就可以做转换工作了,可以通过setValuesForKeysWithDictionary:方法来做转换,但是这样做的前提是保证dict里面的key和model的属性一一对应,不然的话,须重写setValue:forUndefinedKey:来防止crash。这种方式是遍历dict里面的key去给model赋值,我们可以换一种思路,反过来遍历model里面的属性,从dict里面渠道对应的key的值。

    字典转模型的方法,为NSObject添加一个分类:

    #import "NSObject+Model.h"
    #import <objc/runtime.h>
    @implementation NSObject (Model)
    + (instancetype)modelWithDictionary:(NSDictionary *)dict{
        id model = [[self alloc] init];
        unsigned int ivarCount;
        Ivar * ivarList = class_copyIvarList([self class], &ivarCount);
        for (int i = 0; i < ivarCount; i++) {
            NSString * propertyName = [NSString stringWithUTF8String:ivar_getName(ivarList[i])];
            NSString * propertyType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivarList[i])];
            // 去掉_
            NSString * key = [propertyName substringFromIndex:1];
            id value = dict[key];
            // videoTopic , @" @\"propertyType\" "
            if ([value isKindOfClass:[NSDictionary class]] && ![propertyType isEqualToString:@"NSDictionary"]) {
                NSRange range = [propertyType rangeOfString:@"\""];
                /** propertyType\ */
                propertyType = [propertyType substringFromIndex:range.location + range.length];
                range = [propertyType rangeOfString:@"\""];
                // propertyType
                propertyType = [propertyType substringToIndex:range.location];
                Class cls = NSClassFromString(propertyType);
                if (cls) {
                   value = [cls modelWithDictionary:value];
                }
            }
            // 数组里面元素需要转换成模型情况
            if([value isKindOfClass:[NSArray class]]){
                if ([self respondsToSelector:@selector(wsy_objectInArray)]) {
                    // 取出字典中的模型类型
                    NSString * modelStr = [self wsy_objectInArray][key];
                    Class modelClass = NSClassFromString(modelStr);
                    if (modelClass) {
                        NSMutableArray * temp = [NSMutableArray array];
                        for (NSDictionary * dic in value) {
                            id mod = [modelClass modelWithDictionary:dic];
                            [temp addObject:mod];
                        }
                        value = temp;
                    }
                }
            }
            if (value) {
                [model setValue:value forKey:propertyName];
            }
        }
        return model;
    }
    @end
    
    // 数组中需要转换模型的方法,key:为数组名,value:模型类型。
    + (NSDictionary *)wsy_objectInArray{
        return @{
                @"videoTag":@"VideoTopic"
               };
    }
    
    

    至此,我们字典转模型的简单操作就算告一段落了。。。
    官方文档

    相关文章

      网友评论

          本文标题:Runtime梳理(四)KVC原理

          本文链接:https://www.haomeiwen.com/subject/drxafqtx.html