iOS之武功秘籍?: KVC原理及自定義

iOS之武功秘籍 文章匯總

寫在前面

平常開發中經常用到KVC賦值取值、字典轉模型,但KVC的底層原理又是怎樣的呢?本篇就來帶你走進KVC..

本節可能用到的秘籍Demo

一、KVC初探

①.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 {
        TCJPerson *person = [TCJPerson alloc];
        [person setValue:@"TCJ" forKey:@"name"];
        [person setValue:@"CJ" 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;

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

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

于是乎蘋果開發者們就運用通用原則給所有屬性都提供了同一個入口——objc-accessors.mmsetter方法根據修飾符不同調用不同方法,最后統一調用reallySetProperty方法

來到reallySetProperty再根據內存偏移量取出屬性,根據修飾符完成不同的操作

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

至于是哪里調用的objc_setProperty_nonatomic_copy

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

二、KVC使用

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

typedef struct {
    float x, y, z;
} ThreeFloats;

@interface TCJPerson : NSObject{
    @public
    NSString *myName;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSArray *array;
@property (nonatomic, strong) NSMutableArray *mArray;
@property (nonatomic, assign) NSInteger  age;
@property (nonatomic) ThreeFloats threeFloats;
@property (nonatomic, strong) TCJStudent *student;
@end

@interface TCJStudent : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *subject;
@property (nonatomic, copy) NSString *nick;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, assign) NSInteger length;
@property (nonatomic, strong) NSMutableArray *penArr;
@end

①.基本類型

注意一下NSInteger這類的屬性賦值時要轉成NSNumberNSString

TCJPerson *person = [[TCJPerson alloc] init];

// 1:Key-Value Coding (KVC) : 基本類型
[person setValue:@"TCJ" forKey:@"name"];
[person setValue:@19 forKey:@"age"];
[person setValue:@"CJ" forKey:@"myName"];
NSLog(@"%@ - %@ - %@",[person valueForKey:@"name"],[person valueForKey:@"age"],[person valueForKey:@"myName"]);

打印結果:

 ① - KVC簡介[10226:310799] TCJ - 19 - CJ

②.集合類型

TCJPerson *person = [[TCJPerson alloc] init];

// 2:KVC - 集合類型 -
person.array = @[@"1",@"2",@"3"];
// 由于不是可變數組 - 無法做到
// person.array[0] = @"100";
// 搞一個新的數組 - KVC 賦值就OK
NSArray *array = [person valueForKey:@"array"];
// 用 array 的值創建一個新的數組
array = @[@"100",@"2",@"3"];
[person setValue:array forKey:@"array"];
NSLog(@"%@",[person valueForKey:@"array"]);
 
// 取出數組以可變數組形式保存,再修改 KVC 的方式
NSMutableArray *ma = [person mutableArrayValueForKey:@"array"];
ma[0] = @"100";
NSLog(@"%@",[person valueForKey:@"array"]);

打印結果:

 ① - KVC簡介[10226:310799] (
    100,
    2,
    3
)
 ① - KVC簡介[10226:310799] (
    100,
    2,
    3
)

③.訪問非對象類型——結構體

  • 對于非對象類型的賦值總是把它先轉成NSValue類型再進行存儲
  • 取值時轉成對應類型后再使用
TCJPerson *person = [[TCJPerson alloc] init];

// 3:KVC - 訪問非對象屬性 - 面試可能問到
// 3.1 賦值
ThreeFloats floats = {1., 2., 3.};
NSValue *value  = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[person setValue:value forKey:@"threeFloats"];
NSValue *reslut = [person valueForKey:@"threeFloats"];
NSLog(@"非對象類型%@",reslut);
 
// 3.2 取值
ThreeFloats th;
[reslut getValue:&th] ;
NSLog(@"非對象類型的值%f - %f - %f",th.x,th.y,th.z);

打印結果:

 ① - KVC簡介[10226:310799] 非對象類型{length = 12, bytes = 0x0000803f0000004000004040}
 ① - KVC簡介[10226:310799] 非對象類型的值1.000000 - 2.000000 - 3.000000

④.集合操作符

  • 聚合操作符
    • @avg: 返回操作對象指定屬性的平均值
    • @count: 返回操作對象指定屬性的個數
    • @max: 返回操作對象指定屬性的最大值
    • @min: 返回操作對象指定屬性的最小值
    • @sum: 返回操作對象指定屬性值之和
  • 數組操作符
    • @distinctUnionOfObjects: 返回操作對象指定屬性的集合--去重
    • @unionOfObjects: 返回操作對象指定屬性的集合
  • 嵌套操作符
    • @distinctUnionOfArrays: 返回操作對象(嵌套集合)指定屬性的集合--去重,返回的是 NSArray
    • @unionOfArrays: 返回操作對象(集合)指定屬性的集合
    • @distinctUnionOfSets: 返回操作對象(嵌套集合)指定屬性的集合--去重,返回的是 NSSet

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

TCJPerson *person = [[TCJPerson alloc] init];
NSMutableArray *personArray = [NSMutableArray array];
    for (int i = 0; i < 6; i++) {
        TCJStudent *p = [TCJStudent new];
        NSDictionary* dict = @{
                               @"name":@"Tom",
                               @"age":@(18+i),
                               @"nick":@"Cat",
                               @"length":@(175 + 2*arc4random_uniform(6)),
                               };
        [p setValuesForKeysWithDictionary:dict];
        [personArray addObject:p];
    }
    NSLog(@"%@", [personArray valueForKey:@"length"]);
    
    /// 平均身高
    float avg = [[personArray valueForKeyPath:@"@avg.length"] floatValue];
    NSLog(@"%f", avg);
    
    int count = [[personArray valueForKeyPath:@"@count.length"] intValue];
    NSLog(@"%d", count);
    
    int sum = [[personArray valueForKeyPath:@"@sum.length"] intValue];
    NSLog(@"%d", sum);
    
    int max = [[personArray valueForKeyPath:@"@max.length"] intValue];
    NSLog(@"%d", max);
    
    int min = [[personArray valueForKeyPath:@"@min.length"] intValue];
    NSLog(@"%d", min);

打印結果:

 ① - KVC簡介[10544:326204] (
    185,
    177,
    185,
    177,
    181,
    175
)
 ① - KVC簡介[10544:326204] 180.000000
 ① - KVC簡介[10544:326204] 6
 ① - KVC簡介[10544:326204] 1080
 ① - KVC簡介[10544:326204] 185
 ① - KVC簡介[10544:326204] 175

⑤.層層嵌套

通過forKeyPath對實例變量(student)進行取值賦值

TCJPerson *person = [[TCJPerson alloc] init];

TCJStudent *student = [[TCJStudent alloc] init];
    student.subject    = @"iOS";
    person.student     = student;
    [person setValue:@"葵花寶典" forKeyPath:@"student.subject"];
    NSLog(@"%@",[person valueForKeyPath:@"student.subject"]);

打印結果:

 ① - KVC簡介[10544:326204] 葵花寶典

三、KVC底層原理

由于NSKeyValueCoding的實現在Foundation框架中,但它又不開源,我們只能通過KVC官方文檔來了解它

①.設值過程

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


  • ①.按set<Key>:_set<Key>:順序查找對象中是否有對應的方法
    • 找到了直接調用設值
    • 沒有找到跳轉第2步
  • ②.判斷accessInstanceVariablesDirectly結果
    • 為YES時按照_<key>_is<Key><key>is<Key>的順序查找成員變量,找到了就賦值;找不到就跳轉第3步
    • 為NO時跳轉第3步
  • ③.調用setValue:forUndefinedKey:。默認情況下會引發一個異常,但是繼承于NSObject的子類可以重寫該方法就可以避免崩潰并做出相應措施

②.取值過程

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


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

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

    • 如果找到這三個方法,則創建一個響應所有NSSet方法的代理集合對象,并返回該對象
    • 如果沒有找到,跳轉到第4步
  • ④.判斷accessInstanceVariablesDirectly

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

    • 屬性值是對象,直接返回
    • 屬性值不是對象,但是可以轉化為NSNumber類型,則將屬性值轉化為NSNumber 類型返回
    • 屬性值不是對象,也不能轉化為NSNumber類型,則將屬性值轉化為NSValue類型返回
  • ⑥.調用valueForUndefinedKey:.默認情況下會引發一個異常,但是繼承于NSObject的子類可以重寫該方法就可以避免崩潰并做出相應措施

四、自定義KVC

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

新建一個NSObject+TCJKVC的分類,.h開放兩個方法,.m引入<objc/runtime.h>

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

①.自定義setter方法

1.非空判斷

if (key == nil || key.length == 0) return;

2.找到相關方法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 cj_performSelectorWithMethodName:setKey value:value]) {
    NSLog(@"*********%@**********",setKey);
    return;
} else if ([self cj_performSelectorWithMethodName:_setKey value:value]) {
    NSLog(@"*********%@**********",_setKey);
    return;
} else if ([self cj_performSelectorWithMethodName:setIsKey value:value]) {
    NSLog(@"*********%@**********",setIsKey);
    return;
}

