探讨KVC底层原理

详细文档,能够参考官方文档 KVC官方文档 developer.apple.com/library/arc… html

image.png

KVC(key-value coding),键值编码,NSObject 的非正式协议NSKeyValueCoding(也就是说全部继承了NSObject 的类对象均可以使用KVC),容许开发者经过key来访问对象属性,或者给对象属性赋值.它不须要明确的存取方法,能够在运行时(而不是在编译期肯定),动态地访问或者修改对象属性.

KVC(NSKeyValueCoding非正式协议)里面四个经常使用方法(其余方法下面也有提到,能够参看NSKeyValueCoding里其余方法):

//根据key取值  
- (id)valueForKey:(NSString *)key;
//根据key设值
- (void)setValue:(nullable id)value forKey:(NSString *)key;
//根据keyPath取值
- (nullable id)valueForKeyPath:(NSString *)keyPath;
//根据keyPath设值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
复制代码

简单设值(setter)默认执行流程(原理)

官方文档 ios

image.png

1.首先查找setter访问器方法,按照setKey, _setKey, setIsKey顺序查找,找到一个就返回,再也不继续查找. 若是找到访问器方法,开始检查参数类型。若是参数类型是指针对象类型,调用方法便可。若是value是非对象指针类型(简单数值类型或结构体),可是给定的value是nil,则会调用 setNilValueForKey 方法。setNilValueForKey 方法默认实现会抛出NSInvalidArgumentException异常致使程序崩溃,咱们能够重写它。 若是找不到访问器方法,则进行下一步2macos

2.没找到访问器方法,则会调用类方法 accessInstanceVariablesDirectly(默认返回YES),若返回YES,则按照顺序: _key , _isKey , key, isKey进行查找(找到一个不继续查找).若是找到了这样的实例变量,而且其类型是对象指针类型,先release旧对象,再赋值新对象。若是变量类型是其余类型(基本类型),则先使用NSNumber 或者 NSValue 进行转换,再进行赋值操做数组

3.若没找到访问器方法,也没有找到实例变量,则会调用 setValue:forUndefinedKey:方法. setValue:forUndefinedKey: 方法默认实现会抛出NSUnknownKeyException异常致使程序崩溃,咱们能够重写它bash

备注:这里官方文档没有说起到setIsKey方法(不排除将来会移除该方法),截止目前,该方法依然会被查找!markdown

KVC设值验证

为了方便验证设值流程,咱们不使用@property (不想要系统自动给咱们生成getter/setter访问器 以及带下划线_key成员变量,注意不会生成其余成员变量) 新建一个Person类app

#import <Foundation/Foundation.h>
#import "Address.h"

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject

@property (nonatomic, strong) Address * address;

@end

NS_ASSUME_NONNULL_END
复制代码
//Person.m实现类
#import "Person.h"
@interface Person ()
{
    NSString* _name;
    NSString* name;
    NSString* _isName;
    NSString* isName;
    NSInteger age;
    NSPoint location;
}
@end
@implementation Person

-(void)setIsName:(NSString*)n{
    NSLog(@"%s",__func__);
    isName = n;
}
-(void)_setName:(NSString*)n{
    NSLog(@"%s",__func__);
    _name = n;
}
-(void)setName:(NSString*)n{
    NSLog(@"%s",__func__);
    name = n;
}
- (void)setValue:(id)value forKey:(NSString *)key{
    
    NSLog(@"%s",__func__);
    [super setValue:value forKey:key];
    NSLog(@"%s",__func__);
}
+ (BOOL)accessInstanceVariablesDirectly{
    
    NSLog(@"%s",__func__);
    return [super accessInstanceVariablesDirectly];
}

@end
复制代码
#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Person* p = [[Person alloc] init];
        //[p setValue:@"JayJay" forKey:@"_name"];方式会直接获取实例变量
        [p setValue:@"JayJay" forKey:@"name"];
        
    }
    return 0;
}
复制代码

