iOS KVC學習記錄

即將成為型男的濤發表於2019-03-31

前言

目錄

一、概述

KVC全稱是Key Value Coding(鍵值編碼),是可以通過物件屬性名稱(Key)直接對屬性值(value)編碼(coding)“編碼”可以理解為“賦值及訪問”。而不需要呼叫明確的存取方法。這樣就可以在執行時動態在訪問和修改物件的屬性,而不是在編譯時確定。

KVC的優勢是在沒有訪問器(setter、getter)方法的類中,此時點語法無法使用。

KVC提供了一種間接訪問其屬性方法或成員變數的機制,可以通過字串來訪問對應的屬性方法或成員變數。

二、使用

KVC的定義都是對NSObject的擴充套件來實現的,Objective-c中有個顯式的NSKeyValueCoding類別名,所以對於所有繼承了NSObject在型別,都能使用KVC。

在 Swift 中處理 KVC和 Objective-C 中還是有些細微的差別。比如,Objective-C 中所有的類都繼承自 NSObject,而 Swift 中卻不是,所以我們在 Swift 中需要顯式的宣告繼承自 NSObject。

因為 Swift 中的 Optional 機制,所以 valueForKey 方法返回的是一個 Optional 值,我們還需要對返回值做一次解包處理,才能得到實際的屬性值。

1、基本使用

下面是KVC中最常用的幾個方法:

//直接通過Key來取值
- (nullable id)valueForKey:(NSString *)key; 

//通過Key來設值
- (void)setValue:(nullable id)value forKey:(NSString *)key;

//通過KeyPath來取值
- (nullable id)valueForKeyPath:(NSString *)keyPath;

//通過KeyPath來設值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
複製程式碼

舉例:Book類擁有書名name屬性以及author,且author類擁有一個address屬性。

@interface Author : NSObject
@property(strong, nonatomic) NSString* address;
@end

@implementation Author
@end

@interface Book : NSObject
@property(strong, nonatomic) NSString* name;// 書名
@property(strong, nonatomic) Author* author;// 作者
@end

@implementation Book
@end
複製程式碼

使用時:

//  ViewController.m
#import "ViewController.h"
#import "Book.h"

@interface ViewController ()
@end
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Book *book = [[Book alloc]init];
    [book setValue:@"Hello world" forKey:@"name"]; // 設定值
    NSLog(@"age=%@",[book valueForKey:@"name"]);  // 取值
    
    // keyPath 方式
    [book setValue:@"ShangHai" forKeyPath:@"author.address"]; // 設定值
    NSLog(@"author.address=%@",[book valueForKeyPath:@"author.address"]);//取值
}

@end
複製程式碼

2、底層執行機制

  • [object setValue:@”value” forKey:@”property”]
  1. 存取器(setter方法)匹配:先尋找與setKey同名的方法。找到直接賦值。[self setProperty:@”value”]。

  2. 例項變數匹配:尋找與key,_isKey,_key,isKey同名的例項變數,直接賦值。property = value或_property = value等。

  3. 找不到,就會直接報錯 setValue:forUndefinedKey:報找不到的錯誤。

如果我們想讓這個類禁用KVC,那麼重寫+ (BOOL)accessInstanceVariablesDirectly方法讓其返回NO即可,這樣的話如果KVC沒有找到set:屬性名時,會直接用setValue:forUNdefinedKey:方法。

+ (BOOL)accessInstanceVariablesDirectly;
//預設返回YES,表示如果沒有找到Set<Key>方法的話,會按照_key,_iskey,key,iskey的順序搜尋成員,設定成NO就不這樣搜尋
複製程式碼
  • [object valueForKey:@”property”]
  1. 訪問器(getter方法)匹配:先尋找與key,isKey, getKey (實測還有_key)同名的方法。

  2. 例項變數匹配:尋找與key, _key,isKey,_isKey同名的例項變數。

  3. 如果還沒有找到的話,呼叫valueForUndefinedKey:。

若value值為BOOL或Int等值型別時,KVC可以自動的將數值或結構體型的資料打包或解包成NSNumber或NSValue物件,以達到適配的目的。

Ps:swift中使用keyPath的方式,但是僅支援struct

struct Book {
    var name:String
}

var book = Book(name: "Swift")
// set
book[keyPath: name] = "swift4"
// get
let valueOfName = book[keyPath:name]

複製程式碼

3、鍵值驗證

在實際開發中我們獲取對一些Key的value值有一些特殊的要求。KVC為我們提供了驗證Key對應的Value是否可用的方法:

- (BOOL)validateValue:(inoutid*)ioValue forKey:(NSString*)inKey error:(outNSError**)outError;
複製程式碼

這為我們提供了一次糾錯的機會。但是,KVC是不會自動呼叫鍵值驗證方法的,就是說我們如果想要鍵值驗證則需要手動驗證。也就是說,需要自己需要驗證的類中重寫-(BOOL)-validate:error:,預設返回Yes。

舉例:

@implementation Book
- (BOOL)validateName:(id *)value error:(out NSError * _Nullable __autoreleasing *)outError{
    NSString* name = *value;
    name = name.capitalizedString;
    if ([name isEqualToString:@"Not-name"]) {
        return NO;
    }
    return YES;
}
@end
- (void)viewDidLoad {
    [super viewDidLoad];
    
    Book *book = [[Book alloc]init];
    NSError* error;
    NSString *value = @"Not-name"; // result 為 NO 
    //NSString *value = @"BookName";  // result 為 YES
    BOOL result = [book validateValue:&value forKey:@"name" error:&error];
    if (result) {
        NSLog(@"OK");
    } else {
        NSLog(@"NO");
    }
}
複製程式碼