3.判斷是否能夠直接賦值實例變量,不能的情況下就調用setValue:forUndefinedKey:或拋出異常

NSString *undefinedMethodName = @"setValue:forUndefinedKey:";
IMP undefinedIMP = class_getMethodImplementation([self class], NSSelectorFromString(undefinedMethodName));

if (![self.class accessInstanceVariablesDirectly]) {
    if (undefinedIMP) {
        [self cj_performSelectorWithMethodName:undefinedMethodName value:value key:key];
    } else {
        @throw [NSException exceptionWithName:@"TCJUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key %@.", self, NSStringFromSelector(_cmd), key] userInfo:nil];
    }
    return;
}

4.找相關實例變量進行賦值

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;
}

5.調用setValue:forUndefinedKey:或拋出異常

if (undefinedIMP) {
    [self cj_performSelectorWithMethodName:undefinedMethodName value:value key:key];
} else {
    @throw [NSException exceptionWithName:@"TCJUnknownKeyException" 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是否為空

②.自定義getter方法

1.非空判斷

if (key == nil  || key.length == 0) return nil;

2.找相關方法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

3.對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

4.判斷是否能夠直接賦值實例變量,不能的情況下就調用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:@"TCJUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key %@.", self, NSStringFromSelector(_cmd), key] userInfo:nil];
    }
}

