從新認識KVC

KVC

鍵值編碼是一種由NSKeyValueCoding非正式協議啓用的機制,對象採用該機制提供對其屬性的間接訪問。當對象符合鍵值編碼時,其屬性可經過字符串參數經過簡潔,統一的消息傳遞接口尋址。這種間接訪問機制補充了實例變量及其相關訪問器方法提供的直接訪問。html

您一般使用訪問器方法來訪問對象的屬性。get訪問器(或getter)返回屬性的值。set訪問器(或setter)設置屬性的值。在Objective-C中,您還能夠直接訪問屬性的基礎實例變量。以任何這些方式訪問對象屬性都很簡單,但須要調用特定於屬性的方法或變量名。隨着屬性列表的增加或變化,訪問這些屬性的代碼也必須如此。相反,符合鍵值編碼的對象提供了一個簡單的消息傳遞接口,該接口在其全部屬性中都是一致的。objective-c

鍵值編碼是一個基本概念,是許多其餘Cocoa技術的基礎,例如鍵值觀察,Cocoa綁定,核心數據和AppleScript能力。在某些狀況下,鍵值編碼還有助於簡化代碼。數組

Key-Value-Coding,譯名鍵值編碼,最多見的就是字典。在平常開中都或多或少使用過KVC,但有的時候仍是對KVC的取值和寫值的時候有些疑惑,基於探索官方文檔的描述來解決這些疑問。bash

成員變量、實例變量、屬性

在深刻KVC的流程以前,必須很清晰明瞭的知道這三者,還有一些開發的人不清楚這三者。下面是一份示例代碼:app

@interface Person: NSObject{
@public
NSString *name; // 成員變量
int age;        // 成員變量
Person *father; // 實例變量
id pet;         // 多是實例變量也可能不是
} // 括號裏的都爲成員變量
@property (nonatomic, copy) NSSting *birthday;  // 屬性
@end
複製代碼

上述的代碼已經標識了這三者的示例,也許有人會對"括號裏的都爲成員變量"有疑問,可是這句話是沒有問題的。理由是實例變量是成員變量的一種特殊類型。ui

因爲歷史緣由,如今零星的能夠看到一些這樣的代碼,Xcode編譯器底層從GCC升級成了LLVM,LLVM會對屬性自動生成setter和getter,若是屬性沒有匹配成員變量會自動生成一個帶下劃線的成員變量,@synthesize name = _name該語句會強制執行生成setter和getter方法,但不建議這樣作。這就是爲啥在項目中有時一個屬性name,有的地方使用self.name進行賦值,有的使用_name。編碼

KVC-賦值過程

  1. 先依次查詢有沒有相關的方法:set<Key>: _set<Key>: setIs<Key>: 找到直接進行調用賦值。
  2. 若沒有相關方法時,會查看類方法accessInstanceVariablesDirectly是否爲YES時進入下一步。不然進入步驟4
  3. 爲YES時,能夠直接訪問成員變量的來進行賦值,依次尋找變量_<key> _is<Key> <key> is<Key>。找到則直接賦值,不然進入下一步。
  4. 將會調用setValue:forUndefinedKey:方法進行拋出異常。能夠自定義的實現爲未找到的場景來避免拋出異常的行爲。

KVC-取值過程

  1. 先依次尋找是否有相關成員變量:get<Key><key>is<Key>,或者_<key>, 有則進入步驟4,不然下一步
  2. 查看類方法accessInstanceVariablesDirectly是否爲YES,是則進入下一步,不然進入步驟5
  3. 依次尋找_<key>_is<Key><key>,或者is<Key>成員變量是否有值,有則進入步驟4,不然進入步驟5
  4. 若是檢索到的屬性值是對象指針,則只返回結果。若是值是支持的標量類型NSNumber,則將其存儲在NSNumber實例中並 返回該值。若是結果是NSNumber不支持的標量類型,則轉換爲NSValue對象並返回該對象。
  5. 將會調用setValue:forUndefinedKey:方法進行拋出異常。能夠自定義的實現爲未找到的場景來避免拋出異常的行爲

KVC取值、賦值過程當中依次尋找的依據

上面的過程是查看官方文檔後並實操後得出的,確定有人會問是按順序的嗎?答案是確定的。setter和getter的時候咱們經過打印來得出的結果。下面咱們都寫了會尋找的幾種格式的成員變量,而且重寫了setter和getter。atom

