KVC原理分析

該文章屬于劉小壯原創(chuàng)吆豹,轉載請注明:劉小壯

配圖

在工作中經常會使用到KVC凤藏,但是很多人對于KVC的實現原理并不太清楚忙菠。比如說KVC在進行存取時燥滑,是怎么進行查找并賦值的雹洗。

網上有很多講KVC的文章香罐,但是有很多質量并不高。這兩天抽空把我所理解的KVC寫出來时肿,當做學習交流庇茫,正好也讓各位大神幫我指正一下,十分感謝螃成!


協議定義

KVC全稱是Key Value Coding旦签,定義在NSKeyValueCoding.h文件中,是一個非正式協議寸宏。KVC提供了一種間接訪問其屬性方法或成員變量的機制宁炫,可以通過字符串來訪問對應的屬性方法或成員變量。

NSKeyValueCoding Protocol

NSKeyValueCoding中提供了KVC通用的訪問方法氮凝,分別是getter方法valueForKey:setter方法setValue:forKey:羔巢,以及其衍生的keyPath方法,這兩個方法各個類通用的。并且由KVC提供默認的實現竿秆,我們也可以自己重寫對應的方法來改變實現启摄。

基礎操作

KVC主要對三種類型進行操作,基礎數據類型及常量幽钢、對象類型歉备、集合類型。

@interface BankAccount : NSObject
@property (nonatomic, strong) NSNumber *currentBalance;
@property (nonatomic, strong) Person *owner;
@property (nonatomic, strong) NSArray<Transaction *> *transactions;
@end

在使用KVC時搅吁,直接將屬性名當做key威创,并設置value,即可對屬性進行賦值谎懦。

[myAccount setValue:@(100.0) forKey:@"currentBalance"];

keyPath

除了對當前對象的屬性進行賦值外肚豺,還可以對其更“深層”的對象進行賦值。例如對當前對象的address屬性的street屬性進行賦值界拦。KVC進行多級訪問時吸申,直接類似于屬性調用一樣用點語法進行訪問即可。

[myAccount setValue:@"中關村大街" forKeyPath:@"address.street"];

通過keyPath對數組進行取值時享甸,并且數組中存儲的對象類型都相同截碴,可以通過valueForKeyPath:方法指定取出數組中所有對象的某個字段。例如下面例子中蛉威,通過valueForKeyPath:將數組中所有對象的name屬性值取出日丹,并放入一個數組中返回。

NSArray *names = [array valueForKeyPath:@"name"];

多值操作

需要注意的是蚯嫌,雖然看到dictionary的字樣哲虾,下面兩個方法并不是字典的方法。

KVC還有更強大的功能择示,可以根據給定的一組key束凑,獲取到一組value,并且以字典的形式返回栅盲,獲取到字典后可以通過key從字典中獲取到value汪诉。

- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;

同樣,也可以通過KVC進行批量賦值谈秫。在對象調用setValuesForKeysWithDictionary:方法時扒寄,可以傳入一個包含keyvalue的字典進去拟烫,KVC可以將所有數據按照屬性名和字典的key進行匹配旗们,并將valueUser對象的屬性賦值。

- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;

實用技巧

在項目中經常會遇到字典轉模型的情況构灸,如果在自定義的init方法里逐個賦值,這樣每次數據發(fā)生改變還需要改賦值語句。然而通過KVC為我們提供的賦值API喜颁,可以對數據進行批量賦值稠氮。假設有以下JSON數據并定義User類,在外界通過setValuesForKeysWithDictionary:方法對User進行賦值半开。

JSON數據:
{
    "username": "lxz",
    "age": 25,
    "id": 100
}

@interface User : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSString age;
@property (nonatomic, assign) NSInteger userId;
@end

@implementation User
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    if ([key isEqualToString:@"id"]) {
        self.userId = [value integerValue];
    }
}
@end

賦值時會遇到一些問題隔披,例如服務器會返回一個id字段,但是對于客戶端來說id是系統(tǒng)保留字段寂拆,可以重寫setValue:forUndefinedKey:方法并在內部處理id參數的賦值奢米。

