iOS进阶_KVC(&KVC赋值取值过程分析&KVC自定义&异常处理)

KVC(Key-value coding)

键值编码

基本使用

  1. 能够对对象的私有成员进行取值赋值
  2. 对数值和结构体型的属性进行的打包解包处理

实例: WTPerson.h

#import <Foundation/Foundation.h>

@interface WTPerson : NSObject{
//    @public  //@protect默认
    NSString * _name;
}

/** name  **/
//@property(nonatomic,strong)NSString * name;

@end

ViewController.m

#import "ViewController.h"
#import "WTPerson.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *text;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    WTPerson * p = [WTPerson new];
    //访问成员变量
    //p.name = @"wt";
    //NSLog(@"%@",p.name);

    //访问私有变量(必须要要设置为public才可访问)
    //p->_name = @"wt";
    //NSLog(@"%@",p->_name);

    //KVC(即使不用public修饰,也可以访问私有变量)
    [p setValue:@"wt" forKey:@"name"];
    NSLog(@"%@",[p valueForKey:@"name"]);

    [self.text setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
}

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:642 363 427不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!

KVC赋值取值过程分析和自定义及异常处理

赋值过程

  • 1、先找相关方法set<Key>; _set<Key>; setIs<Key>;
  • 2、若是没有相关方法+(BOOL)accessInstanceVariablesDirectly判断是否可以直接访问成员变量
  • 3、如果判断NO,直接执行KVC的setValue:forUndefinedKey:(系统抛出一个异常,未定义key)
  • 4、如果是YES,继续找相关变量_<key> _is<Key> <key> is<Key>
  • 5、方法或成员都不存在,setValue:forUndefinedKey:方法默认是抛出异常

实例验证

WTPerson.h

#import <Foundation/Foundation.h>
@interface WTPerson : NSObject{
    @public  //@protect默认
    NSString * _name;
    NSString * _isName;
    NSString * name;
    NSString * isName;
}
@end

WTPerson.m

#import "WTPerson.h"
@implementation WTPerson

-(void)setName:(NSString *)name{
    NSLog(@"%s",__func__);
}

-(void)_setName:(NSString *)name{
    NSLog(@"%s",__func__);
}

-(void)setIsName:(NSString *)name{
    NSLog(@"%s",__func__);
}
@end

ViewController.m

#import "ViewController.h"
#import "WTPerson.h"

@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    WTPerson * p = [WTPerson new]; 
    //验证KVC赋值过程
    [p setValue:@"wt" forKey:@"name"];

    NSLog(@"name = %@",p->name);
    NSLog(@"_name = %@",p->_name);
    NSLog(@"isname = %@",p->isName);
    NSLog(@"_isname = %@",p->_isName);
}

@end
  • 运行程序,我们把WTPerson.m中的-(void)setName:(NSString *)name-(void)_setName:(NSString *)name-(void)setIsName:(NSString *)name三个方法依次注释,我们发现三个方法都会被依次执行。
  • 然后我们把WTPerson.h中的NSString * _name;NSString * _isName;NSString * name;NSString * isName;依次注释,我们会发现4个属性依次被赋值。

WTPerson.m中我们让accessInstanceVariablesDirectly返回NO,则程序直接崩溃。

+ (BOOL)accessInstanceVariablesDirectly{
    return NO;
}

取值过程

  • 1、先找相关方法get<Key>,key
  • 2、若没有相关方法,+(BOOL)accessInstanceVariabkesDirectly判断是否可以直接访问成员变量
  • 3、如果是NO,直接执行KVC的valueForUndefinedKey:(系统抛出一个异常,未定义key)
  • 4、如果是YES,继续找相关变量_<key>、_is<Key>、<key>、is<Key>
  • 5、方法或成员都不存在,valueForUndefineKey:方法,默认是抛出异常

实例验证

WTPerson.m

#import "WTPerson.h"

@implementation WTPerson

//- (NSString*) getName{
//    NSLog(@"%s",__func__);
//    return @"getName";
//}

- (NSString*) name {
    return @"name";
}

//+ (BOOL)accessInstanceVariablesDirectly{
//    return NO;
//}
@end

ViewController.m

#import "ViewController.h"
#import "WTPerson.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *text;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    WTPerson * p = [WTPerson new];

    //验证KVC取值过程
    NSLog(@"name = %@",[p valueForKey:@"name"]);
}

@end

取值方式与赋值方式大致相同。

KVC自定义

自定义KVC代码实现

创建分类NSObject+KVC

NSObject+KVC.h

#import <Foundation/Foundation.h>

@interface NSObject (KVC)

