iOS-KVC

weixin_33866037發表於2018-07-08
本文章屬於簡書-mr_young_原創,轉載請註明出處:

https://www.jianshu.com/p/23dce2ead05f

1.什麼是KVC?

KVC:即key-value-coding,鍵值編碼。

2.KVC應用場景

1> 通過鍵值路徑可為物件的屬性進行賦值,也可以設定物件的私有屬性。

Person *person = [Person alloc] init];
[person setValue:@"young" forKey:@"name"];
// 如果物件的某一屬性也是物件
[person setValue:@"doge" forKeyPath:@"dog.name"];

2> 通過鍵值路徑獲取物件某一屬性的值,也可以獲取私有屬性。

NSString *pName = [person valueForKey:@"name"];
// 也可以通過kvc獲取到dog的屬性
NSString *dogName = [person valueForKeyPath:@"dog.name"];

3> 簡單的字典-->模型

#import <Foundation/Foundation.h>

@class Dog;

@interface Person : NSObject

@property (nonatomic, assign) int p_id
@property (nonatomic, copy) NSString name;
@property (nonatomic, strong) Dog *dog;

+ (instancetype)personWithDict:(NSDictionary *)dict;
@end

#import "Person.h"
#import "Dog.h"

@implementation Person

+ (instancetype)personWithDict:(NSDictionary *)dict {
    // dict: @{@"id":@(10), @"name":@"young", @"dog":@{@"name":@"doge", @"age":@(3)}}
    Person *person = [Person alloc] init];
    [person setValuesForKeysWithDictionary:dict];
    return person;
}

// Person類中找不到key(此時找不到id)對應的屬性時,呼叫此方法
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    if ([key isEqualToString:@"id"]) {
        self.p_id = [value intValue];
    } else {
        NSLog(@"Person找不到%@對應的屬性", key);
        return nil;
    }
}

@end

4> 用KVC獲取集合中的元素個數、最大值、最小值、平均值以及求和。⚠️注意:通過KVC來獲取集合中元素的個數時應當使用@"@count"

#import <Foundation/Foundation.h>
#import "Person.h"
#import "Dog.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p1 = [[Person alloc] init];
        Dog *dog1 = [[Dog alloc] init];
        dog1.name = @"dog1";
        dog1.age = 3;
        p1.name = @"young";
        p1.dog = dog1;
        
        Person *p2 = [[Person alloc] init];
        Dog *dog2 = [[Dog alloc] init];
        dog2.name = @"dog2";
        dog2.age = 2;
        p2.name = @"wang";
        p2.dog = dog2;
        
        Person *p3 = [[Person alloc] init];
        Dog *dog3 = [[Dog alloc] init];
        dog3.name = @"dog3";
        dog3.age = 6;
        p3.name = @"zhang";
        p3.dog = dog3;
        
        NSArray *arr = [NSArray arrayWithObjects:p1, p2, p3, nil];
        NSLog(@"\narr.count:%@\nmaxAge:%@\nminAg:%@\navgAge:%@\nsumAge:%@",
            [arr valueForKey:@"@count"], [arr valueForKeyPath:@"@max.dog.age"], 
            [arr valueForKeyPath:@"@min.dog.age"], [arr valueForKeyPath:@"@avg.dog.age"], 
            [arr valueForKeyPath:@"@sum.dog.age"]);
    }
    return 0;
}
2062420-e66c98407a5d8de5.png
執行結果

3.手擼KVC之前

3.1 首先,我們要知道在OC中一個屬性對應有四個成員變數,並且他們的優先順序依次為:_key>_isKey>key>isKey

// Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject {
    NSString *_name;
    NSString *_isName;
    NSString *name;
    NSString *isName;
    
    /**
     NSString *_isName;
     NSString *name;
     NSString *isName;
     */
    
    /**
     NSString *name;
     NSString *isName;
     */
    
    /**
     NSString *isName;
     */
}
@end

// Person.m
#import "Person.h"
@implementation Person