轉換時需要服務器數據和類定義匹配,字段數量和字段名都應該匹配纠永。如果User比服務器數據多鬓长,則服務器沒傳的字段為空。如果服務端傳遞的數據User中沒有定義尝江,則會導致崩潰涉波。

KVC進行屬性賦值時,內部會對基礎數據類型做處理炭序,不需要手動做NSNumber的轉換啤覆。需要注意的是,NSArrayNSDictionary等集合對象惭聂,value都不能是nil窗声,否則會導致Crash

異常信息

當根據KVC搜索規(guī)則辜纲,沒有搜索到對應的key或者keyPath笨觅,則會調用對應的異常方法。異常方法的默認實現侨歉,在異常發(fā)生時會拋出一個NSUndefinedKeyException的異常屋摇,并且應用程序Crash

我們可以重寫下面兩個方法幽邓,根據業(yè)務需求合理的處理KVC導致的異常炮温。

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

異常處理

當通過KVC給某個非對象的屬性賦值為nil時,此時KVC會調用屬性所屬對象的setNilValueForKey:方法牵舵,并拋出NSInvalidArgumentException的異常柒啤,并使應用程序Crash

我們可以通過重寫下面方法畸颅,在發(fā)生這種異常時進行處理担巩。例如給name賦值為nil的時候,就可以重寫setNilValueForKey:方法并表示name是空的没炒。

- (void)setNilValueForKey:(NSString *)key {
    if ([key isEqualToString:@"name"]) {
        [self setValue:@"" forKey:@”age”];
    } else {
        [super setNilValueForKey:key];
    }
}

集合屬性操作

根據KVO的實現原理涛癌,是在運行時生成新的子類并重寫其setter方法,在其內容發(fā)生改變時發(fā)送消息。但這只是對屬性直接進行賦值會觸發(fā)拳话,如果屬性是容器對象先匪,對容器對象進行addremove操作,則不會調用KVO的方法弃衍⊙椒牵可以通過KVC對應的API來配合使用,使容器對象內部發(fā)生改變時也能觸發(fā)KVO镜盯。

在進行容器對象操作時岸裙,先調用下面方法通過key或者keyPath獲取集合對象,然后再對容器對象進行addremove等操作時速缆,就會觸發(fā)KVO的消息通知了降允。

- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;

keyPath方法:

- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;
- (NSMutableOrderedSet *)mutableOrderedSetValueForKeyPath:(NSString *)keyPath API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;

集合運算符

KVC提供的valueForKeyPath:方法非常強大,可以通過該方法對集合對象進行“深入”操作激涤,在其keyPath中嵌套集合運算符拟糕,例如求一個數組中對象某個屬性的count。(集合對象主要指NSArrayNSSet倦踢,但不包括NSDictionary)

集合運算符格式

上面表達式主要分為三部分送滞,left部分是要操作的集合對象,如果調用KVC的對象本來就是集合對象辱挥,則left可以為空犁嗅。中間部分是表達式,表達式一般以@符號開頭晤碘。后面是進行運算的屬性褂微。

集合運算符主要分為三類:

  1. 集合操作符:處理集合包含的對象,并根據操作符的不同返回不同的類型园爷,返回值以NSNumber為主宠蚂。
  2. 數組操作符:根據操作符的條件,將符合條件的對象包含在數組中返回童社。
  3. 嵌套操作符:處理集合對象中嵌套其他集合對象的情況求厕,返回結果也是一個集合對象。

example

下面是為了方便模擬KVC操作扰楼,而創(chuàng)建的測試代碼呀癣。定義Transaction類為模型類,類中包含三種類型的屬性弦赖。并定義BankAccount類项栏,其中包含一個數組,下面的代碼示例就都是操作這個數組的蹬竖,并且數組包含所有Transaction對象沼沈。