运行断点调试 oop

image.png

能够看到,优先查找到setName方法,而且给成员变量值name(可随意修改哪一个成员变量)进行设值 接着将setName访问器方法注释掉运行 ui

image.png
image.png
能够看到,找到_setName方法,而且给成员变量值_name(可随意修改哪一个成员变量)进行设值

接着将_setName访问器方法注释掉运行 编码

image.png
image.png
能够看到,找到setIsName方法,而且给成员变量值isName(可随意修改哪一个成员变量)进行设值

接着注释掉setIsName方法,再次运行

image.png

image.png
能够看到,因为没有找到setName,_setName,setIsName方法, [super setValue:value forKey:key] 调用类方法 accessInstanceVariablesDirectly(这里默认返回YES),而后直接访问成员变量 _name进行设值

接着将成员变量_name注释掉,再次运行

image.png
能够看到,直接访问成员变量 _isName进行设值

接着将成员变量_isName注释掉,再次运行

image.png
能够看到,直接访问成员变量 name进行设值

接着将成员变量name注释掉,再次运行

image.png
能够看到,直接访问成员变量 isName进行设值

接着将 isName注释,再次运行 程序崩溃,在方法 setValue:forUndefinedKey: 抛出 NSUnknownKeyException异常,咱们能够重写该方法(空实现程序就不会崩溃).这里若是将全部成员变量不注释,注释掉setKey,_setKey,setIsKey这三个方法,而重写类方法 accessInstanceVariablesDirectly,让其返回NO,也会出现NSUnknownKeyException异常致使崩溃

image.png

这里防止崩溃,咱们重写setValue: forUndefinedKey 方法

- (void)setValue:(id)value forUndefinedKey:(NSString *)key{
    
    NSLog(@"%s",__func__);
}
复制代码

image.png

简单设值流程就是 setKey --> _setKey --> setIsKey --> _key --> _isKey --> key --> isKey

简单取值(getter)默认执行流程(原理)

官方文档:

image.png

1.查找接收者访问器(这里是getter)方法,按照 getKey,key ,isKey,_getKey , _key 顺序寻找方法,找到一个就返回,再也不继续查找 若是方法返回结果是对象指针类型,则直接返回,若是结果的类型是NSNumber支持的数值类型之一,则完成转换并返回NSNumber. 不然,将完成转换并返回NSValue(Mac OS 10.5中的新增功能:任意类型的结果都将转换为NSValues,而不只仅是NSPoint,NSRange,NSRect和NSSize)

2 . 若是没有找到简单getter方法,则匹配countOfKey方法和objectInKeyAtIndex:方法 或者 countOfKey方法和keyAtIndexes:方法 若匹配到,则动态建立一个NSArray集合代理对象,该对象响应全部NSArray方法并返回。不然,执行下一步3

备注:动态建立的NSArray代理对象会将NSArray接收到的countOfKey、objectInKeyAtIndex: 、keyAtIndexes:的消息返回给符合KVC规则的调用者.此时NSArray代理对象与上面的方法工做时,系统会将该对象当作NSArray对象,这样能够响应全部NSArray方法

3.若是没有找到NSArray的简单getter方法,或者NSArray的存取方法组,则查找有没有countOfKey、enumeratorOfKey、memberOfKey: 命名的方法(三者缺一不可)。若是找到,则动态建立一个NSSet集合代理对象,该对象响应全部NSSet方法并返回。不然,执行下一步4

备注:动态建立的NSSet代理对象会将NSSet接收到的countOfKey、enumeratorOfKey、memberOfKey: 的消息返回给符合KVC规则的调用者.此时NSSet代理对象与上面的方法工做时,系统会将该对象当作NSSet对象,这样能够响应全部NSSet方法

4.若是没有找到简单getter方法,或集合存取方法组,以及类方法accessInstanceVariablesDirectly返回YES(默认返回YES),则按照_key , _isKey , key, isKey顺序进行查找实例,若是找到对应的实例,则马上获取实例可用的值并跳转到第5步,不然,跳转到第6步.

