我不知道的KVC

关于KVC应该不用介绍了,通过以下方法:

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

我们可以给对象的属性赋值或者读取属性值,通过配合runTime可以有很多妙用。

以下几点是我之前学习过程中有偏差的或者没有接触过的:

Key与KeyPath

举一个例子:Person类有一个name和father的属性,其中father一个一个Person类的对象。
为了拿到father的name,只用key,可能需要以下这些步骤:

    Person *p2 = [[Person alloc] init];
    p2.name = @"老张";
    
    Person *p1 = [[Person alloc] init];
    p1.name = @"小明";
    p1.father = p2;
    
    Person *father = [p1 valueForKey:@"father"];
    NSString *fatherName = [father valueForKey:@"name"]; 
    NSString *fatherName2 = [p1 valueForKeyPath:@"father.name"];

测试结果如下:

**(lldb) po fatherName**
老张

**(lldb) po fatherName2**
老张

很显然,KeyPath的方法会方便很多,简单来说就是KeyPath支持点语法来访问属性中的属性。

KVC原理

关于KVC的实现原理及一些底层的流程不是非常了解,找了一篇文章,讲的还算比较详细:
KVC之-setValue:forKey:方法实现原理与验证
里面主要讲了KVC对于Key的底层判断处理和一些关于Value类型转换的事。
总结一下还是分成以下几种情况:

  • 1 如果key是一个属性名称,那么KVC自动查找该属性的setter方法并进行调用。如果找不到set方法(默认合成,声明为dynmaic可能找不到):
    (1)没有相同命名形式的实例变量,会调用setValueForUndefinedKey方法。
    (2)有相同命名形式的实例变量,进行KVC赋值。
  • 2 如果Key是一个实例变量,寻找是否有相同命名形式的实例变量,
    (1)有则进行赋值。
    (2)没有则调用setValueForUndefinedKey方法。
  • 3 如果[setValue:nil forKey:@"someKey"],使用空对象进行KVC,则调用setNilValueForKey:方法
  • 4 如果KVC方法的参数类型是NSNumber或NSValue的对应的基本类型,先把它转换为基本数据类,再执行方法,传入转换后的数据。比如属性为:
@property (nonatomic, assign) int age;

那么

    [aUser setValue:@(20) forKey:@"age"];

的执行也没有问题,内部会进行基本数据的转换。
最后解释下相同命名形式:_<key>,_is<Key>,<key>,is<Key>都可以算相同命名形式。

键值验证

KVC提供了验证Key和Value的方法:

-(BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable __autoreleasing *)outError

- (BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError * _Nullable __autoreleasing *)outError

键值验证,它不像名称理解起来一样可以判断是否存在“键”这样的行为。
因为如果不存在键基本都会跑到setValueForUndefinedKey方法。所以这一块的真正作用我也不是非常明白,这一块的资料也不是很多。
按理解就是我们在调用validateValue:ForKey方法的时候,我们能有机会对value做一些特殊的判断操作等。以下参考:Objc KVV

比如一个User类,有个name属性,理论上来说setValue:@"任何字符串" forKey:“name” 都能对Name进行赋值,但是实际情况可能比较复杂,比如我们想要对英文字符串首字母做大写处理,对于字符串长度也希望可以得到一定控制。那么这种情况下键值验证就派上大用场了。

  • 先说明一点,就是在外部调用validateValue:forKey:方法时,会直接调用validate<Key>:error:方法进行验证,如果找不到这个方法的实现,那么return YES。

官方文档:The default implementation of this method searches the class of the receiver for a validator method whose name matches the pattern -validate<Key>:error:. If such a method is found it is invoked and the result is returned. If no such method is found, YES is returned.

验证一下,在User类中针对name声明一个方法:

- (BOOL)validateName:(id *)name error:(NSError **)error
{
    if (*name == nil) {
        return NO;
    }
    else if([*name length] > 10)
    {
        return NO;
    }
    else {
        *name = [*name localizedUppercaseString];
        return YES;
    }
}