@interface Transaction : NSObject
@property (nonatomic, strong) NSString *payee;
@property (nonatomic, strong) NSNumber *amount;
@property (nonatomic, strong) NSDate *date;
@end
@interface BankAccount : NSObject
@property (nonatomic, strong) NSArray *transactions;
@end

集合操作符

集合操作符處理NSArrayNSSet及其子類這樣的集合對象流酬,并根據不同的操作符返回不同類型的對象,返回值一般都是NSNumber列另。

  • @avg用來計算集合中right keyPath指定的屬性的平均值康吵。
NSNumber *transactionAverage = [self.transactions valueForKeyPath:@"@avg.amount"];
  • @count用來計算集合的總數。
NSNumber *numberOfTransactions = [self.transactions valueForKeyPath:@"@count"];

備注:@count操作符比較特殊访递,它不需要寫right keyPath伦意,即使寫了也會被忽略汽煮。

  • @sum用來計算集合中right keyPath指定的屬性的總和。
NSNumber *amountSum = [self.transactions valueForKeyPath:@"@sum.amount"];
  • @max用來查找集合中right keyPath指定的屬性的最大值缴淋。
NSDate *latestDate = [self.transactions valueForKeyPath:@"@max.date"];
  • @min用來查找集合中right keyPath指定的屬性的最小值旱函。
NSDate *earliestDate = [self.transactions valueForKeyPath:@"@min.date"];

備注:@max@min在進行判斷時响巢,都是通過調用compare:方法進行判斷,所以可以通過重寫該方法對判斷過程進行控制棒妨。

數組操作符

  • @unionOfObjects將集合對象中踪古,所有payee對象放在一個數組中并返回。
NSArray *payees = [self.transactions valueForKeyPath:@"@unionOfObjects.payee"];
  • @distinctUnionOfObjects將集合對象中券腔,所有payee對象放在一個數組中伏穆,并將數組進行去重后返回。
NSArray *distinctPayees = [self.transactions valueForKeyPath:@"@distinctUnionOfObjects.payee"];

注意:以上兩個方法中纷纫,如果操作的屬性為nil枕扫,在添加到數組中時會導致Crash

嵌套操作符

由于嵌套操作符是需要對嵌套的集合對象進行操作辱魁,所以新建一個arrayOfArrays對象烟瞧,其中包含兩個數組,數組中存儲的都是Transaction類型對象染簇。

NSArray *moreTransactions = ....;
NSArray *arrayOfArrays = @[self.transactions, moreTransactions];
  • @unionOfArrays是用來操作集合內部的集合對象参滴,將所有right keyPath對應的對象放在一個數組中返回。
NSArray *collectedPayees = [arrayOfArrays valueForKeyPath:@"@unionOfArrays.payee"];
  • @distinctUnionOfArrays是用來操作集合內部的集合對象锻弓,將所有right keyPath對應的對象放在一個數組中砾赔,并進行排重。
NSArray *collectedDistinctPayees = [arrayOfArrays valueForKeyPath:@"@distinctUnionOfArrays.payee"];
  • @distinctUnionOfSets是用來操作集合內部的集合對象弥咪,將所有right keyPath對應的對象放在一個set中过蹂,并進行排重。
NSSet *collectedPayees = [arrayOfArrays valueForKeyPath:@"@distinctUnionOfSets.payee"];

小技巧

如果在集合對象中操作的屬性聚至,本來就是NSNumber類型酷勺,則可以像下面這樣,直接用self代表值自身扳躬。

NSArray *array = @[@(productA.price), @(productB.price), @(productC.price), @(productD.price)];
NSNumber *avg = [array valueForKeyPath:@"@avg.self"];

非對象值處理

KVC是支持基礎數據類型和結構體的脆诉,可以在settergetter的時候甚亭,通過NSValueNSNumber來轉換為OC對象。Swift中不存在這樣的需求击胜,因為Swift中所有變量都是對象亏狰。

以下是結構體轉換的示例代碼,可以調用initWithBool:方法對基礎數據類型進行包裝偶摔,除了調用方法外還可以通過字面量實現暇唾,例如@(YES)的調用。通過NSNumberboolValue屬性轉換為基礎數據類型辰斋。