5.若是取回的是一个指针对象,则直接返回这个结果。若是取回的是简单值类型,而且能够被NSNumber或者NSValue转换,那么转换后再返回

6.若是都没有找到,则会调用 valueForUndefinedKey:方法,会抛出NSUnknownKeyException异常崩溃,能够重写该方法。

备注:这里官方文档没有说起到_getKey (不排除将来移除可能性)方法,可是Xcode API点击进去能够在最后发现下图所示提示(Apple从Mac OS 10.3开始不建议使用下划线开头的KVC方法),实际截至目前仍然会对此方法进行查找

image.png

KVC取值验证

#import <Foundation/Foundation.h>
#import "Address.h"

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject

@property (nonatomic, strong) Address * address;

@end

NS_ASSUME_NONNULL_END
复制代码
#import "Person.h"
@interface Person ()
{
    NSString* _name;
    NSString* name;
    NSString* _isName;
    NSString* isName;
    NSInteger age;
    NSPoint location;
}
@end
@implementation Person

- (instancetype)init
{
    self = [super init];
    if (self) {
        _name    = @"_JayJay";
        _isName  = @"_isJayJay";
        name     = @"JayJay";
        isName   = @"isJayJay";
    }
    return self;
}
-(NSInteger)countOfName{
    NSLog(@"%s",__func__);
    return 3;
}
//具体能够参考NSArray里面 API方法
-(NSString*)objectInNameAtIndex:(NSInteger)index{
    NSLog(@"%s",__func__);
    return @"objectInNameAtIndex";
}
- (NSArray<NSString*> *)nameAtIndexes:(NSIndexSet *)indexes{
    NSLog(@"%s",__func__);
    return [@[@"111",@"222",@"333"] objectsAtIndexes:indexes];
}
//具体能够参考NSSet里面 API方法
-(NSEnumerator<NSString*> *)enumeratorOfName{
    NSLog(@"%s",__func__);
    return [@[@"111",@"222",@"333"] objectEnumerator];
}
- (nullable NSString*)memberOfName:(NSString*)object{

    NSLog(@"%s",__func__);
    return @"memberOfName";
}

-(NSString*)_name{
    NSLog(@"%s",__func__);
    return _name;
}
-(NSString*)_getName{
    NSLog(@"%s",__func__);
    return _isName;
}
-(NSString*)isName{
    NSLog(@"%s",__func__);
    return isName;
}
-(NSString*)name{
    NSLog(@"%s",__func__);
    return name;
}
-(NSString*)getName{
    NSLog(@"%s",__func__);
    return _name;
}
- (id)valueForKey:(NSString *)key{
    
    NSLog(@"%s",__func__);
    id temp = [super valueForKey:key];
    NSLog(@"%s",__func__);
    return temp;
}
+ (BOOL)accessInstanceVariablesDirectly{
    
    NSLog(@"%s",__func__);
    return [super accessInstanceVariablesDirectly];
}

@end
复制代码
#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Person* p = [[Person alloc] init];
        //[p valueForKey:@"_name"];方式会直接获取实例变量
        id result = [p valueForKey:@"name"];
        
        NSLog(@"result = %@",result);
              
    }
    return 0;
}
复制代码

断点调试,首先查找getName方法

image.png
注释掉getName方法,再次运行,会发现去查找name方法
image.png
注释掉name方法,再次运行,会发现去查找isName方法
image.png
注释掉isName方法,再次运行,会发现去查找_getName方法
image.png
注释掉_getName方法,再次运行,会发现去查找_name方法
image.png
注释掉_name方法,再次运行,会发现去优先匹配countOfName和objectInNameAtIndex:方法
image.png

注释掉objectInNameAtIndex:方法,再次运行,会发现匹配countOfName和nameAtIndexes:方法

image.png

