iOS开发之KVC

iOS开发之KVC

KVC简介:

KVC(key - value codeing),是苹果提供的一套基于运行时的编码技术。它允许开发者直接通过key值来访问对象对应的属性,而无需明确调用set、get方法,所以kvc可以在运行时动态的修改对象的属性,这也是他的强大之处。若对象没有实现属性的set、get方法,那么可以直接使用kvc来存取其属性值,无论该变量是在类接口处定义,还是在类实现处定义,无论用了什么样的访问修饰符,只在存在以命名的变量,KVC都可以对该成员变量赋值。若属性实现了set、get方法则尽量通过set、get方法来存取值,这样不会破坏代码的封装性,也更加方便安全高效。KVC的定义都是在NSObject的的扩展中实现的(NSKeyValueCodeing类别)。所以继承了NSObject的对象才可以使用KVC,一些纯swift类的结构体是不能使用KVC的。

以下是KVC一些常用的主要方法:

  • - (nullable id)valueForKey:(NSString *)key;

//通过Key来取值

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

通过Key来设值,可以为NSMutableDictionary赋值,也可以为对象的属性赋值,如字典转模型,若属性类型为对象value值可以为nil,否则不能为nil,因为值类型不能为nil。若value为数字类型不可以直接设置需要转换为NSNumber对象再设置,value为结构体时需要转换为NSValues对象才能赋值。

  • - (nullable id)valueForKeyPath:(NSString *)keyPath;

通过KeyPath来取值,可以使用keypath路径来对深层次的数据进行访问。

  • - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;

通过KeyPath赋值,KeyPath显得更为强大,可以作为kvc的取值,更强大的是可以使用它对集合中的对象进行各种方法的处理,例如提取最大值,最小值,平均值,去重等处理,示例如下(KVC的集合操作):

     NSArray *array = @[@3, @6, @7, @8, @10];
     //获取集合中的和
     NSNumber *sum = [array valueForKeyPath:@"@sum.self"];
     
     //获取集合中的平均值
     NSNumber *avg = [array valueForKeyPath:@"@avg.self"];
     
     //获取集合中的最大值
     NSNumber *max = [array valueForKeyPath:@"@max.self"];
     
     //获取集合中的最小值
     NSNumber *min = [array valueForKeyPath:@"@min.self"];
     
     //指定输出类型
     NSNumber *sum = [array valueForKeyPath:@"@sum.floatValue"];
     NSNumber *avg = [array valueForKeyPath:@"@avg.floatValue"];
     NSNumber *max = [array valueForKeyPath:@"@max.floatValue"];
     NSNumber *min = [array valueForKeyPath:@"@min.floatValue"];
     
     //去重  对象运算符:@distinctUnionOfObjects  @unionOfObjects
     NSArray * txArr = @[@"aa", @"bb",@"cc",@"aa", @"cc"];
     NSLog(@"txArr : %@", [txArr valueForKeyPath:@"@distinctUnionOfObjects.self"]);
     
     //嵌套使用:先对name去重再取出对应的name值
     NSArray * dicArr = @[@{@"zhang":@"wang", @"age":@12, @"gender":@"man"},
     @{@"name":@"zhang", @"age":@12, @"gender":@"woman"},
     @{@"name":@"li", @"age":@12, @"gender":@"man"}];
     NSLog(@"txArr : %@", [txArr valueForKeyPath:@"@distinctUnionOfObjects.name"]);
     
     //更改uitextFile的placeholder字体颜色
     [searchField setValue:[UIColor whiteColor] forKeyPath:@"_placeholderLabel.textColor"];

KVC中一些其他的重要方法:

  • + (BOOL)accessInstanceVariablesDirectly;