@property (nonatomic, assign, readonly) BOOL boolValue;
- (NSNumber *)initWithBool:(BOOL)value NS_DESIGNATED_INITIALIZER;

結構體轉換的代碼定義在UIGeometry.h中策州,以NSValueCategory形式存在。NSValueCGPoint宫仗、CGRect等結構體都提供了轉換方法够挂,例如下面是對CGPoint進行轉換的示例代碼。

@property(nonatomic, assign, readonly) CGPoint CGPointValue;
+ (NSValue *)valueWithCGPoint:(CGPoint)point;

需要注意的是藕夫,無論什么時候都不應該給setter中傳入nil孽糖,會導致Crash并引起NSInvalidArgumentException異常。

屬性驗證

在調用KVC時可以先進行驗證毅贮,驗證通過下面兩個方法進行办悟,支持keykeyPath兩種方式。驗證方法默認實現返回YES嫩码,可以通過重寫對應的方法修改驗證邏輯誉尖。

驗證方法需要我們手動調用,并不會在進行KVC的過程中自動調用铸题。

- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError **)outError;

下面是使用驗證方法的例子铡恕。在validateValue方法的內部實現中,如果傳入的valuekey有問題丢间,可以通過返回NO來表示錯誤探熔,并設置NSError對象。

Person *person = [[Person alloc] init];
NSError *error;
NSString *name = @"John";
if (![person validateValue:&name forKey:@"name" error:&error]) {
    NSLog(@"%@", error);
}

單獨驗證

KVC還支持對單獨屬性做驗證烘挫,可以通過定義validate<Key>:error:格式的方法诀艰,并在方法內部實現驗證代碼。在編寫KVC驗證代碼的時候饮六,應該先查找屬性有沒有自定義validate方法其垄,然后再查找validateValue:方法,如果有則調用自己實現的方法卤橄,如果兩個方法都沒有實現則默認返回YES绿满。

- (BOOL)validateName:(id *)ioValue error:(NSError * __autoreleasing *)outError{
    if ((*ioValue == nil) || ([(NSString *)*ioValue length] < 2)) {
        if (outError != NULL) {
            *outError = [NSError errorWithDomain:PersonErrorDomain
                                            code:PersonInvalidNameCode
                                        userInfo:@{ NSLocalizedDescriptionKey
                                                    : @"Name too short" }];
        }
        return NO;
    }
    return YES;
}

我覺得KVC應該支持validateValue自動驗證,在調用setValuegetValue時自動進行驗證窟扑,如果不符合驗證規(guī)則喇颁,就調用失敗漏健。如果外界使用的地方都先調用一次validateValue的話,這是很麻煩的橘霎。當然也有解決方法蔫浆,可以通過Method Swizzling方法hooksetValuegetValue方法。

搜索規(guī)則

KVC在通過key或者keyPath進行操作的時候姐叁,可以查找屬性方法瓦盛、成員變量等,查找的時候可以兼容多種命名外潜。具體的查找規(guī)則要以官方文檔為主谭溉,所以我把官方文檔翻譯了一下寫在下面。

KVC的實現中橡卤,依賴settergetter的方法實現,所以方法命名應該符合蘋果要求的規(guī)范损搬,否則會導致KVC失敗碧库。

在學習KVC的搜索規(guī)則前,要先弄明白一個屬性的作用巧勤,這個屬性在搜索過程中起到很重要的作用嵌灰。這個屬性表示是否允許讀取實例變量的值,如果為YES則在KVC查找的過程中颅悉,從內存中讀取屬性實例變量的值沽瞭。

@property (class, readonly) BOOL accessInstanceVariablesDirectly;

基礎Getter搜索模式