注释掉objectInNameAtIndex:和 nameAtIndexes:方法,再次运行,会发现匹配countOfName,enumeratorOfName,memberOfName:方法(虽然没看到调用它,系统内部去处理了,注释掉这个方法,则会致使匹配失败)

image.png

注释掉全部简单getter方法,全部集合存取方法,则会去调用类方法accessInstanceVariablesDirectly(默认返回YES),若返回YES,则按照_name , _isName , name , isName顺序取查找,若找到,直接获取值返回 (相似上面setter过程,这里就再也不赘述了)

若是上述全部简单getter方法,全部集合存取方法以及实例变量都没有找到,则会调用valueForUndefinedKey:方法,抛出NSUnknownKeyException异常崩溃,能够重写该方法

-(id)valueForUndefinedKey:(NSString *)key{
    NSLog(@"%s",__func__);
    return NULL;
}
复制代码

image.png

NSKeyValueCoding类别中其余方法:

/*
默认返回YES,表示若是没有找到setter或者getter访问器方法 
就会按照_key,_iskey,key,iskey的顺序查找实例变量
若是返回NO,则跳过查找实例变量
*/
+ (BOOL)accessInstanceVariablesDirectly;
/*
对属性值正确性进行验证,它能够用来检查set的值是否正确,
同时为不正确的值作一个替换值或者拒绝设置新值并返回错误缘由
*/
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
/*
若是属性是一个NSMutableArray,那么能够用这个方法来返回一个集合NSMutableArray
对集合操做的API,里面还有一系列这样的API
*/
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
/*
若是没法搜索到任何和key有关的getter方法或者实例变量
则会调用这个方法,默认会抛出异常
*/
- (nullable id)valueForUndefinedKey:(NSString *)key;
/*
若是没法搜索到任何和key有关的setter方法或者实例变量
则会调用这个方法,默认会抛出异常
*/
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;

/*
若是在setValue过程当中,给非对象属性赋值nil,就会调用此方法,默认会抛出异常
*/
- (void)setNilValueForKey:(NSString *)key;
/*
输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model模型转字典
*/
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
/*
输入一个字典,给调用者对象各个成员变量赋值,用于将字典转Model模型
*/
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;

复制代码

还有访问集合属性的KVC方法:

image.png

KVC使用KeyPath

想象这样一种情形:一个类的成员变量多是自定义类型或者更复杂类型,咱们可使用KVC来操做这个属性,而后再次使用KVC对自定义的属性或者复杂类型进行操做,显然比较繁琐.因而苹果KVC给咱们提供了一个解决方案就是keyPath,就是按路径递归调用(使用“.”号分割)寻找key 来看具体代码:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Address : NSObject

@property (nonatomic, copy) NSString* street;

@end

NS_ASSUME_NONNULL_END


#import "Address.h"

@implementation Address

@end
复制代码
#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
          Person *p = [[Person alloc] init];
          p.address = [[Address alloc] init];
         [p setValue:@"中南路街道" forKeyPath:@"address.street"];
         NSLog(@"address.street = %@", [p valueForKeyPath:@"address.street"]);   
    }
    return 0;
}
复制代码

image.png

KVC异常处理

kvc中主要会出现两种异常:

  • 1.一种是UndefinedKey会抛出NSUnknownKeyException异常
  • 2.另外一种就是NilKey(给非对象类型变量赋值nil)会抛出NSInvalidArgumentException异常

这里NSUnknownKeyException异常上面已经提到过,下面来看NilKey异常

#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
           Person* p = [[Person alloc] init];
           //这里age实例变量是NSInteger 简单数值类型,赋值nil,KVC默认会抛出异常而崩溃
           [p setValue:nil forKey:@"age"];
        
    }
    return 0;
}
复制代码

image.png

能够重写Person类的setNilValueForKey方法

- (void)setNilValueForKey:(NSString *)key{
    
    NSLog(@"%s",__func__);
}
复制代码

