探討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()中傳入方法名的時候,編譯器會有合法性檢查,若是方法不存在或未實現,則會報黃色警告,這樣就能夠減小出錯的機率

相關文章
相關標籤/搜索