@interface OMPerson : NSObject{
    @public
    NSString *name;
    NSString *_name;
    NSString *_isName;
    NSString *isName;
    
}

@property (nonatomic, copy) NSString *subject;

@end
複製代碼
- (void)setName:(NSString *)name{
    NSLog(@"%s",__func__);
}

- (void)_setName:(NSString *)name{
    NSLog(@"%s",__func__);
}
...
複製代碼
- (NSString *)getName{
    NSLog(@"%s",__func__);
    return NSStringFromSelector(_cmd);
}
- (NSString *)name{
    NSLog(@"%s",__func__);
    return NSStringFromSelector(_cmd);
}
...
複製代碼

若是你有時間寫出這些代碼去跑的時候就會發現是依順序的,不是隨機從哪幾種類型都去找的,畢竟也是消耗內存的。spa

KVC-探索

  1. 上述賦值和取值過程都是在NSObject子類中時的步驟,若是對象是數組、可變數組、可變有序集、可變集時的步驟則不一樣,能夠經過官方文檔詳細瞭解這個過程。指針

  2. 上述過程都說起accessInstanceVariablesDirectly這個類方法,默認爲YES。當賦值爲空時,系統會調用setNilValueForKey方法拋出NSInvalidArgumentException異常,若是是key爲空時,系統會調用setValue:forUndefinedKey方法拋出NSUnknownKeyException異常。

  3. validateValue該方法的工做原理是判斷是否實現了下述方法並自行決定返回布爾值來決定是否合法,默認返回YES。

    - (BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable __autoreleasing *)outError{
    
        if (*ioValue == nil || inKey == nil || inKey.length == 0) {
            NSLog(@"value 可能爲nil 或者key爲nil或者空值");
            return NO;
        }
      
        return YES;
    }
    複製代碼
  4. 說下keyPath的事,正常咱們使用都是能夠直接使用第一個方法,可是有些時候屬性多是個對象,須要修改屬性對象中的屬性值就須要使用keyPath了,例如:[person setValue: @"邊牧犬" forKeyPath: @"son.pet"]。也就是第二個方法。

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

- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
複製代碼

KVC在數組中的應用

1.數組平均值
NSNumber *average = [array valueForKeyPath:@"@avg.self"];
複製代碼
2.數組個數
int count = [[array valueForKeyPath: @"count.self"] intValue];
複製代碼
3.數組求和
int sum = [[array valueForKeyPath:@"@sum.self"] intValue];
複製代碼
4.數組最大值
float maxValue = [[array valueForKeyPath:@"@max.self"] floatValue];
複製代碼
5.數組最小值
float minValue = [[array valueForKeyPath:@"@min.self"] floatValue];
複製代碼
6.數組所有集合
NSArray* union = [array valueForKeyPath:@"@unionOfObjects.self"];
複製代碼
7.數組去重集合
NSArray* dis = [array valueForKeyPath:@"@distinctUnionOfObjects.self"];
複製代碼

上面的self表明數組元素自己,開發中每每都是一個數組中的對象,想計算每一個元素的某個屬性的值,就能夠把self換成身高height等等。

KVC的高階應用

1.數組中各個字符串的長度
NSArray *array = @[@"Gsas",@"Cotod",@"Kp",@"Crss"];
NSArray *lenStr= [array valueForKeyPath:@"length"];
複製代碼
2.數組中字符串的大小寫
NSArray *upStr= [array valueForKeyPath:@"uppercaseString"];
NSArray *lowStr= [array valueForKeyPath:@"lowercaseString"];
複製代碼
。。。

例如上面的用法,還有不少,例如首字母大小寫等等之類的,咱們能夠自行進行探索。

KVC的應用場景

- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
複製代碼
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
複製代碼

咱們從上面知道了一些屬性的賦值能夠經過KVC來進行,還能夠方便的操做集合和一些數組中的處理,咱們都知道YYModel相關的字典轉模型相關的庫依據代碼就能把字典解析成Model,當你閱讀過YYModel的源碼你就知道底層使用基於Runtime的KVC來實現的。KVC提供了簡單的字典轉模型和模型轉字典的API。簡單就是如下幾點:

  • 屬性賦值
  • 操做集合
  • 字典轉模型
  • 使用一些私有屬性(例如修改搜索框中的xx顏色)
相關文章
相關標籤/搜索