4、不存在的key及nil值處理

  • 處理不存在的key值

我們可以考慮重寫setValue: forUndefinedKey:方法與valueForUndefinedKey:方法

- (void)setValue:(id)value forUndefinedKey:(NSString *)key {

    NSLog(@"您設定的key:[%@]不存在", key);
    NSLog(@"您設定的value為:[%@]", value);

}

- (id)valueForUndefinedKey:(NSString *)key {

    NSLog(@"您訪問的key:[%@]不存在", key);
    return nil;

}
複製程式碼
  • value為nil的處理

當程式嘗試為某個屬性設定nil值時,如果該屬性並不接受nil值,那麼程式將會自動執行該物件的setNilValueForKey:方法。我們同樣可以重寫這個方法:

- (void)setNilValueForKey:(NSString *)key {
    //對不能接受nil的屬性進行處理
    if ([key isEqualToString:@"price"]) {
        //對應你具體的業務來處理
        price = 0;
    }else {
        [super setNilValueForKey:key];
    }
}
複製程式碼

5、一些函式操作

KVC同時還提供了一些較複雜的函式,主要有下面這些:

  • 集合運算子

目前總共有五個函式:@avg, @count , @max , @min ,@sum5

  • 物件運算子

共有兩個:@distinctUnionOfObjects 及 @unionOfObjects

它們的返回值都是NSArray。兩者的區別在於:前者會去除返回值中重複的資料,後者則將資料全部返回。

舉例:

@interface Book : NSObject
@property(assign, nonatomic) NSInteger price;
@end

@implementation Book
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    Book *book1 = [Book new];
    book1.price = 40;
    Book *book2 = [Book new];
    book2.price = 20;
    Book *book3 = [Book new];
    book3.price = 30;
    Book *book4 = [Book new];
    book4.price = 10;
    // 價格重複的
    Book *book5 = [Book new];
    book5.price = 10;
    
    NSLog(@"----------集合運算子----------");
    NSArray* arr = @[book1,book2,book3,book4];
    NSNumber* sum = [arr valueForKeyPath:@"@sum.price"];
    NSLog(@"sum:%f",sum.floatValue);
    NSNumber* avg = [arr valueForKeyPath:@"@avg.price"];
    NSLog(@"avg:%f",avg.floatValue);
    NSNumber* count = [arr valueForKeyPath:@"@count"];
    NSLog(@"count:%f",count.floatValue);
    NSNumber* min = [arr valueForKeyPath:@"@min.price"];
    NSLog(@"min:%f",min.floatValue);
    NSNumber* max = [arr valueForKeyPath:@"@max.price"];
    NSLog(@"max:%f",max.floatValue);
    
    NSLog(@"----------物件操作符----------");
    NSArray* arrBooks = @[book1,book2,book3,book4, book5];
    NSLog(@"distinctUnionOfObjects");
    NSArray* arrDistinct = [arrBooks valueForKeyPath:@"@distinctUnionOfObjects.price"];
    for (NSNumber *price in arrDistinct) {
        NSLog(@"%f",price.floatValue);
    }
    NSLog(@"unionOfObjects");
    NSArray* arrUnion = [arrBooks valueForKeyPath:@"@unionOfObjects.price"];
    for (NSNumber *price in arrUnion) {
        NSLog(@"%f",price.floatValue);
    }
}
@end

---輸出結果:---
----------集合運算子----------
sum:100.000000
avg:25.000000
count:4.000000
min:10.000000
max:40.000000

----------物件操作符----------
distinctUnionOfObjects
10.000000
20.000000
30.000000
40.000000
unionOfObjects
40.000000
20.000000
30.000000
10.000000
10.000000
複製程式碼

三、實際應用

1、訪問私有變數

對於類裡的私有屬性,Objective-C是無法直接訪問的,但是KVC是可以的。

@interface Book : NSObject
{
    NSString * owner;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    Book *book = [Book new];
    
    // 這種訪問方式,會直接報錯。因為owner是私有屬性。
    // book.owner = @"tao"; 
    
    // 利用KVC訪問到私有變數
    [book setValue:@"tao" forKey:@"owner"];
    NSLog([book valueForKey:@"owner"]); // 輸出 tao
}
@end
複製程式碼

2、修改一些控制元件的內部屬性

在開發中,我們常常需要對一些控制元件的某些屬性做修改,但是很多UI控制元件都由很多內部UI控制元件組合而成的,系統並沒有提供這訪問這些控制元件的API,這樣我們就無法正常地訪問和修改這些控制元件的樣式。但是,KVC可以幫我們解決大部分這種型別的問題。

iOS KVC學習記錄

3、結合Runtime打造字典轉model

可以利用KVC和執行時將字典轉換為模型。 具體程式碼可以我的Github上檢視。

四、總結

上面的幾點就是本人對KVC學習的一些記錄,如有不妥之處,請大家多多指正。關於KVC更多更詳細的資料,大家可以去到官方文件Key-Value Coding Programming Guide檢視學習。