天天看點

ios 通過kvc修改屬性會觸發kvo_iOS探索 KVC原理及自定義

寫在前面

平常開發中經常用到KVC指派取值、字典轉模型,但KVC的底層原理又是怎樣的呢?

Demo

一、KVC初探

1.KVC定義及API

KVC(Key-Value Coding)

是利用

NSKeyValueCoding

非正式協定實作的一種機制,對象采用這種機制來提供對其屬性的間接通路

寫下KVC代碼并點選跟進

setValue

會發現

NSKeyValueCoding

是在

Foundation

架構下

  • KVC通過對

    NSObject

    的擴充來實作的——所有內建了

    NSObject

    的類可以使用KVC
  • NSArray、NSDictionary、NSMutableDictionary、NSOrderedSet、NSSet

    等也遵守KVC協定
  • 除少數類型(結構體)以外都可以使用KVC

int main(int argc, const char * argv[]) { @autoreleasepool { FXPerson *person = [FXPerson new]; [person setValue:@"Felix" forKey:@"name"]; [person setValue:@"Felix" forKey:@"nickname"]; } return 0; } 複制代碼

KVC

常用方法,這些也是我們在日常開發中經常用到的

// 通過 key 設值 - (void)setValue:(nullable id)value forKey:(NSString *)key; // 通過 key 取值 - (nullable id)valueForKey:(NSString *)key; // 通過 keyPath 設值 - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; // 通過 keyPath 取值 - (nullable id)valueForKeyPath:(NSString *)keyPath; 複制代碼

NSKeyValueCoding

類别的其它方法

// 預設為YES。 如果傳回為YES,如果沒有找到 set<Key> 方法的話, 會按照_key, _isKey, key, isKey的順序搜尋成員變量, 傳回NO則不會搜尋 + (BOOL)accessInstanceVariablesDirectly; // 鍵值驗證, 可以通過該方法檢驗鍵值的正确性, 然後做出相應的處理 - (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError; // 如果key不存在, 并且沒有搜尋到和key有關的字段, 會調用此方法, 預設抛出異常。兩個方法分别對應 get 和 set 的情況 - (nullable id)valueForUndefinedKey:(NSString *)key; - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key; // setValue方法傳 nil 時調用的方法 // 注意文檔說明: 當且僅當 NSNumber 和 NSValue 類型時才會調用此方法 - (void)setNilValueForKey:(NSString *)key; // 一組 key對應的value, 将其轉成字典傳回, 可用于将 Model 轉成字典 - (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys; 複制代碼

2.拓展——自動生成的setter和getter方法

試想一下編譯器要為成千上萬個屬性分别生成

setter

getter

方法那不得歇菜了嘛

于是乎蘋果開發者們就運用

通用原則

給所有屬性都提供了同一個入口——

objc-accessors.mm

setter

方法根據

修飾符不同

調用不同方法,最後統一調用

reallySetProperty

方法

ios 通過kvc修改屬性會觸發kvo_iOS探索 KVC原理及自定義

來到

reallySetProperty

再根據記憶體偏移量取出屬性,根據修飾符完成不同的操作

  • 在第一個屬性

    name

    指派時,此時的記憶體偏移量為8,剛好偏移

    isa

    所占記憶體(8位元組)來到

    name

  • 在第二個屬性

    nickname

    指派時,此時的記憶體偏移量為16,剛好偏移

    isa、name

    所占記憶體(8+8)來到

    nickname

ios 通過kvc修改屬性會觸發kvo_iOS探索 KVC原理及自定義

至于是哪裡調用的

objc_setProperty_nonatomic_copy

并不是在objc源碼中,而在llvm源碼中發現了它,根據它一層層找上去就能找到源頭

ios 通過kvc修改屬性會觸發kvo_iOS探索 KVC原理及自定義

二、KVC使用

相信大部分閱讀本文的小夥伴們都對KVC的使用都比較了解了,但筆者建議還是看一下查漏補缺

