首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >编码篇-精析OC史诗级技术之KVC

编码篇-精析OC史诗级技术之KVC

作者头像
進无尽
发布2018-09-12 18:05:15
1.2K0
发布2018-09-12 18:05:15
举报
文章被收录于专栏:進无尽的文章進无尽的文章

概述

KVC 全称 key valued coding 键值编码。

不得不承认KVC在开发过程中是神器一般的存在。如果正确灵活使用kvc,会使得整个开发过程轻松很多。简单而强大。

反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性.JAVA,C#都有这个机制。ObjC也有,所以你根部不必进行任何操作就可以进行属性的动态读写,就是KVC。

KVC的操作方法由NSKeyValueCoding提供,而他是NSObject的类别,也就是说ObjC中几乎所有的对象都支持KVC操作。它提供一种机制来间接访问对象的属性。直接访问对象是通过调用访问器的方法实现,而KVC不需要调用访问器的设置和获取方法。

KVC的主要方法和用途

- (nullable id)valueForKey:(NSString *)key;                          //直接通过Key来取值
- (void)setValue:(nullable id)value forKey:(NSString *)key;          //通过Key来设值
- (nullable id)valueForKeyPath:(NSString *)keyPath;                  //通过KeyPath来取值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;  //通过KeyPath来设值

  ************************************************************************

  当然NSKeyValueCoding类别中还有其他的一些方法,下面列举一些

  + (BOOL)accessInstanceVariablesDirectly;
  //默认返回YES,表示如果没有找到Set<Key>方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索

  - (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
  //KVC提供属性值正确性�验证的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。

  - (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
  //这是集合操作的API,里面还有一系列这样的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回。

  - (nullable id)valueForUndefinedKey:(NSString *)key;
  //如果Key不存在,且没有KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常。

  - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
  //和上一个方法一样,但这个方法是设值。

  - (void)setNilValueForKey:(NSString *)key;
  //如果你在SetValue方法时面给Value传nil,则会调用这个方法

  - (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
  //输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。


setValue:forKey:方法:给模型的属性赋值

 赋值原理:(以 setIcon为例:)

(1)去模型中查找有没有setIcon方法,就直接调用这个set方法,给模型这个属性赋值[self setIcon:dict[@"icon"]];
(2)如果找不到set方法,接着就会去寻找有没有icon属性,如果有,就直接访问模型中icon = dict[@"icon"];
(3)如果找不到icon属性,接着又会去寻找_icon属性,如果有,直接_icon = dict[@"icon"];
(4)如果都找不到就会报错
    [<Flag 0x7fb74bc7a2c0> setValue:forUndefinedKey:]
  • 直接赋值
  • 支持键值路径
  • 支持操作符
  • 字典转模型
  • 修改UI私有属性

直接赋值

对于属性值我们可以通过setter 和getter方法,或读取或写入数值。 当然我们也可以用KVC 的方式进行读写数据。

举个例子:

  @interface Person : NSObject
  @property(nonatomic,copy,readonly)NSString* name;
  @property(nonatomic,assign)NSNumber *age;
  @end

 Person *person=[[Person alloc] init];

[person setValue:@"25" forKey:@"age"];
[person setValue:@"皮拉夫大王" forKey:@"name"];

NSLog(@"person 的名字是%@",person.name);
NSLog(@"person 的年领是%@",[person valueForKey:@"age"]);

从上面的例子中我们可以发现:

  • 只读的属性怎么可以赋值?
  • 还有age属性明明是NSNumber类型的,怎么可以把字符串赋给它?

(1)KVC 不但能够赋值,而且还能破坏只读的特性。 (2)更重要的是KVC 有自动装箱(自动类型转换)的功能,我们不需要去转换类型了。由于开发过程中数据领域是字符串的天下,所以这个自动装箱的功能的确是极好的。 (3)KVC可以访问成员变量,无论是否提供getter/setter方法,无论可见性是怎样,是否有readonly修饰。

支持键值路径

什么叫支持键值路径?说白了就是支持多层级属性直接赋值。假如现在有一个书籍类,类中包含了书籍的名称name。书籍可以被Person所拥有(就是可以作为person的属性)

#import <Foundation/Foundation.h>
  @interface Book : NSObject
  @property(nonatomic,copy)NSString* name;
  @end

那么我们就可以这样来用

Person *person=[[Person alloc] init];
Book *myBook=[[Book alloc] init];
person.book=myBook;

[person setValue:@"程序员摊煎饼指南" forKeyPath:@"book.name"];

NSLog(@"%@",[person valueForKeyPath:@"book.name"]);

需要说明的是:在不必要的情况下使用keyPath会浪费性能。

支持操作符

格式为:[p valueForKeyPath:@"Left keypath部分.@Collectionoperator部分.Right keypath部分”];

Left keypath部分:需要操作对象路径。
Collectionoperator部分:通过@符号确定使用的集合操作。
Right keypath部分:需要进行集合操作的属性。

(1)简单集合操作符

  @count: 返回一个值为集合中对象总数的NSNumber对象。
  @sum: 首先把集合中的每个对象都转换为double类型,然后计算其总,最后返回一个值为这个总和的NSNumber对象。
  @avg: 把集合中的每个对象都转换为double类型,返回一个值为平均值的NSNumber对象。
  @max: 使用compare:方法来确定最大值。所以为了让其正常工作,集合中所有的对象都必须支持和另一个对象的比较。
  @min: 和@max一样,但是返回的是集合中的最小值。

  [products valueForKeyPath:@"@count"]; 
  [products valueForKeyPath:@"@sum.price"]; 
  [products valueForKeyPath:@"@avg.price"]; 
  [products valueForKeyPath:@"@max.price"]; 
  [products valueForKeyPath:@"@min.launchedOn"]; 

如果操作对象(集合/数组)内是NSNumber,可以这样写

 [products valueForKeyPath:@"@sum.self"]; 

1.使用类的方法做操作符

 NSArray *array = @[@"name", @"w", @"aa", @"jimsa"];
 NSLog(@"%@", [array    valueForKeyPath:@"uppercaseString"]);

 输出:
(  NAME, W,  AA, JIMSA)

相当于数组中的每个成员执行了uppercaseString方法,然后把返回的对象组成一个新数组返回。既然可以用uppercaseString方法,那么NSString的其他方法也是可以的.

2.剔除重复数据

NSArray *array = @[@"name", @"w", @"aa", @"jimsa", @"aa"]; NSLog(@"%@",     
[array valueForKeyPath:@"@distinctUnionOfObjects.self"]);

打印:
 ( name, w,  jimsa, aa    )

3.对NSDictionary数组快速找出相应key对的值

NSArray *array = @[
@{@"name1" : @"cookeee",@"code" : @1}, 
@{@"name": @"jim",@"code" : @2}, 
@{@"name": @"jim",@"code" : @1},
@{@"name": @"jbos",@"code" : @1}];
NSLog(@"%@", [array valueForKeyPath:@"name"]);

直接得到字典中name key对应的值组成的数组,显然比循环取值再加入到新数组中方便快捷,
由于第一个元素没有name这个Key ,所以里面为<null>)

(  "<null>",   jim, jim,   jbos   )

(2)对象操作符

@unionOfObjects:返回操作对象内部的所有对象,返回值为数组
@distinctUnionOfObjects:返回操作对象内部的不同对象,返回值为数组

(3)数组和集合操作符

@unionOfArrays:返回操作对象(且操作对象内对象必须是数组/集合)中数组/集合的所有对象,返回值为数组
@distinctUnionOfArrays:返回操作对象(且操作对象内对象必须是数组/集合)中数组/集合的不同对象,返回值为数组
@distinctUnionOfSets:返回操作对象(且操作对象内对象必须是数组/集合)中数组/集合的所有对象,返回值为集合

提示:集合无重复元素

(4)自定义操作符

NSArray为例,runtime跑一下

#import <objc/runtime.h>
unsigned int outconunt = 0;
Method *meths =class_copyMethodList([NSArray class], &outconunt);
for (int i = 0; i<outconunt; i++) {
    Method meth = meths[i];
    SEL metSel = method_getName(meth);
    NSLog(@"L : %@",NSStringFromSelector(metSel));
    
}

可以看到一大堆的方法,由于太多了,无法截图完整的,看上图红框中的代码是不是很眼熟。

猜想:实现_<key>ForKeyPath:即可自定义Collection Operators 尝试定义一个名为@jackCollection Operators

可见,只要写好实现,完全可以自定义一些比较有用的Collection Operators

字典转模型

下面是常见的使用方法,目前有很多KVC 和 Runtime一起使用达到Json数据自动转模型的方法,本文暂时不做介绍。

@implementation Model
-(instancetype)initWithDict:(NSDictionary *)dict
{
    if (self=[super init])
    {
        // 字典转模型的常用语句
        [self setValuesForKeysWithDictionary:dict];
    }
    return self;
}

-(void)setValue:(id)value forUndefinedKey:(NSString *)key
{
    if ([key isEqualToString:@"id"])
    {
          self.whoCare=value;
      }
  }
@end

修改UI私有属性

(1)如何实现这样的效果?

系统默认的是这样的:

看了系统自带的API,无法解决这个问题,现在有两个路:

  • 自定义PageControl
  • 通过runtime遍历出UIPageControl所有属性(包括私有成员属性)利用KVC可强制修改系统的PageControl,达到想要的效果。充满了黑科技之感
 u_int count;
  Ivar *properties =class_copyIvarList([UIPageControl class], &count);
  for (int i = 0; i<count; i++)
  {
    const char* propertyName =ivar_getName(properties[i]);
    const char* propertyType = ivar_getTypeEncoding(properties[i]);
    NSLog(@"属性:%@  =  %@",[NSString stringWithUTF8String: propertyName],[NSString stringWithUTF8String: propertyType]);
  }

结果非常满意,果然找到我想要的图片设置属性。 然后通过KVC设置自定义图片,实现了效果,代码如下:

 UIPageControl *pageControl = [[UIPageControl alloc] init]; 
 [pageControl setValue:[UIImage imageNamed:@"home_slipt_nor"] forKeyPath:@"_pageImage"];
 [pageControl setValue:[UIImage imageNamed:@"home_slipt_pre"] forKeyPath:@"_currentPageImage"];

(2)还有很多其他的修改UI控件私有属性的常见操作: 比如修改UISearchBar的输入框显示效果。

 UITextField * searchField = [searchBar valueForKey:@"searchField"];
 [searchField setValue:GrayTextColor forKeyPath:@"placeholderLabel.textColor"];
 [searchField setValue:[UIFont boldSystemFontOfSize:10] forKeyPath:@"_placeholderLabel.font"];


 UITextField *searchField = [[mySearchBar subviews] lastObject];
 [searchField setReturnKeyType:UIReturnKeyDone];

参考文章:

iOS开发技巧系列---详解KVC KVC进阶(三) iOS底层-KVC使用实践以及实现原理 iOS开发技巧系列---详解KVC(我告诉你KVC的一切)

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.02.07 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
  • KVC的主要方法和用途
    • 直接赋值
      • 支持键值路径
        • 支持操作符
          • 字典转模型
            • 修改UI私有属性
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档