這是valueForKey:的默認實現,給定一個key當做輸入參數剩瓶,開始下面的步驟驹溃,在這個接收valueForKey:方法調用的類內部進行操作。

  1. 通過getter方法搜索實例延曙,例如get<Key>, <key>, is<Key>, _<key>的拼接方案豌鹤。按照這個順序,如果發(fā)現符合的方法枝缔,就調用對應的方法并拿著結果跳轉到第五步布疙。否則,就繼續(xù)到下一步愿卸。

  2. 如果沒有找到簡單的getter方法灵临,則搜索其匹配模式的方法countOf<Key>objectIn<Key>AtIndex:趴荸、<key>AtIndexes:儒溉。

    如果找到其中的第一個和其他兩個中的一個,則創(chuàng)建一個集合代理對象赊舶,該對象響應所有NSArray的方法并返回該對象睁搭。否則赶诊,繼續(xù)到第三步。

    代理對象隨后將NSArray接收到的countOf<Key>园骆、objectIn<Key>AtIndex:舔痪、<key>AtIndexes:的消息給符合KVC規(guī)則的調用方。

    當代理對象和KVC調用方通過上面方法一起工作時锌唾,就會允許其行為類似于NSArray一樣锄码。

  3. 如果沒有找到NSArray簡單存取方法,或者NSArray存取方法組晌涕。則查找有沒有countOf<Key>滋捶、enumeratorOf<Key>memberOf<Key>:命名的方法余黎。

    如果找到三個方法重窟,則創(chuàng)建一個集合代理對象,該對象響應所有NSSet方法并返回惧财。否則巡扇,繼續(xù)執(zhí)行第四步。

    此代理對象隨后轉換countOf<Key>垮衷、enumeratorOf<Key>厅翔、memberOf<Key>:方法調用到創(chuàng)建它的對象上。實際上搀突,這個代理對象和NSSet一起工作刀闷,使得其表象上看起來是NSSet

  4. 如果沒有發(fā)現簡單getter方法仰迁,或集合存取方法組甸昏,以及接收類方法accessInstanceVariablesDirectly是返回YES的。搜索一個名為_<key>徐许、_is<Key>筒扒、<key>is<Key>的實例绊寻,根據他們的順序花墩。

    如果發(fā)現對應的實例,則立刻獲得實例可用的值并跳轉到第五步澄步,否則冰蘑,跳轉到第六步。

  5. 如果取回的是一個對象指針村缸,則直接返回這個結果祠肥。
    如果取回的是一個基礎數據類型,但是這個基礎數據類型是被NSNumber支持的梯皿,則存儲為NSNumber并返回仇箱。
    如果取回的是一個不支持NSNumber的基礎數據類型县恕,則通過NSValue進行存儲并返回。

  6. 如果所有情況都失敗剂桥,則調用valueForUndefinedKey:方法并拋出異常忠烛,這是默認行為。但是子類可以重寫此方法权逗。

基礎Setter搜索模式

這是setValue:forKey:的默認實現美尸,給定輸入參數valuekey。試圖在接收調用對象的內部斟薇,設置屬性名為keyvalue师坎,通過下面的步驟:

  1. 查找set<Key>:_set<Key>命名的setter,按照這個順序堪滨,如果找到的話胯陋,調用這個方法并將值傳進去(根據需要進行對象轉換)。

  2. 如果沒有發(fā)現一個簡單的setter袱箱,但是accessInstanceVariablesDirectly類屬性返回YES惶岭,則查找一個命名規(guī)則為_<key>_is<Key>犯眠、<key>is<Key>的實例變量症革。根據這個順序筐咧,如果發(fā)現則將value賦值給實例變量。

  3. 如果沒有發(fā)現setter或實例變量噪矛,則調用setValue:forUndefinedKey:方法量蕊,并默認提出一個異常,但是一個NSObject的子類可以提出合適的行為艇挨。

NSMutableArray搜索模式