- (void)wt_setValue:(nullable id)value forKey:(NSString *)key;

- (id)wt_valueForKey:(NSString *)key;

@end

NSObject+KVC.m

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

@implementation NSObject (KVC)

- (id)wt_valueForKey:(NSString *)key{
    //判断是否合法
    if (key == nil && key.length ==0) {
        return nil;
    }

    //Key
    NSString * Key = key.capitalizedString;

    //先找相关方法 get<Key>,key
    NSString * getKey = [NSString stringWithFormat:@"get%@:",Key];

    if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
        return [self performSelector:NSSelectorFromString(getKey)];
    }

    if ([self respondsToSelector:NSSelectorFromString(key)]) {
        return [self performSelector:NSSelectorFromString(key)];
    }

    if (![self.class accessInstanceVariablesDirectly]) {
        NSException * exception = [NSException exceptionWithName:@"NSUnknownKeyException" reason:@"setValue:forUndefineKey" userInfo:nil];
        @throw exception;
    }

    //再找相关变量
    //获取所有的成员变量
    unsigned int count = 0;
    Ivar * ivars = class_copyIvarList([self class], &count);
    NSMutableArray * arr = [[NSMutableArray alloc]init];
    for (int i = 0; i<count; i++) {
        Ivar var = ivars[i];
        const char * varName = ivar_getName(var);
        NSString *name = [NSString stringWithUTF8String:varName];
        [arr addObject:name];
    }

    //_<key> _is<Key> <key> is<Key>
    for (int i = 0; i < count; i++) {
        NSString *keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"_%@",key]]) {

            return object_getIvar(self, ivars[i]);
        }
    }

    for (int i = 0; i < count; i++) {
        NSString *keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"_is%@",Key]]) {

            return object_getIvar(self, ivars[i]);
        }
    }

    for (int i = 0; i < count; i++) {
        NSString *keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"%@",key]]) {

            return object_getIvar(self, ivars[i]);
        }
    }

    for (int i = 0; i < count; i++) {
        NSString *keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"is%@",Key]]) {
            return object_getIvar(self, ivars[i]);
        }
    }
    free(ivars);
    return nil;
}

- (void)wt_setValue:(nullable id)value forKey:(NSString *)key{

    //判断是否合法
    if (key == nil && key.length ==0) {
        return;
    }

    //Key
    NSString * Key = key.capitalizedString;

    //先找相关方法 set<Key>; _set<Key>; setIs<Key>;
    NSString * setKey = [NSString stringWithFormat:@"set%@:",Key];

    if ([self respondsToSelector:NSSelectorFromString(setKey)]) {
        [self performSelector:NSSelectorFromString(setKey) withObject:value];
        return;
    }

    NSString * _setKey = [NSString stringWithFormat:@"_set%@:",Key];

    if ([self respondsToSelector:NSSelectorFromString(_setKey)]) {
        [self performSelector:NSSelectorFromString(_setKey) withObject:value];
        return;
    }

    NSString * setIsKey = [NSString stringWithFormat:@"setIs%@:",Key];
    if ([self respondsToSelector:NSSelectorFromString(setIsKey)]) {
        [self performSelector:NSSelectorFromString(setIsKey) withObject:value];
        return;
    }

    if (![self.class accessInstanceVariablesDirectly]) {
        NSException * exception = [NSException exceptionWithName:@"NSUnknownKeyException" reason:@"setValue:forUndefineKey" userInfo:nil];
        @throw exception;
    }

    //再找相关变量
    //获取所有的成员变量
    unsigned int count = 0;
    Ivar * ivars = class_copyIvarList([self class], &count);
    NSMutableArray * arr = [[NSMutableArray alloc]init];
    for (int i = 0; i<count; i++) {
        Ivar var = ivars[i];
        const char * varName = ivar_getName(var);
        NSString *name = [NSString stringWithUTF8String:varName];
        [arr addObject:name];
    }

    //_<key> _is<Key> <key> is<Key>
    for (int i = 0; i < count; i++) {
        NSString *keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"_%@",key]]) {
            object_setIvar(self, ivars[i], value);
            free(ivars);
            return;
        }
    }

    for (int i = 0; i < count; i++) {
        NSString *keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"_is%@",Key]]) {
            object_setIvar(self, ivars[i], value);
            free(ivars);
            return;
        }
    }

    for (int i = 0; i < count; i++) {
        NSString *keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"%@",key]]) {
            object_setIvar(self, ivars[i], value);
            free(ivars);
            return;
        }
    }

    for (int i = 0; i < count; i++) {
        NSString *keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"is%@",Key]]) {
            object_setIvar(self, ivars[i], value);
            free(ivars);
            return;
        }
    }

    [self setValue:value forUndefinedKey:Key];
    free(ivars);
}
@end