image.png

KVC值类型和结构体类型处理

咱们仔细查看-(id)valueForKey:(NSString *)key 知道,方法老是返回一个对象,可是实际应用中彻底可能返回的不是对象。那么怎么去处理? 此外- (void)setValue:(id)value forUndefinedKey:(NSString *)key方法中传入的value也必须是对象类型,那又该怎么去处理?固然说的这些Apple早就给咱们处理好了。具体就是:对于设值操做,须要咱们本身对非对象数据类型(这里主要是值类型或者结构体类型)进使用NSNumber 或者 NSValue来包装.而对于取值操做,Apple已经使用NSNumber 或者 NSValue给咱们封装好了非对象数据类型,返回的就是NSNumber 或者 NSValue对象

NSNumber支持的值类型有哪些,具体能够参看Apple 提供的API:

@interface NSNumber (NSNumberCreation)

+ (NSNumber *)numberWithChar:(char)value;
+ (NSNumber *)numberWithUnsignedChar:(unsigned char)value;
+ (NSNumber *)numberWithShort:(short)value;
+ (NSNumber *)numberWithUnsignedShort:(unsigned short)value;
+ (NSNumber *)numberWithInt:(int)value;
+ (NSNumber *)numberWithUnsignedInt:(unsigned int)value;
+ (NSNumber *)numberWithLong:(long)value;
+ (NSNumber *)numberWithUnsignedLong:(unsigned long)value;
+ (NSNumber *)numberWithLongLong:(long long)value;
+ (NSNumber *)numberWithUnsignedLongLong:(unsigned long long)value;
+ (NSNumber *)numberWithFloat:(float)value;
+ (NSNumber *)numberWithDouble:(double)value;
+ (NSNumber *)numberWithBool:(BOOL)value;
+ (NSNumber *)numberWithInteger:(NSInteger)value API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
+ (NSNumber *)numberWithUnsignedInteger:(NSUInteger)value API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end
复制代码

NSValue支持的值类型(macOS 和 iOS有所不一样,这里展现的是macOS,具体能够参看API):

+ (NSValue *)valueWithPoint:(NSPoint)point;
+ (NSValue *)valueWithSize:(NSSize)size;
+ (NSValue *)valueWithRect:(NSRect)rect;
+ (NSValue *)valueWithEdgeInsets:(NSEdgeInsets)insets API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));
+ (NSValue *)valueWithRange:(NSRange)range;
复制代码

来看点代码:

#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {

            Person *p = [[Person alloc] init];
            //传入的value,是简单数值类型,须要咱们本身使用NSNumber来封装(也能够:[p setValue:@20] forKey:@"age"])
             [p setValue:[NSNumber numberWithInt:20] forKey:@"age"];
            //传入的value,是结构体类型,须要咱们本身使用NSValue来封装
           [p setValue:[NSValue valueWithPoint:CGPointMake(100, 100)] forKey:@"location"];
            //取值操做,这里返回的并非NSInteger类型,而是NSNumber类型
           id  age = [p valueForKey:@"age"]; 
           //取值操做,这里返回的并非NSPoint类型,而是NSValue类型
           id  location = [p valueForKey:@"location"];  
    }
    return 0;
} 
复制代码

image.png

KVC键值验证(Key-Value Validation)

KVC提供了对给定的key来对对应的value进行验证,以确保value是否可用。默认状况下KVC中不会自动调用,而CoreData在保存托管上下文时会自动验证(能够查看Core Data文档developer.apple.com/library/arc… )。 此外在macOS 中 ,Cocoa Bindings 也会建议您应该自动验证(能够查看 Cocoa Bindings相关文档 developer.apple.com/library/arc… )

Person类重写下面方法:

-(BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError *__autoreleasing  _Nullable *)outError{
    
     NSLog(@"%s",__func__);
     NSString* value = *ioValue;
     if ([value isEqualToString:@"JayJay"]) {
         return NO;
     }
     return [super validateValue:ioValue forKey:inKey error:outError];
}
复制代码