這是mutableArrayValueForKey:的默認實現残炮,給一個key當做輸入參數。在接收訪問器調用的對象中缩滨,返回一個名為key的可變代理數組势就,這個代理數組就是用來響應外界KVO的對象,通過下面的步驟進行查找:

  1. 查找一對方法insertObject:in<Key>AtIndex:removeObjectFrom<Key>AtIndex:(相當于NSMutableArray的原始方法insertObject:atIndex:removeObjectAtIndex:)或者方法名是insert<Key>:atIndexes:remove<Key>AtIndexes:(相當于NSMutableArray的原始方法insertObjects:atIndexes:removeObjectsAtIndexes:)脉漏。

    如果找到最少一個insert方法和最少一個remove方法苞冯,則返回一個代理對象,來響應發(fā)送給NSMutableArray的組合消息insertObject:in<Key>AtIndex:侧巨、removeObjectFrom<Key>AtIndex:舅锄、insert<Key>:atIndexes:,和remove<Key>AtIndexes:消息司忱。

    當對象接收一個mutableArrayValueForKey:消息并實現可選替換方法皇忿,例如replaceObjectIn<Key>AtIndex:withObject:replace<Key>AtIndexes:with<Key>:方法畴蹭,代理對象會在適當的情況下使用它們,以獲得最佳性能鳍烁。

  2. 如果對象沒有可變數組方法叨襟,查找一個替代方法,命名格式為set<Key>:老翘。在這種情況下芹啥,向mutableArrayValueForKey:的原始響應者發(fā)送一個set<Key>:消息,來返回一個代理對象來響應NSMutableArray事件铺峭。

    提示:
    這一步描述的機制遠不如上一步有效墓怀,因為它可能重復創(chuàng)建新的集合對象,而不是修改現有的對象卫键。因此傀履,在自己設計的KVC時應該盡量避免它。

  3. 如果沒有可變數組的方法莉炉,也沒有找到訪問器钓账,但接受響應的類accessInstanceVariablesDirectly屬性返回YES,則查找一個名為_<key><key>的實例變量絮宁。

    按照這個順序梆暮,如果找到實例變量,則返回一個代理對象绍昂。改對象將接收所有NSMutableArray發(fā)送過來的消息啦粹,通常是NSMutableArray或其子類。

  4. 如果所有情況都失敗窘游,則返回一個可變的集合代理對象唠椭。當它接收NSMutableArray消息時,發(fā)送一個setValue:forUndefinedKey:消息給接收mutableArrayValueForKey:消息的原始對象忍饰。

    這個setValue:forUndefinedKey:的默認實現是提出一個NSUndefinedKeyException異常贪嫂,但是子類可以重寫這個實現。

其他搜索模式

還有NSMutableSetNSMutableOrderedSet兩種搜索模式艾蓝,這兩種搜索模式和NSMutableArray步驟相同力崇,只是搜索和調用的方法不同。詳細的搜索方法都可以在KVC官方文檔中找到赢织,再套用上面的流程即可理解餐曹。

代碼示例

根據上面KVC查找規(guī)則的描述,我們定義一個TestObject類敌厘,并指定其他settergetter台猴,以及合成為其他的成員變量,看KVC是否能夠找到屬性的對象并賦值。

@interface TestObject : NSObject {
    NSObject *_newObject;
}
@property (nonatomic, strong, setter=newSetObject:, getter=newObject) NSObject *object;
@property (nonatomic, strong) NSObject *twoObject;
@end

@implementation TestObject
@synthesize object = _newObject;
@end

這里對兩個屬性進行賦值饱狂,twoObject屬性賦值沒有任何問題曹步,而第二個屬性賦值則會導致Crash。崩潰信息如上面所述拋出一個NSUnknownKeyException異常休讳,并提示沒有找到object獲取方法和實例對象讲婚。

TestObject *object = [[TestObject alloc] init];
[object setValue:[NSObject new] forKey:NSStringFromSelector(@selector(twoObject))];
[object setValue:[NSObject new] forKey:NSStringFromSelector(@selector(object))];

如果將object改為newObject則可以解決這個問題,以此驗證上面的KVC查找規(guī)則俊柔。

KVC性能