验证

ViewController.m

#import "ViewController.h"
#import "WTPerson.h"
#import "NSObject+KVC.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    WTPerson * p =[WTPerson new];
    [p wt_setValue:@"wt" forKey:@"name"];

    NSLog(@"name-KVC = %@",[p wt_valueForKey:@"name"]);
    NSLog(@"_name = %@",p->_name);
    NSLog(@"_isName = %@",p->_isName);
    NSLog(@"name = %@",p->name);
    NSLog(@"isName = %@",p->isName);
}
@end

在项目中 commond+shift+o 搜索setValue:forKey发现在Foundation框架下的NSKeyValueCoding文件下

ht3bbp1s51 (1).png

我们查看这个文件中的方法,发现这个文件中是一些分类的集合

zsormyk4s5 (1).png

KVC异常处理及正确性验证

KVC异常处理

  • 1、赋值为空 setNilValueForKey
  • 2、Key值不存在 setValue:forUndefinedKey

正确性验证

validateValue 该方法的工作原理:

  • 1、先找一下你的类中是否实现了方法 -(BOOL)validate<Key>:error;
  • 2、如果实现了就会根据实现方法里面的自定义逻辑返回NO或者YES;如果没有实现这个方法,则系统默认返回YES

示例代码

WTPerson…h

#import <Foundation/Foundation.h>

@interface WTPerson : NSObject

/** name  **/
@property(nonatomic,strong)NSString * name;

/** age  **/
@property(nonatomic,assign)int age;

@end

WTPerson.m

#import "WTPerson.h"

@implementation WTPerson

//对非对象类型,值不能为空
- (void) setNilValueForKey:(NSString *)key{
    NSLog(@"%@ 值不能为空",key);
}

//赋值的key不存在
- (void) setValue:(id)value forUndefinedKey:(NSString *)key{
    NSLog(@"key = %@值不存在",key);
}

//取值的key不存在
- (id) valueForUndefinedKey:(NSString *)key{
    NSLog(@"key = %@值不存在",key);
    return nil;
}

//正确性验证
- (BOOL) validateAge:(inout id  _Nullable __autoreleasing *)ioValue error:(out NSError * _Nullable __autoreleasing *)outError{
    NSNumber* value = (NSNumber*)*ioValue;
    NSLog(@"%@",value);
    if ([value integerValue] >= 0 && [value integerValue] <= 200) {
        return YES;
    }
    return NO;
}

@end

ViewController.m

#import "ViewController.h"
#import "WTPerson.h"
#import "WTContainer.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    WTPerson * p = [WTPerson new];

    //异常处理
    [p setValue:@18 forKey:@"name"];
    [p setValue:nil forKey:@"name"];
    NSLog(@"name = %@",p.name);

    [p setValue:nil forKey:@"age"];
    NSLog(@"age = %d",p.age);

    [p setValue:@"hello" forKey:@"name1"];

    NSLog(@"name = %@",[p valueForKey:@"name1"]);

    //万能容器
    WTContainer * container = [WTContainer new];

    [container setValue:@"wt" forKey:@"name"];
    [container setValue:@18 forKey:@"age"];

    NSLog(@"name = %@,age = %@",[container valueForKey:@"name"],[container valueForKey:@"age"]);

    //正确性验证
    NSNumber * value = @200;
    NSNumber * value1 = @199;

    if ([p validateValue:&value1 forKey:@"age" error:NULL]) {

        [p setValue:value1 forKey:@"age"];
    }

    NSLog(@"%@",[p valueForKey:@"age"]);
}

@end

原文:GitHub

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容

  • 学习参考,如有错误,欢迎批评指正!!! KVC Key-Value Coding 键值编码,可以通过一个ke...
    人间四月天_Andy阅读 574评论 0 2
  • 在编程中,最常见的就是程序的流程取决于你所使用的各种变量和属性的值,根据变量和属性的值确定后面运行的代码,有时会检...
    pro648阅读 1,607评论 2 27
  • 赋值过程: 先找相关方法:set<key>:,_set<key>:,setIs<key>: 若没有相关方法则:+ ...
    wp_Demo阅读 664评论 0 1
  • UI总结-KVC赋值 在实际的项目阶段,后台给我们的数据都是以字典的形式.我们在拿到数据的时候...
    Dear丶Musk阅读 328评论 0 1
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,472评论 28 53