- (instancetype)init {
    self = [super init];
    if (self) {
        _name = @"_name";
        _isName = @"_isName";
        name = @"name";
        isName = @"isName";
        // 執行結果: KVC-Test[1799:310739] name: _name
        
        /**
        // _name = @"_name";
        _isName = @"_isName";
        name = @"name";
        isName = @"isName";
        // 執行結果: KVC-Test[1799:310739] name: _isName
        
        // _name = @"_name";
        // _isName = @"_isName";
        name = @"name";
        isName = @"isName";
        // 執行結果: KVC-Test[1799:310739] name: name
        
        // _name = @"_name";
        // _isName = @"_isName";
        // name = @"name";
        isName = @"isName";
        // 執行結果: KVC-Test[1799:310739] name: isName
        */
    }
    return self;
}

@end

// main.m
#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        NSLog(@"name: %@", [person valueForKey:@"name"]);
    }
    return 0;
}
@end

3.2 通過valueForKey:@"key"獲取物件某一屬性時:

  1. 首先,呼叫getter方法,一個屬性對應有3個getter方法,並且它們的優先順序依次為:getKey>key>isKey
// 還是用3.1的例子?,重寫Person類的3個getter方法
// Person.m
#import "Person.h"
@implementation Person

- (instancetype)init {
    self = [super init];
    if (self) {
        _name = @"_name";
        _isName = @"_isName";
        name = @"name";
        isName = @"isName";
    }
    return self;
}

- (NSString *)getName {
    // 執行結果:KVC-Test[2115:403089] name: getter- getName
    return @"getter- getName";
}

- (NSString *)name {
    // 註釋getName方法後,執行結果:KVC-Test[2115:403089] name: getter - name
    return @"getter - name";
}

- (NSString *)isName {
    // 註釋getName和name方法後,執行結果:KVC-Test[2115:403089] name: getter - isName
    return @"getter - isName";
}
@end
  1. 如果沒有getter方法,則會呼叫NSArry的兩個物件方法 - (NSInteger)countOfKey- (id)objectInKeyAtIndex:(NSInteger)index,如果實現了這兩個方法則會返回一個陣列。
#import "Person.h"
@implementation Person

- (NSUInteger)countOfName {
    return 10;
}

- (id)objectInNameAtIndex:(NSUInteger)index {
    return [NSString stringWithFormat:@"name-%lu", (unsigned long)index];
}
// 列印結果為
//KVC-Test[2278:424945] name: (
//                             "name-0",
//                             "name-1",
//                             "name-2",
//                             "name-3",
//                             "name-4",
//                             "name-5",
//                             "name-6",
//                             "name-7",
//                             "name-8",
//                             "name-9"
//                             )
//Program ended with exit code: 0
@end
2062420-ea4ab2c07cf15e55.jpeg
3.2.2執行結果
  1. 如果沒有NSArray的兩個物件方法,則會呼叫類方法+(BOOL)accessInstanceVariablesDirectly。返回NO,則不會查詢任何成員變數,並且報錯valueForUndefinedKey:]: this class is not key value coding-compliant for the key。因此,該類方法預設返回YES。
  1. 只有+ (BOOL)accessInstanceVariablesDirectly返回YES時,KVC才會查詢Person類中的4個成員變數:_key_isKeykeyisKey(_name_isNamenameisName)。它們優先順序依次為_key>_isKey>key>isKey, 當找到_key時,即返回_key的值,如果找不到_key再查詢_isKey...如果最後找不到isKey的成員變數時,同樣報錯valueForUndefinedKey:]: this class is not key value coding-compliant for the key
  1. 如果類方法+ (BOOL)accessInstanceVariablesDirectly返回NO,或最後也沒又找到isKey對應的成員變數時,系統還會給我們最後一次機會防止程式崩潰:就是實現- (id)valueForUndefinedKey:(NSString *)key方法,該方法return nil;時,會防止程式崩潰。

3.3 通過setValue:@"value" ForKey:@"key"設定物件某一屬性時:

  1. 首先,呼叫setter方法。對於一個屬性,對應有2個setter方法,並且它們的優先順序依次為:setKey>setIsKey
// main.m
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        [person setValue:@"young" forKey:@"name"];
    }
    return 0;
}
@end

// Person.m
#import "Person.h"

@implementation Person
- (void)setName:(NSString *)name {
    // 執行結果:KVC-Test[2315:444214] setter name - young
    NSLog(@"setter name - %@", name);
}