typedef struct { float x, y, z; } ThreeFloats; @interface FXPerson : NSObject @property (nonatomic, copy) NSString *name; @property (nonatomic, assign) NSInteger age; @property (nonatomic, copy) NSArray *family; @property (nonatomic) ThreeFloats threeFloats; @property (nonatomic, strong) FXFriend *friends; @end @interface FXFriend : NSObject @property (nonatomic, copy) NSString *name; @property (nonatomic, assign) NSInteger age; @end 複制代碼

1.基本類型

注意一下NSInteger這類的屬性指派時要轉成NSNumber或NSString

FXPerson *person = [FXPerson new]; [person setValue:@"Felix" forKey:@"name"]; [person setValue:@(18) forKey:@"age"]; NSLog(@"名字%@ 年齡%@", [person valueForKey:@"name"], [person valueForKey:@"age"]); 複制代碼

列印結果:

2020-03-08 14:06:20.913692+0800 FXDemo[2998:151140] 名字Felix 年齡18 複制代碼

2.集合類型

兩種方法對數組進行指派,更推薦使用第二種方法

FXPerson *person = [FXPerson new]; person.family = @[@"FXPerson", @"FXFather"]; // 直接用新的數組指派 NSArray *temp = @[@"FXPerson", @"FXFather", @"FXMother"]; [person setValue:temp forKey:@"family"]; NSLog(@"第一次改變%@", [person valueForKey:@"family"]); // 取出數組以可變數組形式儲存,再修改 NSMutableArray *mTemp = [person mutableArrayValueForKeyPath:@"family"]; [mTemp addObject:@"FXChild"]; NSLog(@"第二次改變%@", [person valueForKey:@"family"]); 複制代碼

列印結果:

2020-03-08 14:06:20.913794+0800 FXDemo[2998:151140] 第一次改變( FXPerson, FXFather, FXMother ) 2020-03-08 14:06:20.913945+0800 FXDemo[2998:151140] 第二次改變( FXPerson, FXFather, FXMother, FXChild ) 複制代碼

3.通路非對象類型——結構體

  • 對于非對象類型的指派總是把它先轉成NSValue類型再進行存儲
  • 取值時轉成對應類型後再使用

FXPerson *person = [FXPerson new]; // 指派 ThreeFloats floats = {180.0, 180.0, 18.0}; NSValue *value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)]; [person setValue:value forKey:@"threeFloats"]; NSLog(@"非對象類型%@", [person valueForKey:@"threeFloats"]); // 取值 ThreeFloats th; NSValue *currentValue = [person valueForKey:@"threeFloats"]; [currentValue getValue:&th]; NSLog(@"非對象類型的值%f-%f-%f", th.x, th.y, th.z); 複制代碼

列印結果:

2020-03-08 14:06:20.914088+0800 FXDemo[2998:151140] 非對象類型{length = 12, bytes = 0x000034430000344300009041} 2020-03-08 14:06:20.914182+0800 FXDemo[2998:151140] 非對象類型的值180.000000-180.000000-18.000000 2020-03-08 14:06:20.914333+0800 FXDemo[2998:151140] ( 18, 19, 20, 21, 22, 23 ) 複制代碼

4.集合操作符

  • 聚合操作符
    • @avg

      : 傳回操作對象指定屬性的平均值
    • @count

      : 傳回操作對象指定屬性的個數
    • @max

      : 傳回操作對象指定屬性的最大值
    • @min

      : 傳回操作對象指定屬性的最小值
    • @sum

      : 傳回操作對象指定屬性值之和
  • 數組操作符
    • @distinctUnionOfObjects

      : 傳回操作對象指定屬性的集合--去重
    • @unionOfObjects

      : 傳回操作對象指定屬性的集合
  • 嵌套操作符
    • @distinctUnionOfArrays

      : 傳回操作對象(嵌套集合)指定屬性的集合--去重,傳回的是 NSArray
    • @unionOfArrays

      : 傳回操作對象(集合)指定屬性的集合
    • @distinctUnionOfSets

      : 傳回操作對象(嵌套集合)指定屬性的集合--去重,傳回的是 NSSet

集合操作符用得少之又少。下面舉個

