iOS探索 KVC原理及自定義

歡迎閱讀iOS探索系列(按序閱讀食用效果更加)html

寫在前面

日常開發中常常用到KVC賦值取值、字典轉模型,但KVC的底層原理又是怎樣的呢?c++

Demogit

1、KVC初探

1.KVC定義及API

KVC(Key-Value Coding)是利用NSKeyValueCoding 非正式協議實現的一種機制,對象採用這種機制來提供對其屬性的間接訪問github

寫下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經常使用方法,這些也是咱們在平常開發中常常用到的api

// 經過 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方法

試想一下編譯器要爲成千上萬個屬性分別生成settergetter方法那不得歇菜了嘛安全

因而乎蘋果開發者們就運用通用原則給全部屬性都提供了同一個入口——objc-accessors.mmsetter方法根據修飾符不一樣調用不一樣方法,最後統一調用reallySetProperty方法 bash

來到reallySetProperty再根據內存偏移量取出屬性,根據修飾符完成不一樣的操做多線程

  • 在第一個屬性name賦值時,此時的內存偏移量爲8,恰好偏移isa所佔內存(8字節)來到name
  • 在第二個屬性nickname賦值時,此時的內存偏移量爲16,恰好偏移isa、name所佔內存(8+8)來到nickname

至因而哪裏調用的objc_setProperty_nonatomic_copy

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

2、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
複製代碼

3、KVC底層原理

因爲NSKeyValueCoding的實如今Foundation框架,但它又不開源,咱們只能經過KVO官方文檔來了解它

1.設值過程

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

  1. set<Key>:_set<Key>:順序查找對象中是否有對應的方法

    • 找到了直接調用設值
    • 沒有找到跳轉第2步
  2. 判斷accessInstanceVariablesDirectly結果

    • 爲YES時按照_<key>_is<Key><key>is<Key>的順序查找成員變量,找到了就賦值;找不到就跳轉第3步
    • 爲NO時跳轉第3步
  3. 調用setValue:forUndefinedKey:。默認狀況下會引起一個異常,可是繼承於NSObject的子類能夠重寫該方法就能夠避免崩潰並作出相應措施

2.取值過程

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

  1. 按照get<Key><key>is<Key>_<key>順序查找對象中是否有對應的方法

    • 若是有則調用getter,執行第5步
    • 若是沒有找到,跳轉到第2步
  2. 查找是否有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步
  3. 查找名爲countOf<Key>enumeratorOf<Key>memberOf<Key>這三個方法(對應於NSSet類定義的原始方法)

    • 若是找到這三個方法,則建立一個響應全部NSSet方法的代理集合對象,並返回該對象
    • 若是沒有找到,跳轉到第4步
  4. 判斷accessInstanceVariablesDirectly

    • 爲YES時按照_<key>_is<Key><key>is<Key>的順序查找成員變量,找到了就取值
    • 爲NO時跳轉第6步
  5. 判斷取出的屬性值

    • 屬性值是對象,直接返回
    • 屬性值不是對象,可是能夠轉化爲NSNumber類型,則將屬性值轉化爲NSNumber 類型返回
    • 屬性值不是對象,也不能轉化爲NSNumber類型,則將屬性值轉化爲NSValue類型返回
  6. 調用valueForUndefinedKey:。默認狀況下會引起一個異常,可是繼承於NSObject的子類能夠重寫該方法就能夠避免崩潰並作出相應措施

4、自定義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中還有一些異常小技巧,在前文中已經說起過,這裏再總結一下

5、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類型有效

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操做一下

相關文章
相關標籤/搜索