- (void)setIsName:(NSString *)name {
    // 註釋setName方法後,執行結果:KVC-Test[2315:444214] setter isName - young
    NSLog(@"setter isName - %@", name);
}
@end
  1. 如果這個類沒有實現setter方法,則系統就會呼叫類方法:+ (BOOL)accessInstanceVariablesDirectly預設返回YES; 如果該類方法返回值為NO,便如方法名所言,不能訪問例項的所有成員變數。並且報錯:setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name.'
  1. 當然,如果+ (BOOL)accessInstanceVariablesDirectly``+ (BOOL)accessInstanceVariablesDirectly類方法返回NO時,系統還給我們最後一次防止報錯的機會:實現- (void)setValue:(id)value forUndefinedKey:(NSString *)key方法。
#import "Person.h"

@implementation Person
+ (BOOL)accessInstanceVariablesDirectly {
    return NO;
}

- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    // 執行結果:KVC-Test[2391:471126] Person類中沒有name屬性
    NSLog(@"Person類中沒有%@屬性", key);
}
@end

3.4 將nil值通過KVC設定物件某一屬性時,該屬性不能為基本資料型別,否則報錯:

2062420-a24bd0f5a068215e.jpeg
將nil賦值給int型別的age屬性
如果實現了- (void)setNilValueForKey:(NSString *)key方法,便可防止此異常的發生。

4.手擼KVC

// NSObject+MyKVX.h
#import <Foundation/Foundation.h>

@interface NSObject (MyKVC)
- (id)my_valueForKey:(NSString *)key;
- (void)my_setValue:(id)value forKey:(NSString *)key;
@end

// NSObject+MyKVX.m
#import "NSObject+MyKVC.h"
#import <objc/runtime.h>

@implementation NSObject (MyKVC)

- (id)my_valueForKey:(NSString *)key {
    // key值需要合法
    if (key == nil || key.length == 0) {
        NSLog(@"key is nil!!!");
        return nil;
    }
    
    // step 1: 呼叫getter方法: getKey、key、getIsKey
    NSString *getKey = [NSString stringWithFormat:@"get%@", key.capitalizedString];
    // 呼叫getKey方法
    if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
        return [self performSelector:NSSelectorFromString(getKey)];
    }
    // 呼叫key方法
    if ([self respondsToSelector:NSSelectorFromString(key)]) {
        return [self performSelector:NSSelectorFromString(key)];
    }
    NSString *getIsKey = [NSString stringWithFormat:@"getIs%@", key.capitalizedString];
    if ([self respondsToSelector:NSSelectorFromString(getIsKey)]) {
        return [self performSelector:NSSelectorFromString(getIsKey)];
    }
    
    // step 2: 沒有getter方法,呼叫countOfKey和objectInKeyAtIndex:(NSInteger)index方法
    NSString *countOfKey = [NSString stringWithFormat:@"countOf%@", key.capitalizedString];
    // 注意⚠️:objectInKeyAtIndex:(NSInteger)index有引數,方法名要包含冒號
    NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:", key.capitalizedString];
    NSUInteger keyCount = 0;
    if ([self respondsToSelector:NSSelectorFromString(countOfKey)] &&
        [self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) {
        keyCount = (NSUInteger)[self performSelector:NSSelectorFromString(countOfKey)];
        NSMutableArray *keyValues = [NSMutableArray array];
        for (int i = 0; i < keyCount; i++) {
            [keyValues addObject:[self performSelector:NSSelectorFromString(objectInKeyAtIndex) withObject:@(i)]];
        }
        return keyValues;
    }
    
    // step 3: 沒有實現countOfKey和objectInKeyAtIndex方法
    // 並且 accessInstanceVariablesDirectly類方法返回NO
    if (![self.class accessInstanceVariablesDirectly]) { // 丟擲異常
        NSException *exception = [NSException exceptionWithName:@"MyKVC Exception" reason:@"你不讓我訪問成員變數,我能怎麼辦!" userInfo:nil];
        @throw exception;
    }
    
    // 獲取類中的所有成員變數
    unsigned int ivarCount;
    Ivar *ivars = class_copyIvarList(self.class, &ivarCount); // 拷貝一個類的成員變數列表到堆區域
    
    // step 4: 根據成員變數的優先順序將獲取成員變數的值
    Ivar _keyIvar = nil;
    Ivar _isKeyIvar = nil;
    Ivar keyIvar = nil;
    Ivar isKeyIvar = nil;
    for (int i = 0; i < ivarCount; i++) {
        Ivar ivar = ivars[i];
        NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
        if ([name isEqualToString:[NSString stringWithFormat:@"_%@", key]]) {
            _keyIvar = ivar;
        } else if ([name isEqualToString:[NSString stringWithFormat:@"_is%@", key.capitalizedString]]) {
            _isKeyIvar = ivar;
        } else if ([name isEqualToString:[NSString stringWithFormat:@"%@", key]]) {
            keyIvar = ivar;
        } else if ([name isEqualToString:[NSString stringWithFormat:@"is%@", key.capitalizedString]]) {
            isKeyIvar = ivar;
        }
    }
    id obj = nil;
    if (_keyIvar) {
        obj = object_getIvar(self, _keyIvar);
        free(ivars);
        return obj;
    } else if (_isKeyIvar) {
        obj = object_getIvar(self, _isKeyIvar);
        free(ivars);
        return obj;
    } else if (keyIvar) {
        obj = object_getIvar(self, keyIvar);
        free(ivars);
        return obj;
    } else if (isKeyIvar) {
        obj = object_getIvar(self, isKeyIvar);
        free(ivars);
        return obj;
    }

    // step 5: 呼叫valueForUndefinedKey方法
    free(ivars);
    return [self valueForUndefinedKey:key];
}