FXPerson *person = [FXPerson new]; NSMutableArray *friendArray = [NSMutableArray array]; for (int i = 0; i < 6; i++) { FXFriend *f = [FXFriend new]; NSDictionary* dict = @{ @"name":@"Felix", @"age":@(18+i), }; [f setValuesForKeysWithDictionary:dict]; [friendArray addObject:f]; } NSLog(@"%@", [friendArray valueForKey:@"age"]); float avg = [[friendArray valueForKeyPath:@"@avg.age"] floatValue]; NSLog(@"平均年齡%f", avg); int count = [[friendArray valueForKeyPath:@"@count.age"] intValue]; NSLog(@"調查人口%d", count); int sum = [[friendArray valueForKeyPath:@"@sum.age"] intValue]; NSLog(@"年齡總和%d", sum); int max = [[friendArray valueForKeyPath:@"@max.age"] intValue]; NSLog(@"最大年齡%d", max); int min = [[friendArray valueForKeyPath:@"@min.age"] intValue]; NSLog(@"最小年齡%d", min); 複制代碼

列印結果:

2020-03-08 14:06:20.914503+0800 FXDemo[2998:151140] 平均年齡20.500000 2020-03-08 14:06:20.914577+0800 FXDemo[2998:151140] 調查人口6 2020-03-08 14:06:20.914652+0800 FXDemo[2998:151140] 年齡總和123 2020-03-08 14:06:20.914739+0800 FXDemo[2998:151140] 最大年齡23 2020-03-08 14:06:20.914832+0800 FXDemo[2998:151140] 最小年齡18 複制代碼

5.層層嵌套

通過

forKeyPath

對執行個體變量(friends)進行取值指派

FXPerson *person = [FXPerson new]; FXFriend *f = [[FXFriend alloc] init]; f.name = @"Felix的朋友"; f.age = 18; person.friends = f; [person setValue:@"Feng" forKeyPath:@"friends.name"]; NSLog(@"%@", [person valueForKeyPath:@"friends.name"]); 複制代碼

列印結果:

2020-03-08 14:06:20.914927+0800 FXDemo[2998:151140] Feng 複制代碼

三、KVC底層原理

由于

NSKeyValueCoding

的實作在

Foundation

架構,但它又不開源,我們隻能通過KVO官方文檔來了解它

1.設值過程

官方文檔上對Setter方法的過程進行了這樣一段講解

ios 通過kvc修改屬性會觸發kvo_iOS探索 KVC原理及自定義
  1. set<Key>:

    _set<Key>:

    順序查找對象中是否有對應的方法
  • 找到了直接調用設值
  • 沒有找到跳轉第2步
  1. 判斷

    accessInstanceVariablesDirectly

    結果
  • 為YES時按照

    _<key>

    _is<Key>

    <key>

    is<Key>

    的順序查找成員變量,找到了就指派;找不到就跳轉第3步
  • 為NO時跳轉第3步
  1. 調用

    setValue:forUndefinedKey:

    。預設情況下會引發一個異常,但是繼承于

    NSObject

    的子類可以重寫該方法就可以避免崩潰并做出相應措施
ios 通過kvc修改屬性會觸發kvo_iOS探索 KVC原理及自定義

2.取值過程

同樣的官方文檔上也給出了Getter方法的過程

