iOS KVC小結(jié)

iOS KVC小結(jié)

KVC的概念

KVC,即Key-value coding,鍵值編碼。給我們提供了一套更加直接的方式,來訪問一個(gè)對象的屬性,或者給對象的屬性賦值。而不是通過setter和getter方法。是一種可以直接通過名字或者key來獲取對象的屬性的機(jī)制。KVC的功能很強(qiáng)大,也屬于iOS的黑魔法之一。類的只讀屬性或者私有屬性(iOS 13以后,一些控件的私有屬性就不能獲取了,運(yùn)行時(shí)會報(bào)錯(cuò);但是自定義的類,用代碼測試還可以使用),只要能獲取到屬性的名字,使用KVC都可以進(jìn)行值的操作。

成員變量、屬性

開始之前,先來回憶一下成員變量、屬性的概念,以便于對后面的KVC的流程有更清楚的理解。

    @interface KVCPerson : NSObject
    {
        //成員變量
        NSString * _name;
    }
    //屬性
    @property (nonatomic, copy) NSString * address;
    @end

創(chuàng)建了一個(gè)KVCPerson類,代表一個(gè)人。一個(gè)人有名字、家庭住址。所以我們把名字添加為“成員變量”,把家庭住址添加為“屬性”。

  • 在大括號{}中聲明的變量都是成員變量。成員變量不會自動(dòng)生成settergetter方法。

    • 成員變量的取值/賦值,使用->。例如:p->name
  • OC中用@property來聲明一個(gè)屬性,編譯器會自動(dòng)為屬性生成“成員變量”,并且為成員變量生成對應(yīng)的settergetter方法。

    • 屬性可以使用“點(diǎn)語法”進(jìn)行操作。例如:p.name

    • 獲取KVCPerson類的IvarList和MethodList展示如下:

    //成員變量列表  
    mIvarList ==
        (
         "_name",
         "_address"
        )
    //方法列表
    mMthodList ==
         (
         getIvarList,
         getMethodList,
         //自動(dòng)生成的address的getter方法
         address,
         //自動(dòng)生成的address的setter方法
         "setAddress:",
         init,
         ".cxx_destruct"
        )

KVC常用的API

設(shè)置值的方法

    //給定一個(gè)值和一個(gè)屬性的鍵名,設(shè)置該屬性的值。
    - (void)setValue:(nullable id)value forKey:(NSString *)key;
    
    //給定一個(gè)值和一個(gè)“屬性的屬性”的鍵名,設(shè)置該“屬性的屬性”的值。like:address.city。
    - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
    
    //處理設(shè)置值的時(shí)候沒有找到鍵名的異常情況的API。找不到對應(yīng) key 命名的屬性時(shí),就會 NSUnknownKeyException 異常崩潰,可以在對象里重寫下面兩個(gè)方法,防止崩潰。
    - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
    
    //如果將非對象類型的屬性設(shè)置為 nil的情況下,會報(bào)NSInvalidArgumentException 的異常;可以使用這個(gè)方法來處理異常,防止崩潰
    - (void)setNilValueForKey:(NSString *)key;

在給屬性賦值的時(shí)候,KVC不會像setter、getter方法那么溫柔,對于類的只讀屬性或者私有屬性,settergetter是訪問不到的。
相比之下,KVC就會顯得比較粗暴。一個(gè)類的只讀屬性、私有屬性,都可以進(jìn)行賦值操作。

獲取值的方法

    //給定一個(gè)鍵名,獲取到該鍵對應(yīng)的值
    - (nullable id)valueForKey:(NSString *)key; 
    
    //給一個(gè)“屬性的屬性”的鍵名,獲取到該“屬性的屬性”的值。這個(gè)方法結(jié)合操作符做到一些比較好玩的事情
    - (nullable id)valueForKeyPath:(NSString *)keyPath; 
    
    //獲取值操作的情況下,沒有找到鍵名,會拋出異常。在類中可以重寫這個(gè)方法,防止程序的崩潰。
    - (nullable id)valueForUndefinedKey:(NSString *)key;