关于error没有处理,意思到了就行。
在实际使用中我们可以这样:

    NSError *error = nil;
    NSString *name = @"shdkjahfkjsdahjfkdkjfshdk";
    if ([aUser validateValue:&name forKey:@"name" error:&error]) {
        [aUser setValue:name forKey:@"name"];
        NSLog(@"%@",[aUser valueForKey:@"name"]);
    }

    NSString *name2 = @"fenghou";
    if ([aUser validateValue:&name2 forKey:@"name" error:&error]) {
        [aUser setValue:name2 forKey:@"name"];
        NSLog(@"%@",[aUser valueForKey:@"name"]);
    }

name太长,根据validateName:error:方法应该是返回NO的,而name2则全部变成大写。运行测试一下:

**2016-07-20 15:25:52.018 RunTimePlayGround[85433:19941715] FENGHOU**

和预计的一样。

键值验证用的非常少,几乎见不到,因为以上这些工作在set方法里就可以做到。所以适用场景不是很广,就学习下。

集合运算符

以下内容参考官方文档

集合运算符是一种特殊的keyPath,所以只能用在setValue:forKeyPath:方法中。
使用结构如下:

集合运算KeyPath结构图

LeftKeyPath

LeftKeyPath表示该方法调用者的容器属性,如果调用者本身就是一个容器类,那么可以省略。举个例子,比如在一个VC中有一个userArray属性,
如果想要使用运算符的方法,有两种方法:

* 1 [self valueForKeyPath:@"userArray.@avg.age"]
* 2 [self.userArray valueForKeyPath:@"@avg.age"]

两种方法的结果都是一样的,但是第二种方法的LeftKeyPath是可以省略的。

RightKeyPath

再讲一下Right Key Path,它用来表示集合中元素想要被操作的那个属性,比如上文中的userArray里头包含了很多user对象,但是我们希望操作是针对user对象的age属性,那么RightKeyPath就是age,如果想要进行score(得分)的操作,RightKeyPath就是score。简单点说就是RightKeyPath就是要操作的属性,不能为空(除了@count运算符后面可以不跟RightKeyPath)。

简单运算符

简单运算符包含一下5个:@avg,@count,@max,@min,@sum,目前还不支持自定义。所做的字面操作都和字面意思一样,按顺序就是:取平均,取总数,最大值,最小值,总合。

对前面的LeftKeyPath和RightKeyPath有所了解之后,下面运用简单运算符也不算难:

    User *user1 = [[User alloc] initWithAge:10];
    User *user2 = [[User alloc] initWithAge:2];
    User *user3 = [[User alloc] initWithAge:37];
    User *user4 = [[User alloc] initWithAge:69];
    self.userArray = @[user1, user2, user3, user4];
    NSLog(@"%@",[self valueForKeyPath:@"userArray.@avg.age"]);
    NSLog(@"%@",[self valueForKeyPath:@"userArray.@count.age"]);
    NSLog(@"%@",[self valueForKeyPath:@"userArray.@max.age"]);
    NSLog(@"%@",[self valueForKeyPath:@"userArray.@min.age"]);
    NSLog(@"%@",[self valueForKeyPath:@"userArray.@sum.age"]);

打印如下:

2016-07-20 17:15:14.228 RunTimePlayGround[86511:20234568] 29.5
2016-07-20 17:15:14.228 RunTimePlayGround[86511:20234568] 4
2016-07-20 17:15:14.229 RunTimePlayGround[86511:20234568] 69
2016-07-20 17:15:14.229 RunTimePlayGround[86511:20234568] 2
2016-07-20 17:15:14.229 RunTimePlayGround[86511:20234568] 118
  • @avg操作符会将待计算对象转为double值进行计算平均,然后返回一个NSNumber对象。
  • @count就是计算待计算对象的数量,可以不需要RightKeyPath。
  • @max和@min通过对象的compare方法来进行比较大小,不支持compare方法的对象会崩溃。如果待计算对象为数字类型,返回NSNumber,其余返回待计算对象本身类型。
  • @sum,与@avg类似,将待计算对象转为double进加和,返回NSNumber对象。