ios 通過kvc修改屬性會觸發kvo_iOS探索 KVC原理及自定義
  1. 按照

    get<Key>

    <key>

    is<Key>

    _<key>

    順序查找對象中是否有對應的方法
  • 如果有則調用getter,執行第5步
  • 如果沒有找到,跳轉到第2步
  1. 查找是否有

    countOf<Key>

    objectIn<Key>AtIndex:

    方法(對應于

    NSArray

    類定義的原始方法)以及

    <key>AtIndexes:

    方法(對應于

    NSArray

    方法

    objectsAtIndexes:

    )
  • 如果找到其中的第一個

    (countOf<Key>)

    ,再找到其他兩個中的至少一個,則建立一個響應所有 NSArray方法的代理集合對象,并傳回該對象(即要麼是

    countOf<Key> + objectIn<Key>AtIndex:

    ,要麼是

    countOf<Key> + <key>AtIndexes:

    ,要麼是

    countOf<Key> + objectIn<Key>AtIndex: + <key>AtIndexes:

    )
  • 如果沒有找到,跳轉到第3步
  1. 查找名為

    countOf<Key>

    enumeratorOf<Key>

    memberOf<Key>

    這三個方法(對應于

    NSSet

    類定義的原始方法)
  • 如果找到這三個方法,則建立一個響應所有

    NSSet

    方法的代理集合對象,并傳回該對象
  • 如果沒有找到,跳轉到第4步
  1. 判斷

    accessInstanceVariablesDirectly

  • 為YES時按照

    _<key>

    _is<Key>

    <key>

    is<Key>

    的順序查找成員變量,找到了就取值
  • 為NO時跳轉第6步
  1. 判斷取出的屬性值
  • 屬性值是對象,直接傳回
  • 屬性值不是對象,但是可以轉化為

    NSNumber

    類型,則将屬性值轉化為

    NSNumber

    類型傳回
  • 屬性值不是對象,也不能轉化為

    NSNumber

    類型,則将屬性值轉化為

    NSValue

    類型傳回
  1. 調用

    valueForUndefinedKey:

    。預設情況下會引發一個異常,但是繼承于

    NSObject

    的子類可以重寫該方法就可以避免崩潰并做出相應措施
ios 通過kvc修改屬性會觸發kvo_iOS探索 KVC原理及自定義

四、自定義KVC

根據KVC的設值過程、取值過程,我們可以自定義KVC的setter方法和getter方法,但是這一切都是根據官方文檔做出的猜測,自定義KVC隻能在一定程度上取代系統KVC,大緻流程幾乎一緻:實作了 setValue:forUndefinedKey: 、 valueForUndefinedKey: 的調用,且 accessInstanceVariablesDirectly 無論為true為false,都能保持兩次調用

建立一個

NSObject+FXKVC

的分類,.h開放兩個方法,.m引入

<objc/runtime.h>

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

  • - (nullable id)fx_valueForKey:(NSString *)key;

1.自定義setter方法

  1. 非空判斷

if (key == nil || key.length == 0) return; 複制代碼

  1. 找到相關方法

    set<Key>

    _set<Key>

    setIs<Key>

    ,若存在就直接調用

NSString *Key = key.capitalizedString; NSString *setKey = [NSString stringWithFormat:@"set%@:",Key]; NSString *_setKey = [NSString stringWithFormat:@"_set%@:",Key]; NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",Key]; if ([self fx_performSelectorWithMethodName:setKey value:value]) { NSLog(@"*********%@**********",setKey); return; } else if ([self fx_performSelectorWithMethodName:_setKey value:value]) { NSLog(@"*********%@**********",_setKey); return; } else if ([self fx_performSelectorWithMethodName:setIsKey value:value]) { NSLog(@"*********%@**********",setIsKey); return; } 複制代碼

  1. 判斷是否能夠直接指派執行個體變量,不能的情況下就調用

    setValue:forUndefinedKey:

    或抛出異常

NSString *undefinedMethodName = @"setValue:forUndefinedKey:"; IMP undefinedIMP = class_getMethodImplementation([self class], NSSelectorFromString(undefinedMethodName)); if (![self.class accessInstanceVariablesDirectly]) { if (undefinedIMP) { [self fx_performSelectorWithMethodName:undefinedMethodName value:value key:key]; } else { @throw [NSException exceptionWithName:@"FXUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key %@.", self, NSStringFromSelector(_cmd), key] userInfo:nil]; } return; } 複制代碼

  1. 找相關執行個體變量進行指派

NSMutableArray *mArray = [self getIvarListName]; NSString *_key = [NSString stringWithFormat:@"_%@",key]; NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key]; NSString *isKey = [NSString stringWithFormat:@"is%@",Key]; if ([mArray containsObject:_key]) { Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String); object_setIvar(self , ivar, value); return; } else if ([mArray containsObject:_isKey]) { Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String); object_setIvar(self , ivar, value); return; } else if ([mArray containsObject:key]) { Ivar ivar = class_getInstanceVariable([self class], key.UTF8String); object_setIvar(self , ivar, value); return; } else if ([mArray containsObject:isKey]) { Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String); object_setIvar(self , ivar, value); return; } 複制代碼

  1. 調用

    setValue:forUndefinedKey:

    或抛出異常