备注:该方法会在内部调用(BOOL)validateKey: error: 也就意味着咱们能够不须要上面方法,而直接使用它进行单独验证!

来看代码:

#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
            Person *p = [[Person alloc] init];
            NSString*key    = @"name";
            NSString*value  = @"JayJay";
            NSError* error;
            BOOL valid = [p validateValue:&value forKey:key error:&error];
            if (valid) {
                NSLog(@"键值匹配");
               [p setValue:value forKey:key];
            }else{
               NSLog(@"键值不匹配");
            }
   
    }
    return 0;
}
复制代码

image.png

再来看如何单独验证 注释掉

image.png

Person类中添加方法:

- (BOOL)validateName:(id *)ioValue error:(NSError * __autoreleasing *)outError{
    
    NSLog(@"%s",__func__);
    if ((*ioValue == nil) || ([(NSString *)*ioValue length] < 10)) {
        if (outError != NULL) {
            *outError = [NSError errorWithDomain:@"PersonErrorDomain"
                                            code:110
                                        userInfo:@{ NSLocalizedDescriptionKey
                                                    : @"姓名过短了!!!" }];
        }
        return NO;
    }
    return YES;
}
复制代码

image.png

KVC处理集合

集合运算符

1.png
向对象发送使用valueForKeyPath消息的时候,能够将集合运算符嵌入到路径中,先对集合中每一个元素进行操做(运算符定义的操做,具体取决于运算符), 而后再将结果返回。

  • Left key path 表示 要进行运算操做的对象.若是这个对象自己就是集合,那么Left key path能够省略不写
  • Collection operator 表示运算符具体操做,该符号指示对集合中元素进行某种操做
  • Right key path 表示要操做的属性

集合运算符分三类:

  • 1.聚合运算 以某种方式合并集合对象,而且返回 Righ key path指定属性相匹配的集合。 注意:@count是个例外,不须要指定Right key path,而且始终返回NSNumber实例
  • 2.数组运算 返回一个NSArray实例(返回数组NSArray中对象类型由操做对象属性决定)
  • 3.嵌套运算 嵌套运算符在包含其余集合的集合上工做,并返回NSArray或NSSet实例(取决于运算符),该实例以某种方式组合了嵌套集合的对象
1.聚合运算

聚合运算符(Collection operator)有5种: @avg, @count , @max , @min ,@sum

注意:这里聚合运算符只对NSArray,NSSet操做不包括NSDictionary(NSDictionary 仅仅支持@count操做)

来看点例子:

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSInteger age;

@end

@implementation Person

@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
      
        NSMutableArray* pArr = [NSMutableArray array];
        for (int i= 0; i< 5; i++) {
            
            Person* p = [[Person alloc ] init];
            p.age = i + 20;
            p.name = [NSString stringWithFormat:@"JayJay%d",i];
            [pArr addObject:p];
        }
        
        //@count特殊
        NSNumber* count = [pArr valueForKeyPath:@"@count"];
        NSLog(@"count: %d",count.intValue);
        
        NSNumber* sum = [pArr valueForKeyPath:@"@sum.age"];
        NSLog(@"sum: %d",sum.intValue);
        NSNumber* avg = [pArr valueForKeyPath:@"@avg.age"];
        NSLog(@"avg: %d",avg.intValue);
        NSNumber* min = [pArr valueForKeyPath:@"@min.age"];
        NSLog(@"min: %d",min.intValue);
        NSNumber* max = [pArr valueForKeyPath:@"@max.age"];
        NSLog(@"max: %d",max.intValue);
     
    }
    return 0;
}
复制代码

image.png

2.数组运算

数组运算有以下两种:

@distinctUnionOfObjects 返回一个去重数组 @unionOfObjects 返回一个不去重数组

注意:若是有一个对象为nil,就会形成异常崩溃

例子:

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSInteger age;