沒有setter/getter等方法情況下,根據(jù)這個(gè)方法的返回決定是否直接查詢成員變量

     //在找不到屬性的setter/getter方法的情況下,是否直接去查詢屬性的成員變量。默認(rèn)返回yes
    + (BOOL)accessInstanceVariablesDirectly;

屬性值正確性驗(yàn)證

    //屬性的正確性驗(yàn)證,默認(rèn)返回yes??梢詫σ粋€(gè)屬性進(jìn)行驗(yàn)證,如果符合邏輯條件就可以返回yes,否則返回false。
    - (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
使用案例:

驗(yàn)證年齡的合理性,如果年齡超過200歲就不合情理了,所以可以對年齡加上正確性驗(yàn)證。在KVCPerson類中增加一個(gè)屬性age,并添加驗(yàn)證代碼。

    //正確性驗(yàn)證
    - (BOOL) validateAge:(inout id  _Nullable __autoreleasing *)ioValue error:(out NSError * _Nullable __autoreleasing *)outError{
     NSNumber* value = (NSNumber*)*ioValue;
     if ([value integerValue] >= 0 && [value integerValue] <= 200) {
         return YES;
      }
     return NO;
    }

在使用KVCPerson類的時(shí)候,添加年齡的驗(yàn)證邏輯代碼

    KVCPerson * p = [[KVCPerson alloc] init];
    NSNumber * value = @200;
    if ([p validateValue:&value forKey:@"age" error:NULL]) {
         NSLog(@"我的數(shù)據(jù)不正確");
    }

KVC賦值的流程順序

在調(diào)用- (void)setValue:(nullable id)value forKey:(NSString *)key方法的時(shí)候,執(zhí)行的流程如下:

  • 查詢類中的有沒有相關(guān)的方法實(shí)現(xiàn):

    • 查詢類中有沒有set<key>:(NSString *)key的方法。有的話通過setter方法給屬性設(shè)值;否則繼續(xù)查找。

    • 查詢類中有沒有_set<key>:(NSString *)key的方法。有的話通過setter方法給屬性設(shè)值;否則繼續(xù)查找。

    • 查詢類中有沒有setIs<key>:(NSString *)key的方法。有的話通過setter方法給屬性設(shè)值;沒有的話,需要去查詢accessInstanceVariablesDirectly的返回值。

  • 沒有查詢到上述的方法的情況下,會去查詢accessInstanceVariablesDirectly這個(gè)方法的返回值(沒有重寫的話,默認(rèn)返回YES)。

    • 如果該方法的返回值是NO的情況下,會拋出NSUnknownKeyException異常

    • 如果該方法的返回值是YES的情況下,會去找類中的成員變量,并且按照一定的優(yōu)先級順序先匹配成員變量。獲取到匹配的成員變量后,直接賦值。

  • 沒有找到對應(yīng)的setter方法,直接給成員變量賦值的流程

    • 按照優(yōu)先級順序進(jìn)行匹配成員變量:_<key> > _is<Key> > <key> > is<Key>

    • 按照順序查詢到一個(gè)能匹配的成員變量后,就給該成員變量賦值。

    • 假設(shè)以上四種形式的成員變量都存在,只匹配優(yōu)先級最高的一個(gè)。

流程圖顯示如下:


setvalue.jpg

KVC取值的流程順序

調(diào)用- (nullable id)valueForKey:(NSString *)key方法的時(shí)候,執(zhí)行的流程如下:

  • 查詢類的方法列表中有沒有相關(guān)的getter方法實(shí)現(xiàn)

    • 查詢類中有沒有- (NSString *)get<Key>方法。有的話通過getter方法取出屬性的值;否則繼續(xù)查找。

    • 查詢類中有沒有- (NSString *)<key>方法。有的話通過getter方法取出屬性的值;否則繼續(xù)查找。

    • 查詢類中有沒有- (NSString *)is<Key>方法。有的話通過getter方法取出屬性的值;否則繼續(xù)查找。

    • 查詢類中有沒有- (NSString *)_<key>方法。有的話通過getter方法取出屬性的值;沒有的話,需要去查詢accessInstanceVariablesDirectly的返回值。

  • 沒有查詢到上述的方法的情況下,會去查詢accessInstanceVariablesDirectly這個(gè)方法的返回值(沒有重寫的話,默認(rèn)返回YES)

    • 如果該方法的返回值是NO的情況下,會拋出NSUnknownKeyException異常

    • 如果該方法的返回值是YES的情況下,會去找類中的成員變量,并且按照一定的優(yōu)先級順序先匹配成員變量。獲取到匹配的成員變量后,取出該成員變量的值。

  • 沒有找到對應(yīng)的getter方法,就需要直接查詢匹配的成員變量,然后取出該成員變量的值。

    • 按照優(yōu)先級順序進(jìn)行匹配成員變量:_<key> > _is<Key> > <key> > is<Key>

    • 按照順序查詢到一個(gè)能匹配的成員變量后,就取出該成員變量的值。

    • 假設(shè)以上四種形式的成員變量都存在,只匹配優(yōu)先級最高的一個(gè)。

流程圖顯示如下:


valueforkey.png

KVC集合操作符

在學(xué)習(xí)KVC的時(shí)候,看到了KVC集合操作符,眼前一亮啊。所以,為了以后能夠隨時(shí)看一下,在這里進(jìn)行記錄。

KVC集合操作符允許在valueForKeyPath:方法中使用操作運(yùn)算,作用于集合中所有的元素,來獲取到想要的成果。

集合操作符根據(jù)其返回值的不同,分為三個(gè)類型:

  • 簡單的集合操作符,返回的是 strings, number, 或者 dates
    * @count: 返回的值為集合中對象總數(shù),是`NSNumber`類型數(shù)據(jù)。
    * @sum  : 首先把集合中的每個(gè)對象都轉(zhuǎn)換為`double`類型,然后計(jì)算其總和,最后返回值為這個(gè)總和的`NSNumber`對象。
    * @avg  : 把集合中的每個(gè)對象都轉(zhuǎn)換為`double`類型,然后計(jì)算其平均值,返回值為平均值的`NSNumber`對象。
    * @max  : 使用`compare:`方法來確定最大值。所以為了讓其正常工作,集合中所有的對象都必須支持和另一個(gè)對象的比較。
    * @min  : 和`@max`一樣,但是返回的是集合中的最小值。
  • 對象操作符,返回的是一個(gè)數(shù)組
    @unionOfObjects / @distinctUnionOfObjects: 返回一個(gè)由操作符右邊的 key path 所指定的對象屬性組成的數(shù)組。兩個(gè)方法中@distinctUnionOfObjects會對數(shù)組去重, 而且@unionOfObjects不會.</pre>
  • 數(shù)組和集合操作符, 返回的是一個(gè)數(shù)組或者集合
    @distinctUnionOfArrays / @unionOfArrays: 返回了一個(gè)數(shù)組,其中包含這個(gè)集合中每個(gè)數(shù)組對于這個(gè)操作符右面指定的 key path 進(jìn)行操作之后的值。正如你期望的,distinct版本會移除重復(fù)的值。
    
    @distinctUnionOfSets: 和@distinctUnionOfArrays差不多, 但是它期望的是一個(gè)包含著NSSet對象的NSSet,并且會返回一個(gè)NSSet對象。因?yàn)榧喜荒馨貜?fù)的值,所以它只有distinct操作。</pre>