测试了一下,数字类型的对象以上5个计算符都支持,NSDate支持@max,@min,@count。
字符串类型与NSDate比较相像,但是特殊的一点在于如果字符串首字符是数字或者字符串是纯数字那么也能进行@avg和@sum的计算,与@avg和@sum的操作方式一致:先转成double。

对象运算符

有两种运算符@distinctUnionOfObjects和@unionOfObjects,使用类似于简单运算符,只是功能不一样。
两种运算符都是将所有待运算对象重新打包成一个新的数组进行返回,区别在于@unionOfObjects不会剔除那些重复元素,@distinctUnionOfObjects则会做去重处理,直接看例子:

    User *user1 = [[User alloc] initWithAge:10 birth:birth1];
    User *user2 = [[User alloc] initWithAge:10 birth:birth2];
    User *user3 = [[User alloc] initWithAge:37 birth:birth3];
    User *user4 = [[User alloc] initWithAge:69 birth:birth4];
    self.userArray = @[user1, user2, user3, user4];
    NSArray *distinctUnion = [self valueForKeyPath:@"userArray.@distinctUnionOfObjects.age"];
    NSArray *unionArray = [self valueForKeyPath:@"userArray.@unionOfObjects.age"];

结果如下:

**(lldb) po distinctUnion**
<__NSArrayI 0x7fb789d56e70>(
10,
69,
37
)

**(lldb) po unionArray**
<__NSArrayM 0x7fb789d56ec0>(
10,
10,
37,
69
)

可以明显看到两种运算符的区别。

容器运算符

包含以下三种运算符:
@distinctUnionOfArrays
@unionOfArrays
@distinctUnionOfSets
其实进行的运算和对象运算符差不多,只不过适用于容器中的容器这种情况,直接举例子好了:

    User *user1 = [[User alloc] initWithAge:10 ];
    User *user2 = [[User alloc] initWithAge:10 ];
    User *user3 = [[User alloc] initWithAge:37 ];
    User *user4 = [[User alloc] initWithAge:69 ];
    self.userArray1 = @[user1, user2, user3, user4];
    
    User *user5 = [[User alloc] initWithAge:69 ];
    User *user6 = [[User alloc] initWithAge:11 ];
    User *user7 = [[User alloc] initWithAge:23 ];
    User *user8 = [[User alloc] initWithAge:18 ];
    self.userArray2 = @[user5,user6,user7,user8];
    NSArray *array3 = @[self.userArray1, self.userArray2];
    NSArray *distinctUnionArray = [array3 valueForKeyPath:@"@distinctUnionOfArrays.age"];
    NSArray *unionArray = [array3 valueForKeyPath:@"@unionOfArrays.age"];

结果如下:

**(lldb) po distinctUnionArray**
<__NSArrayI 0x7fa150d4af60>(
18,
10,
23,
37,
11,
69
)

**(lldb) po unionArray**
<__NSArrayM 0x7fa150d15590>(
10,
10,
37,
69,
69,
11,
23,
18
)

前两个针对的集合是Arrays,后一个针对的集合是Sets。 因为Sets中的元素本身就是唯一的,所以没有对应的@unionOfSets运算符,只有@distinctUnionOfSets

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

推荐阅读更多精彩内容

  • KVC(Key-value coding)键值编码,单看这个名字可能不太好理解。其实翻译一下就很简单了,就是指iO...
    黑暗中的孤影阅读 49,651评论 74 441
  • KVC(Key-value coding)键值编码,单看这个名字可能不太好理解。其实翻译一下就很简单了,就是指iO...
    朽木自雕也阅读 1,548评论 6 1
  • KVC(Key-value coding)键值编码,单看这个名字可能不太好理解。其实翻译一下就很简单了,就是指iO...
    Fendouzhe阅读 667评论 0 6
  • KVC简单介绍 KVC(Key-value coding)键值编码,就是指iOS的开发中,可以允许开发者通过Key...
    公子无礼阅读 1,379评论 0 6
  • 你听过听风者,听雨者,不知道有没有听过淋雨者 淋雨者,爱淋雨人也。 淋雨时,心情舒畅,万事不顾,只享受雨拍打在身上...
    别的其他阅读 644评论 0 0