@end

@implementation Person

@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
      
        NSMutableArray* pArr = [NSMutableArray array];
        for (int i= 1; i <= 5; i++) {
            
            Person* p = [[Person alloc ] init];
            p.age = 20 + i%2;
            p.name = [NSString stringWithFormat:@"JayJay%d",i];
            [pArr addObject:p];
        }
        
       NSArray *distinctAgePersons = [pArr valueForKeyPath:@"@distinctUnionOfObjects.age"];
       NSArray *persons = [pArr valueForKeyPath:@"@unionOfObjects.age"];
      
    }
    return 0;
}
复制代码

这里操做属性是age,简单数值int类型,返回结果数组里面对应的就是NSNumber类型

image.png

3.嵌套运算

有三种操做符: @distinctUnionOfArrays @unionOfArrays @distinctUnionOfSets

代码:

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSInteger age;

@end

@implementation Person

@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
      
        NSMutableArray* pArr = [NSMutableArray array];
        for (int i= 1; i <= 5; i++) {
            
            Person* p = [[Person alloc ] init];
            p.age = 20 + i;
            p.name = [NSString stringWithFormat:@"JayJay%d",i];
            [pArr addObject:p];
       }
       NSMutableArray* moreArr = [NSMutableArray array];
        for (int i= 1; i <= 5; i++) {
            
            Person* p = [[Person alloc ] init];
            p.age = 22 + i;
            p.name = [NSString stringWithFormat:@"More%d",i];
            [moreArr addObject:p];
        }
        
       NSArray* arrayOfArrays = @[pArr, moreArr];
       NSArray *collectedDistinctPersons = [arrayOfArrays valueForKeyPath:@"@distinctUnionOfArrays.age"];
       NSArray *collectedPersons = [arrayOfArrays valueForKeyPath:@"@unionOfArrays.age"];
        
        
    }
    return 0;
}
复制代码

image.png

KVC处理字典

KVC很方便来处理字典和模型之间的转换,这里须要注意一点的就是要处理好KVC异常(上面已经提到过),来加强程序健壮性

- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
复制代码

示例代码:

#import <Foundation/Foundation.h>

@interface Person : NSObject
@property (nonatomic, copy)  NSString* name;
@property (nonatomic, copy)  NSString* sex;
@property (nonatomic, strong) NSArray* languages;
@property (nonatomic, assign) NSInteger age;

@end

@implementation Person

@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSDictionary * dic = @{
                @"name":@"JayJay",
                @"sex":@"男",
                @"age":@20,
                @"languages":@[@"C++",@"Objective-c",@"Swift",@"Dart",@"Python"]
            };
        
        Person* p = [[Person alloc] init];
        [p setValuesForKeysWithDictionary:dic];
        
        NSArray* keys = [dic allKeys];
        NSDictionary * dict = [p dictionaryWithValuesForKeys:keys];
        
        NSLog(@"dict = %@" ,dict);
              
    }
    return 0;
}
复制代码

image.png

KVC使用场景

  • 一、动态设置和取值
  • 二、访问或者修改私有变量
  • 三、模型和字典互转
  • 四、修改系统内部UI属性,好比替换系统自带的导航栏、tabbar, 个性化UITextField中的placeHolderText等
  • 五、操做集合
  • 六、Core Data
  • 七、AppleScript
  • 八、Cocoa bindings (苹果MVC)
  • 九、KVO
  • ...

写在最后: 在使用KVC过程当中,因为传入的key或者keyPath都是字符串,手动设置或者修改后很容易出错,从而容易Crash.这里咱们能够采用iOS反射机制尽可能避免这个问题.具体作法就是:经过@selector()获取到方法的SEL,而后经过NSStringFromSelector()将SEL反射为字符串,这样在@selector()中传入方法名的时候,编译器会有合法性检查,若是方法不存在或未实现,则会报黄色警告,这样就能够减小出错的几率

相关文章
相关标签/搜索