事例數(shù)據(jù):

name price date
iPhone 5 199 September 21, 2012
iPad Mini 329 November 2, 2012
MacBook Pro 1699 June 11, 2012
iMac 1299 November 2, 2012

創(chuàng)建一個(gè)類,類名為Product,如下。把以上數(shù)據(jù)包裝成對象,保存到productArray數(shù)組中。

@interface Product : NSObject
//產(chǎn)品名稱
@property (nonatomic, copy) NSString * name;
//產(chǎn)品價(jià)格
@property (nonatomic, assign) int price;
//產(chǎn)品的上市時(shí)間
@property (nonatomic, strong) NSDate * date;
@end
  • 簡單的集合操作符,應(yīng)用實(shí)例:
        NSString * count = [self.productArray valueForKeyPath:@"@count"]; 
        NSString * sum_price = [self.productArray valueForKeyPath:@"@sum.price"]; 
        NSString * avg_pric = [self.productArray valueForKeyPath:@"@avg.price"]; 
        NSString * max_price = [self.productArray valueForKeyPath:@"@max.price"]; 
        NSString * min_date = [self.productArray valueForKeyPath:@"@min.date"]; 

        NSLog(@"count is %@",count);            -----> count is 4
        NSLog(@"sum_price is %@",sum_price);    -----> sum_price is 3526
        NSLog(@"avg_pric is %@",avg_pric);      -----> avg_pric is 881.5
        NSLog(@"max_price is %@",max_price);    -----> max_price is 1699
        NSLog(@"min_date is %@",min_date);      -----> min_date is June 11, 2012
  • 對象操作符,應(yīng)用實(shí)例(在productArray數(shù)組中,使用第一條數(shù)據(jù)重復(fù)生成多次對象,能看到兩個(gè)方法的區(qū)別):
        //@unionOfObjects方法,不會對數(shù)組去重
        NSArray *unionOfObjects = [self.productArray valueForKeyPath:@"@unionOfObjects.name"];
        //@distinctUnionOfObjects 會對數(shù)組去重
        NSArray *distinctUnionOfObjects = [self.productArray valueForKeyPath:@"@distinctUnionOfObjects.name"];
        //打印數(shù)據(jù)
        NSLog(@"unionOfObjects is %@",unionOfObjects);
        NSLog(@"distinctUnionOfObjects is %@",distinctUnionOfObjects);
        -----------------------------------------------------------------------------------------------------
         unionOfObjects is (
          "iPhone 5",
          "iPhone 5",
          "iPhone 5",
          "iPad Mini",
          "MacBook Pro",
          iMac
        )

        distinctUnionOfObjects is (
          iMac,
          "iPad Mini",
          "MacBook Pro",
          "iPhone 5"
        )
  • 數(shù)組和集合操作符, 應(yīng)用實(shí)例。(再復(fù)制一份商品數(shù)據(jù),稍加修改后,包裝成對象存放在一個(gè)數(shù)組中。并與存放上份數(shù)據(jù)的數(shù)組放在同一個(gè)數(shù)組中。)。集合運(yùn)算符與數(shù)組操作法是相似的,此處不做贅述。
        //把兩份產(chǎn)品數(shù)據(jù)存放到同一個(gè)數(shù)組中
        NSArray *arraysInArray = @[self.productArray, newProductArray];
        //@unionOfArrays方法,不會對數(shù)據(jù)去重
        NSArray *unionOfArrays = [arraysInArray valueForKeyPath:@"@unionOfArrays.name"];
        //@distinctUnionOfArrays方法,會對獲取到的數(shù)據(jù)去重
        NSArray *distinctUnionOfArrays = [arraysInArray valueForKeyPath:@"@distinctUnionOfArrays.name"];
        //打印數(shù)據(jù)
        NSLog(@"unionOfArrays is %@",unionOfArrays);
        NSLog(@"distinctUnionOfArrays is %@",distinctUnionOfArrays);
            -----------------------------------------------------------------------------------------------
        unionOfArrays is (
            "iPhone 5",
            "iPhone 5",
            "iPhone 5",
            "iPad Mini",
            "MacBook Pro",
            iMac,
            "iPhone 5 - 1",
            "iPhone 5 - 1",
            "iPhone 5 - 1",
            "iPad Mini - 1",
            "MacBook Pro - 1",
            "iMac - 1"
        ),
        distinctUnionOfArrays is (
            "iPhone 5 - 1",
            "iPad Mini - 1",
            "iPhone 5",
            "iPad Mini",
            "iMac - 1",
            "MacBook Pro - 1",
            "MacBook Pro",
            iMac
        )
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容