默认返回YES,在运行时若未找到属性的Set方法时,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO会对此类禁用KVC,若未找到set,get方法直接抛出异常。

  • - (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;

KVC验证属性值的API,它可以用来检查set的值是否正确、可以添加值不正确时的处理逻辑。

  • - (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;

集合操作的API,若属性是一个NSMutableArray,那么可以用这个方法获取。

  • - (nullable id)valueForUndefinedKey:(NSString *)key

取值时如果Key不存在,会调用这个方法,默认是抛出异常,可用于处理异常。

  • - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;

设值时若key不存在会调用,抛出异常,可用于处理异常。

  • - (void)setNilValueForKey:(NSString *)key;

SetValue时面给Value传nil,会调用这个方法.

  • - (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;

传入一组key,返回该组key对应的Value,并转成字典返回,可用于Model转字典。

  • \ - (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;

用来修改Model中对应key的属性,用于字典转模型。

注意: setObject: forKey: 是NSMutableDictionary特有的方法,value不能为nil/null,但可以为[NSNull null](因为它为对象类型),必须为对象。

以下为场景示例:

  • KVC为对象赋值:

以下示例中,Cameras类有两个NiCon类型的属性,其中nicon1为公有的属性,nicon1 未自动生成set、get方法;nicon2为私有的属性;NiCon类有一个公有属性devName,一个私有属性devNO;

// Cameras.h 
#import <Foundation/Foundation.h>
#import "NiCon.h"
NS_ASSUME_NONNULL_BEGIN

@interface Cameras : NSObject

@property(nonatomic,strong)NiCon * nicon1;
@end

NS_ASSUME_NONNULL_END

//Cameras.m
#import "Cameras.h"

@interface Cameras()

@property(nonatomic,strong)NiCon * nicon2;
@end
@implementation Cameras
//让nicon1不要自动生成set、get方法;
@synthesize nicon1;


- (instancetype)init
{
    self = [super init];
    if (self) {
        
        
    }
    return self;
}


- (id)valueForUndefinedKey:(NSString *)key{
    
    NSLog(@"该key: %@  不存在", key);

    return nil;
}


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



+(BOOL)accessInstanceVariablesDirectly{
    
//    return NO;
//        2019-03-11 16:49:44.899991+0800 test[2256:72136] 该key值: nicon1  不存在
//    2019-03-11 16:49:44.900311+0800 test[2256:72136] nicon1:  (null)


    return YES;
//    2019-03-11 16:44:29.408502+0800 test[2112:64508] nicon1:  <NiCon: 0x60000164e180>
//    2019-03-11 16:44:29.408724+0800 test[2112:64508] nicon2:  <NiCon: 0x60000164e180>
//    2019-03-11 16:44:29.409306+0800 test[2112:64508] nicon1.devName:  尼康X525
//    2019-03-11 16:44:29.409462+0800 test[2112:64508] nicon2.devName:  尼康X525

}
@end

第二个类:

//NiCon.h
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface NiCon : NSObject

@property(nonatomic, copy)NSString * devName;

@end

NS_ASSUME_NONNULL_END
//NiCon.m

#import "NiCon.h"
@interface NiCon()

@property(nonatomic,assign) int devNO;

@end
@implementation NiCon

-(BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable __autoreleasing *)outError{
    
    return [inKey isEqualToString:@"devNO"];
}
@end

接下来我们在viewController中使用KVC来对对象的属性进行赋值:

//viewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Cameras* cams = [[Cameras alloc]init];
    NiCon * nic1 = [[NiCon alloc]init];
    
    //设置公有属性
    [cams setValue:nic1 forKey:@"nicon1"];
    NSLog(@"nicon1:  %@", cams.nicon1);
    // 2019-03-11 16:06:01.305184+0800 test[876:12560] nicon1:  <NiCon: 0x600001737bc0>
    
    //设置私有属性
    [cams setValue:nic1 forKey:@"nicon2"];
    NSLog(@"nicon2:  %@", [cams valueForKey:@"nicon2"]);
    //    2019-03-11 16:09:26.472743+0800 test[999:20027] nicon2:  <NiCon: 0x600001bc6820>
}

从以上示例可以看出,无论是公有属性还是私有属性,KVC都可以成功的赋值和取值,所以KVC是不受访问修饰符限制的。

接下来我们分别将Cameras的属性nicon1的属性名改为_nicon1、isNicon1,并将+(BOOL)accessInstanceVariablesDirectly返回值分别设为YES和NO看能否赋值成功?

//Cameras.h
//@property(nonatomic,strong)NiCon * _nicon1;
//@property(nonatomic,strong)NiCon * isNicon1;


//NiCon.m
//如果Key不存在,会调用这两方法,默认是抛出异常,可用于处理异常。
- (id)valueForUndefinedKey:(NSString *)key{
    
    NSLog(@"该key: %@  不存在", key);
    return nil;
}

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


+(BOOL)accessInstanceVariablesDirectly{
    
//    return NO;

    return YES;
- (void)viewDidLoad {
    [super viewDidLoad];
    
    Cameras* cams = [[Cameras alloc]init];
    NiCon * nic1 = [[NiCon alloc]init];
    
    //设置公有属性
    [cams setValue:nic1 forKey:@"nicon1"];
    NSLog(@"nicon1:  %@", cams._nicon1);
//    return NO;
//        2019-03-11 16:49:44.899991+0800 test[2256:72136] 该key值: nicon1  不存在
//    2019-03-11 16:49:44.900311+0800 test[2256:72136] nicon1:  (null)

//    return YES;
//    2019-03-11 16:44:29.408502+0800 test[2112:64508] nicon1:  <NiCon: 0x60000164e180>
//    2019-03-11 16:44:29.408724+0800 test[2112:64508] nicon2:  <NiCon: 0x60000164e180>

    //设置公有属性
    [cams setValue:nic1 forKey:@"nicon1"];
    NSLog(@"nicon1:  %@", cams.isNicon1);

//    return NO;
//        2019-03-11 16:49:44.899991+0800 test[2256:72136] 该key值: nicon1  不存在
//    2019-03-11 16:49:44.900311+0800 test[2256:72136] nicon1:  (null)

//    return YES;
//    2019-03-11 16:44:29.408502+0800 test[2112:64508] nicon1:  <NiCon: 0x60000164e180>
//    2019-03-11 16:44:29.408724+0800 test[2112:64508] nicon2:  <NiCon: 0x60000164e180>

}

以上测试验证了+(BOOL)accessInstanceVariablesDirectly;
当返回值设为YES(默认),在运行时若未找到属性的Set方法时,会按照_key,_iskey,key,iskey的顺序搜索成员变量,设置成NO会对此类禁用KVC搜索,若未找到set,get方法直接抛出异常。

使用KVC的setValue:forKeyPath:设置对象的嵌套属性:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Cameras* cams = [[Cameras alloc]init];
    NiCon * nic1 = [[NiCon alloc]init];
    
    //使用keyPath设置公有嵌套属性
    [cams setValue:@"尼康X525" forKeyPath:@"nicon1.devName"];
    NSLog(@"nicon1.devName:  %@", [cams valueForKeyPath:@"nicon1.devName"]);
//  test[1380:36444] nicon1.devName:  尼康X525
    
    //使用keyPath设置私有嵌套属性
    [cams setValue:@"尼康X525" forKeyPath:@"nicon2.devName"];
    NSLog(@"nicon2.devName:  %@", [cams valueForKeyPath:@"nicon2.devName"]);
// test[1540:43465] nicon2.devName:  尼康X525

由此可见KVC对与公有私有的嵌套属性,也是从来不惧,哈哈就是这么强大!

使用KVC的键值验证:- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;

//NiCon.m
@implementation NiCon

-(BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable __autoreleasing *)outError{
    
    return [inKey isEqualToString:@"devNO"];
}




//viewController.m

    NSNumber * num = [NSNumber numberWithInteger:1011];
    if ([cams validateValue:&num forKey:@"nicon2.devNO" error:nil]) {//使用KVC键值验证属性如果键名是正确的再赋值
        
        [cams setValue:num forKeyPath:@"nicon2.devNO"];
        NSLog(@"nicon2.devNO:  %@", [cams valueForKeyPath:@"nicon2.devNO"]);
        //log: test[6716:263169] nicon2.devNO:  1011
    }

模型和字典互转:

    Cameras* cams = [[Cameras alloc]init];
    //模型转字典
    NSArray * modelKeys = @[@"devName", @"devNO"];
    NSDictionary * modelDic = [[cams valueForKey:@"nicon2"] dictionaryWithValuesForKeys:modelKeys];    NSLog(@"modelDic:  %@", modelDic);
/*    2019-03-12 09:53:33.489429+0800 test[6716:263169] modelDic:  {
        devNO = 1011;
        devName = "\U5c3c\U5eb7X525";
    }
*/

//字典转模型
    NiCon * nicon3 = [[NiCon alloc]init];
    [nicon3 setValuesForKeysWithDictionary:modelDic];
    NSLog(@"devName: %@   devNO:  %@",nicon3.devName, [nicon3 valueForKey:@"devNO"]);
// 2019-03-12 09:53:33.489696+0800 test[6716:263169] devName: 尼康X525   devNO:  1011

KVC确实很强大,但是万事皆有度,不可过分,否则会适得其反。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值