5.找相關實例變量,找到了就返回

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);;
}

6.調用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:@"TCJUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key %@.", self, NSStringFromSelector(_cmd), key] userInfo:nil];
}

③.封裝的方法

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

  • cj_performSelectorWithMethodName:value:key:安全調用方法及傳兩個參數

    - (BOOL)cj_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;
    }
    
  • cj_performSelectorWithMethodName:key:安全調用方法及傳參

    - (BOOL)cj_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異常小技巧

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

  • 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]);
    
    

②.技巧二 —— 設置空值

有時候在設值時設置空值,可以通過重寫setNilValueForKey來監聽,但是以下代碼只有打印一次

// Int類型設置nil
[person setValue:nil forKey:@"age"];
// NSString類型設置nil
[person setValue:nil forKey:@"subject"];

@implementation TCJPerson

- (void)setNilValueForKey:(NSString *)key {
    NSLog(@"設置 %@ 是空值", key);
}

@end

這是因為setNilValueForKey只對NSNumber類型有效

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

對于未定義的key我們可以通過重寫setValue:forUndefinedKey:valueForUndefinedKey:來監聽

@implementation TCJPerson

- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    NSLog(@"未定義的key——%@",key);
}

- (id)valueForUndefinedKey:(NSString *)key {
    NSLog(@"未定義的key——%@",key);
    return @"未定義的key";
}

@end

④.技巧四——鍵值驗證

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

NSError *error;
NSString *name = @"TCJ";
if (![person validateValue:&name forKey:@"names" error:&error]) {
    NSLog(@"%@",error);
}else{
    NSLog(@"%@", [person valueForKey:@"name"]);
}

@implementation TCJPerson

- (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

寫在后面

和諧學習,不急不躁.我還是我,顏色不一樣的煙火.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,791評論 6 545
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,795評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,943評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,057評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,773評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,106評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,082評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,282評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,793評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,507評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,741評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,220評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,929評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,325評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,661評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,482評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,702評論 2 380

推薦閱讀更多精彩內容