if (undefinedIMP) { [self fx_performSelectorWithMethodName:undefinedMethodName value:value key:key]; } else { @throw [NSException exceptionWithName:@"FXUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key %@.", self, NSStringFromSelector(_cmd), key] userInfo:nil]; } 複制代碼

在這裡筆者存在一個疑問:沒有實作setValue:forUndefinedKey:時,目前類可以響應respondsToSelector這個方法,但是直接performSelector會崩潰,是以改用了判斷IMP是否為空

2.自定義getter方法

  1. 非空判斷

if (key == nil || key.length == 0) return nil; 複制代碼

  1. 找相關方法

    get<Key>

    <key>

    ,找到就傳回(這裡使用

    -Warc-performSelector-leaks

    消除警告)

NSString *Key = key.capitalizedString; NSString *getKey = [NSString stringWithFormat:@"get%@",Key]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" if ([self respondsToSelector:NSSelectorFromString(getKey)]) { return [self performSelector:NSSelectorFromString(getKey)]; } else if ([self respondsToSelector:NSSelectorFromString(key)]) { return [self performSelector:NSSelectorFromString(key)]; } #pragma clang diagnostic pop 複制代碼

  1. NSArray

    進行操作:查找

    countOf<Key>

    objectIn<Key>AtIndex

    方法

NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",Key]; NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",Key]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" if ([self respondsToSelector:NSSelectorFromString(countOfKey)]) { if ([self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) { int num = (int)[self performSelector:NSSelectorFromString(countOfKey)]; NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1]; for (int i = 0; i<num-1; i++) { num = (int)[self performSelector:NSSelectorFromString(countOfKey)]; } for (int j = 0; j<num; j++) { id objc = [self performSelector:NSSelectorFromString(objectInKeyAtIndex) withObject:@(num)]; [mArray addObject:objc]; } return mArray; } } #pragma clang diagnostic pop 複制代碼

  1. 判斷是否能夠直接指派執行個體變量,不能的情況下就調用

    valueForUndefinedKey:

    或抛出異常

NSString *undefinedMethodName = @"valueForUndefinedKey:"; IMP undefinedIMP = class_getMethodImplementation([self class], NSSelectorFromString(undefinedMethodName)); if (![self.class accessInstanceVariablesDirectly]) { if (undefinedIMP) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" return [self performSelector:NSSelectorFromString(undefinedMethodName) withObject:key]; #pragma clang diagnostic pop } else { @throw [NSException exceptionWithName:@"FXUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key %@.", self, NSStringFromSelector(_cmd), key] userInfo:nil]; } } 複制代碼

  1. 找相關執行個體變量,找到了就傳回

NSMutableArray *mArray = [self getIvarListName]; NSString *_key = [NSString stringWithFormat:@"_%@",key]; NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key]; NSString *isKey = [NSString stringWithFormat:@"is%@",Key]; if ([mArray containsObject:_key]) { Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String); return object_getIvar(self, ivar);; } else if ([mArray containsObject:_isKey]) { Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String); return object_getIvar(self, ivar);; } else if ([mArray containsObject:key]) { Ivar ivar = class_getInstanceVariable([self class], key.UTF8String); return object_getIvar(self, ivar);; } else if ([mArray containsObject:isKey]) { Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String); return object_getIvar(self, ivar);; } 複制代碼

  1. 調用

    valueForUndefinedKey:

    或抛出異常

if (undefinedIMP) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" return [self performSelector:NSSelectorFromString(undefinedMethodName) withObject:key]; #pragma clang diagnostic pop } else { @throw [NSException exceptionWithName:@"FXUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key %@.", self, NSStringFromSelector(_cmd), key] userInfo:nil]; } 複制代碼

3.封裝的方法

這裡簡單封裝了幾個用到的方法

  • fx_performSelectorWithMethodName:value:key:

    安全調用方法及傳兩個參數