根據上面KVC的實現原理筹麸,我們可以看出KVC的性能并不如直接訪問屬性快,雖然這個性能消耗是微乎其微的雏婶。所以在使用KVC的時候物赶,建議最好不要手動設置屬性的settergetter留晚,這樣會導致搜索步驟變長酵紫。

而且盡量不要用KVC進行集合操作,例如NSArray错维、NSSet之類的奖地,集合操作的性能消耗更大,而且還會創(chuàng)建不必要的對象赋焕。

私有訪問

根據上面的實現原理我們知道参歹,KVC本質上是操作方法列表以及在內存中查找實例變量。我們可以利用這個特性訪問類的私有變量隆判,例如下面在.m中定義的私有成員變量和屬性犬庇,都可以通過KVC的方式訪問。

這個操作對readonly的屬性蜜氨,@protected的成員變量,都可以正常訪問捎泻。如果不想讓外界訪問類的成員變量飒炎,則可以將accessInstanceVariablesDirectly屬性賦值為NO

TestObject.m文件

@interface TestObject () {
    NSObject *_objectOne;
}
@property (nonatomic, strong) NSObject *objectTwo;
@end

KVC在實踐中也有很多用處笆豁,例如UITabbarUIPageControl這樣的控件郎汪,系統(tǒng)已經為我們封裝好了,但是對于一些樣式的改變并沒有提供足夠的API闯狱,這種情況就需要我們用KVC進行操作了煞赢。

新浪微博

可以自定義一個UITabbar對象,然后在內部創(chuàng)建自己想要的視圖哄孤,并通過layoutSubviews方法在內部進行重新布局照筑。然后通過KVC的方式,將UITabbarControllertabbar屬性替換為自定義的類即可。

安全性檢查

KVC存在一個問題在于凝危,因為傳入的keykeyPath是一個字符串波俄,這樣很容易寫錯或者屬性自身修改后字符串忘記修改,這樣會導致Crash蛾默。

可以利用iOS的反射機制來規(guī)避這個問題懦铺,通過@selector()獲取到方法的SEL,然后通過NSStringFromSelector()SEL反射為字符串支鸡。這樣在@selector()中傳入方法名的過程中冬念,編譯器會有合法性檢查,如果方法不存在或未實現會報黃色警告牧挣。

[self valueForKey:NSStringFromSelector(@selector(object))];
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末急前,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子浸踩,更是在濱河造成了極大的恐慌叔汁,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件检碗,死亡現場離奇詭異据块,居然都是意外死亡,警方通過查閱死者的電腦和手機折剃,發(fā)現死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門另假,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人怕犁,你說我怎么就攤上這事边篮。” “怎么了奏甫?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵戈轿,是天一觀的道長。 經常有香客問我阵子,道長思杯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任挠进,我火速辦了婚禮色乾,結果婚禮上,老公的妹妹穿的比我還像新娘领突。我一直安慰自己暖璧,他們只是感情好,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布君旦。 她就那樣靜靜地躺著澎办,像睡著了一般嘲碱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上浮驳,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天悍汛,我揣著相機與錄音,去河邊找鬼至会。 笑死离咐,一個胖子當著我的面吹牛,可吹牛的內容都是我干的奉件。 我是一名探鬼主播宵蛀,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼县貌!你這毒婦竟也來了术陶?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤煤痕,失蹤者是張志新(化名)和其女友劉穎梧宫,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體摆碉,經...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡塘匣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了巷帝。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片忌卤。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖楞泼,靈堂內的尸體忽然破棺而出驰徊,到底是詐尸還是另有隱情,我是刑警寧澤堕阔,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布棍厂,位于F島的核電站,受9級特大地震影響超陆,放射性物質發(fā)生泄漏牺弹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一侥猬、第九天 我趴在偏房一處隱蔽的房頂上張望例驹。 院中可真熱鬧捐韩,春花似錦退唠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春垢油,著一層夾襖步出監(jiān)牢的瞬間盆驹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工滩愁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留躯喇,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓硝枉,卻偏偏與公主長得像廉丽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子妻味,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353