- (void)my_setValue:(id)value forKey:(NSString *)key {
    // key值需要合法
    if (key == nil || key.length == 0) {
        NSLog(@"key is nil!!!");
        return;
    }
    
    // 第一步:呼叫setter方法:setKey、setIsKey; 注意⚠️:setter方法有引數,所以方法名後有冒號
    // capitalizedString方法:字串首字母大寫
    NSString *setKey = [NSString stringWithFormat:@"set%@:", key.capitalizedString];
    // 呼叫setKey方法
    if ([self respondsToSelector:NSSelectorFromString(setKey)]) {
        [self performSelector:NSSelectorFromString(setKey) withObject:value];
        return;
    }
    // 呼叫setIsKey
    NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:", key.capitalizedString];
    if ([self respondsToSelector:NSSelectorFromString(setIsKey)]) {
        [self performSelector:NSSelectorFromString(setIsKey) withObject:value];
    }
    
    // 第二步:沒有setter方法 並且 accessInstanceVariablesDirectly類方法返回NO
    if (![self.class accessInstanceVariablesDirectly]) { // 丟擲異常
        NSException *exception = [NSException exceptionWithName:@"MyKVC Exception" reason:@"你不讓我訪問成員變數,我能怎麼辦!" userInfo:nil];
        @throw exception;
        return;
    }
    
    // 獲取類中的所有成員變數
    unsigned int ivarCount;
    Ivar *ivars = class_copyIvarList(self.class, &ivarCount); // 拷貝一個類的成員變數列表到堆區域
    
    // 第三步:根據成員變數的優先順序將value賦值給成員變數
    Ivar _keyIvar = nil;
    Ivar _isKeyIvar = nil;
    Ivar keyIvar = nil;
    Ivar isKeyIvar = nil;
    for (int i = 0; i < ivarCount; i++) {
        Ivar ivar = ivars[i];
        NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
        if ([name isEqualToString:[NSString stringWithFormat:@"_%@", key]]) {
            _keyIvar = ivar;
        } else if ([name isEqualToString:[NSString stringWithFormat:@"_is%@", key.capitalizedString]]) {
            _isKeyIvar = ivar;
        } else if ([name isEqualToString:[NSString stringWithFormat:@"%@", key]]) {
            keyIvar = ivar;
        } else if ([name isEqualToString:[NSString stringWithFormat:@"is%@", key.capitalizedString]]) {
            isKeyIvar = ivar;
        }
    }
    if (_keyIvar) {
        object_setIvar(self, _keyIvar, value);
        free(ivars);
        return;
    } else if (_isKeyIvar) {
        object_setIvar(self, _isKeyIvar, value);
        free(ivars);
        return;
    } else if (keyIvar) {
        object_setIvar(self, keyIvar, value);
        free(ivars);
        return;
    } else if (isKeyIvar) {
        object_setIvar(self, isKeyIvar, value);
        free(ivars);
        return;
    }
    // 異常處理方法
    [self setValue:value forUndefinedKey:key];
    free(ivars);
}

@end