- (BOOL)fx_performSelectorWithMethodName:(NSString *)methodName value:(id)value key:(id)key { if ([self respondsToSelector:NSSelectorFromString(methodName)]) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [self performSelector:NSSelectorFromString(methodName) withObject:value withObject:key]; #pragma clang diagnostic pop return YES; } return NO; } 複制代碼

  • fx_performSelectorWithMethodName:key:

    安全調用方法及傳參

- (BOOL)fx_performSelectorWithMethodName:(NSString *)methodName key:(id)key { if ([self respondsToSelector:NSSelectorFromString(methodName)]) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [self performSelector:NSSelectorFromString(methodName) withObject:key]; #pragma clang diagnostic pop return YES; } return NO; } 複制代碼

  • getIvarListName

    取成員變量

- (NSMutableArray *)getIvarListName { NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1]; unsigned int count = 0; Ivar *ivars = class_copyIvarList([self class], &count); for (int i = 0; i<count; i++) { Ivar ivar = ivars[i]; const char *ivarNameChar = ivar_getName(ivar); NSString *ivarName = [NSString stringWithUTF8String:ivarNameChar]; NSLog(@"ivarName == %@",ivarName); [mArray addObject:ivarName]; } free(ivars); return mArray; } 複制代碼

KVC中還有一些異常小技巧,在前文中已經提及過,這裡再總結一下

五、KVC異常小技巧

1.技巧一——自動轉換類型

  • 用int類型指派會自動轉成__NSCFNumber

[person setValue:@18 forKey:@"age"]; [person setValue:@"20" forKey:@"age"]; NSLog(@"%@-%@", [person valueForKey:@"age"], [[person valueForKey:@"age"] class]); 複制代碼

  • 用結構體類型類型指派會自動轉成NSConcreteValue

ThreeFloats floats = {1.0, 2.0, 3.0}; NSValue *value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)]; [person setValue:value forKey:@"threeFloats"]; NSLog(@"%@-%@", [person valueForKey:@"threeFloats"], [[person valueForKey:@"threeFloats"] class]); 複制代碼

2.技巧二——設定空值

有時候在設值時設定空值,可以通過重寫

setNilValueForKey

來監聽,但是以下代碼隻有列印一次

// Int類型設定nil [person setValue:nil forKey:@"age"]; // NSString類型設定nil [person setValue:nil forKey:@"subject"]; @implementation FXPerson - (void)setNilValueForKey:(NSString *)key { NSLog(@"設定 %@ 是空值", key); } @end 複制代碼

這是因為

setNilValueForKey

隻對NSNumber類型有效

ios 通過kvc修改屬性會觸發kvo_iOS探索 KVC原理及自定義

3.技巧三——未定義的key

對于未定義的key我們可以通過重寫

setValue:forUndefinedKey:

valueForUndefinedKey:

來監聽

@implementation FXPerson - (void)setValue:(id)value forUndefinedKey:(NSString *)key { NSLog(@"未定義的key——%@",key); } - (id)valueForUndefinedKey:(NSString *)key { NSLog(@"未定義的key——%@",key); return @"未定義的key"; } @end 複制代碼

4.技巧四——鍵值驗證

一個比較雞肋的功能——鍵值驗證,可以自行展開做重定向

NSError *error; NSString *name = @"Felix"; if (![person validateValue:&name forKey:@"names" error:&error]) { NSLog(@"%@",error); }else{ NSLog(@"%@", [person valueForKey:@"name"]); } @implementation FXPerson - (BOOL)validateValue:(inout id _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError *__autoreleasing _Nullable *)outError { if([inKey isEqualToString:@"name"]){ [self setValue:[NSString stringWithFormat:@"裡面修改一下: %@",*ioValue] forKey:inKey]; return YES; } *outError = [[NSError alloc]initWithDomain:[NSString stringWithFormat:@"%@ 不是 %@ 的屬性",inKey,self] code:10088 userInfo:nil]; return NO; } @end 複制代碼

寫在後面

我們平時開發中經常用到KVC,了解KVC的使用和原理對我們會有很大幫助,具體可以下載下傳Demo操作一下

正在跳轉 (iOS交流裙 密碼:123)