Objective-C 之 KVC 原理

蘋果官網地址
Key-value coding is a mechanism enabled by the NSKeyValueCoding informal protocol that objects adopt to provide indirect access to their properties. When an object is key-value coding compliant, its properties are addressable via string parameters through a concise, uniform messaging interface. This indirect access mechanism supplements the direct access afforded by instance variables and their associated accessor methods.

一、KVC簡介

KVC的全稱是Key-Value Coding,俗稱“鍵值編碼”,可以通過一個key來訪問某個屬性。常見的API有:

- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;//通過keyPath可以設置屬性的屬性
- (void)setValue:(id)value forKey:(NSString *)key;//通過key設置自己的屬性
- (id)valueForKeyPath:(NSString *)keyPath;//通過keyPath訪問屬性的屬性
- (id)valueForKey:(NSString *)key; //通過key訪問自己的屬性

二、賦值:-setValue:forKey:

2.1規則:

  1. 先查找setter方法,set<Key>_set<Key>
  2. 未找到,且+(BOOL)accessInstanceVariablesDirectly返回YES(默認為YES),則查找實例變量;
  3. 查找 _<key>, _is<Key>, <key>, or is<Key>,查到即執行
  4. 拋出異常,也可以執行-setValue:forUndefinedKey:處理異常

2.2不同數據類型的使用

新建一個工程,添加一個類Animal.h

typedef struct {
    float a;
    float b;
    float c;
}ThreeFloats;


@interface Animal : NSObject


@property (nonatomic,copy) NSString *nickname;

@property (nonatomic,assign) int age;

@property (nonatomic,strong) NSArray *friends;

@property (nonatomic,assign) ThreeFloats floats;


@end

在viewcontroller里面

- (void)viewDidLoad {
    [super viewDidLoad];
    
    
    Animal *animal = [Animal alloc];
    
    //1、基本對象類型
    [animal setValue:@"dog" forKey:@"nickname"];
    NSLog(@"nickname:%@", animal.nickname);

    //2、集合類型
    [animal setValue:@[@"Jack", @"Rose"] forKey:@"friends"];
    NSLog(@"friends:%@", animal.friends);
    NSMutableArray *tmpFriends = [animal mutableArrayValueForKey:@"friends"];
    tmpFriends[1] = @"Lili";
    NSLog(@"friends:%@", animal.friends);
    
    //3、 NSNumber支持的基礎類型
    [animal setValue:@3 forKey:@"age"];
    NSLog(@"age:%d", animal.age);
    
    //4、 其他類型,除上述類型外,其他類型要包裝為NSValue進行賦值。
    ThreeFloats threeFloats = {1.1, 2.2, 3.3};
    NSValue *value = [NSValue valueWithBytes:&threeFloats
                                    objCType:@encode(ThreeFloats)];
    [animal setValue:value forKey:@"floats"];
    
    NSValue *floatsValue = [animal valueForKey:@"floats"];
    ThreeFloats floats;
    [floatsValue getValue:&floats];
    NSLog(@"\na = %f, b = %f, c = %f", floats.a, floats.b, floats.c);
    
    
}

三、取值:valueForKey:

2.1規則:

看下圖,也可以到蘋果官網查看,文章開頭有鏈接


1111.png
  1. 先查找該類中是否有直接的getter,如果有則調用getter,執行第5步;
  2. 沒有直接的getter,查找類中是否有上述NSArray塊中的方法,如果實現第一個后面兩個中的一個或兩個方法,則返回一個集合對象,這個集合對象可以響應所有NSArray的方法
  3. 查找實例是否實現了上述NSSet塊中的3個方法,如果有,則返回一個集合,這個集合可以響應NSSet的所有方法。
  4. 檢查類方法accessInstanceVariablesDirectly如果返回YES則查找對象的實例變量是否匹配下列各式:_<key>->_is<Key>-><key>->is<Key>,如果匹配,執行第5步,否則執行第6步
  5. 如果屬性類型是對象則直接返回;如果屬性類型是被NSNumber支持的類型,則返回一個NSNumber對象;否則返回一個NSValue對象。
  6. 拋出異常,也可以執行- (nullable id)valueForUndefinedKey:(NSString *)key處理異常

2.2不同數據類型的使用

在上面代碼的基礎上修改,在Animal.h

- (NSInteger)countOfNames {
    return _friends.count;
}

- (id)objectInNamesAtIndex:(NSUInteger)index {
    return _friends[index];
}


-(NSArray *)namesAtIndexes:(NSIndexSet *)indexes{
    return [_friends objectsAtIndexes:indexes];
}

- (nullable id)valueForUndefinedKey:(NSString *)key{
    
    return @"UndefinedKey";
}

在viewcontroller里面

