iOS-KVC

本文章属于简书-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;
}
运行结果

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
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设置对象某一属性时,该属性不能为基本数据类型,否则报错:

将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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,295评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,928评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,682评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,209评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,237评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,965评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,586评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,487评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,016评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,136评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,271评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,948评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,619评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,139评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,252评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,598评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,267评论 2 358

推荐阅读更多精彩内容