Animal *animal = [[Animal alloc] init];
    
    //1、基本對象
    animal.nickname = @"Jack";
    NSString *nickname = [animal valueForKey:@"nickname"];
    NSLog(@"nickname: %@", nickname);
    
    
    //2、數組類型的使用,通過-valueForKey:取值時
    //   需要實現-countOf<Key>和-objectIn<Key>AtIndex:兩個方法,我們以names為key,來獲取friends屬性:
    animal.friends = @[@"Jack", @"Rose"];
    NSArray *names = [animal valueForKey:@"names"];
    NSLog(@"names: %@", names);
    
    
    //3、NSNumber支持的基本類型
    animal.age = 3;
    NSNumber *number = [animal valueForKey:@"age"];
    NSLog(@"age: %d", [number intValue]);
    
    
    //4、NSValue類型
    ThreeFloats threeFloats = {1.1, 2.2, 3.3};
    animal.floats = threeFloats;
    NSValue *floatsValue = [animal valueForKey:@"floats"];
    ThreeFloats floats;
    [floatsValue getValue:&floats];
    NSLog(@"\na = %f, b = %f, c = %f", floats.a, floats.b, floats.c);

四、KVC模式匹配的順序及驗證

4.1 setter和getter方法

添加代碼

@interface Animal () {
    NSString *_nickname;
}

@end

@implementation Animal

#pragma mark - setter
// set<Key>
//- (void)setNickname:(NSString *)nickname {
//    printf("%s\n", __func__);
//    _nickname = nickname;
//}

// _set<Key>
- (void)_setNickname:(NSString *)nickname {
    printf("%s\n", __func__);
    _nickname = nickname;
}

// setIs<Key>
- (void)setIsNickname:(NSString *)nickname {
    printf("%s\n", __func__);
    _nickname = nickname;
}

#pragma mark - getter
//// get<Key>
//- (NSString *)getNickname {
//    printf("%s\n", __func__);
//    return _nickname;
//}

// <key>
- (NSString *)nickname {
    printf("%s\n", __func__);
    return _nickname;
}

// _<key>
- (NSString *)_nickname {
    printf("%s\n", __func__);
    return _nickname;
}

// is<Key>
- (NSString *)isNickname {
    printf("%s\n", __func__);
    return _nickname;
}
@end

viewController相關代碼

    Animal *animal = [[Animal alloc] init];
    [animal setValue:@"Jack" forKey:@"nickname"];
    NSLog(@"\nnickname: %@", [animal valueForKey:@"nickname"]);

這里不在一一測試,可自行注釋方法來驗證

4.2 實例變量

@interface Animal : NSObject{
    @public
    //NSString *_nickname;
    NSString *nickname;
    NSString *_isNickname;
    NSString *isNickname;
}

@end

@implementation Animal

@end

    Animal *animal = [[Animal alloc] init];
    [animal setValue:@"Jack" forKey:@"nickname"];
    //NSLog(@"_nickname: %@", animal->_nickname);
    NSLog(@"nickname: %@", animal->nickname);
    NSLog(@"_isNickname: %@", animal->_isNickname);
    NSLog(@"isNickname: %@", animal->isNickname);

打印結果:

2020-04-10 11:23:20.867793+0800 kvc[31577:17258752] nickname: (null)
2020-04-10 11:23:20.868476+0800 kvc[31577:17258752] _isNickname: Jack
2020-04-10 11:23:20.868991+0800 kvc[31577:17258752] isNickname: (null)

4.3 + (BOOL)accessInstanceVariablesDirectly方法

添加代碼

@implementation Animal

+ (BOOL)accessInstanceVariablesDirectly {
    return NO;
}

@end

再次運行,拋出異常:'NSUnknownKeyException', reason: '[<Animal 0x6000015fb5c0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key nickname.'

五、kvc特殊異常用法

5.1 自動類型轉換

當賦值對象是intbool等基本類型時,賦值NSString,取值會自動轉換為對象類型。如下,當age賦值為NSString,取值時對應類型時__NSCFNumber

//@property (nonatomic, assign) int  age;
[person setValue:@"20" forKey:@"age"]; // int - string
 NSLog(@"%@-%@",[person valueForKey:@"age"],[[person valueForKey:@"age"] class]);

5.2 設置空值

賦值nil,當方法參數類型為NSNumber或者NSValue時,可以重寫setNilValueForKey方法重定向。

5.3

設值或者取值找不到key,也可以重寫對應的方法setValue: forUndefinedKey:valueForUndefinedKey重定向,這個上面有說過。

5.4 鍵值驗證

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

我們可以用這個方法,進行容錯派發消息轉發等操作。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • KVC(Key-value coding)鍵值編碼,iOS的開發中,可以允許開發者通過Key名直接訪問對象的屬性,...
    CALayer_Sai閱讀 2,538評論 0 4
  • 什么是KVC? KVC(Key-value coding)鍵值編碼,單看這個名字可能不太好理解。其實是指iOS的開...
    祀夢_閱讀 947評論 0 7
  • 什么是KVC? KVC(Key-value coding)鍵值編碼,單看這個名字可能不太好理解。其實是指iOS的開...
    薩繆閱讀 818評論 0 5
  • 原文:iOS 關于KVC的一些總結 本文參考: KVC官方文檔 KVC原理剖析 iOS KVC詳解 KVC 簡介 ...
    liyoucheng2014閱讀 953評論 0 3
  • 什么是KVC? KVC(Key-value coding)鍵值編碼,單看這個名字可能不太好理解。其實是指iOS的開...
    薩繆閱讀 4,669評論 1 13